~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/HttpServer.py

  • Committer: Lukáš Lalinský
  • Date: 2007-12-29 18:55:20 UTC
  • mto: (3156.1.1 ianc-integration)
  • mto: This revision was merged to the branch mainline in revision 3157.
  • Revision ID: lalinsky@gmail.com-20071229185520-42dr3votzal51sl8
Don't require a working tree in cmd_annotate.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
 
17
import BaseHTTPServer
17
18
import errno
18
 
import httplib
19
19
import os
 
20
from SimpleHTTPServer import SimpleHTTPRequestHandler
 
21
import socket
20
22
import posixpath
21
23
import random
22
24
import re
23
 
import SimpleHTTPServer
24
 
import socket
25
 
import SocketServer
26
25
import sys
27
26
import threading
28
27
import time
29
28
import urllib
30
29
import urlparse
31
30
 
32
 
from bzrlib import transport
33
 
from bzrlib.transport import local
 
31
from bzrlib.transport import Server
 
32
from bzrlib.transport.local import LocalURLServer
34
33
 
35
34
 
36
35
class WebserverNotAvailable(Exception):
42
41
        return 'path %s is not in %s' % self.args
43
42
 
44
43
 
45
 
class TestingHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
 
44
class TestingHTTPRequestHandler(SimpleHTTPRequestHandler):
46
45
    """Handles one request.
47
46
 
48
 
    A TestingHTTPRequestHandler is instantiated for every request received by
49
 
    the associated server. Note that 'request' here is inherited from the base
50
 
    TCPServer class, for the HTTP server it is really a connection which itself
51
 
    will handle one or several HTTP requests.
 
47
    A TestingHTTPRequestHandler is instantiated for every request
 
48
    received by the associated server.
52
49
    """
53
 
    # Default protocol version
54
 
    protocol_version = 'HTTP/1.1'
55
 
 
56
 
    # The Message-like class used to parse the request headers
57
 
    MessageClass = httplib.HTTPMessage
58
 
 
59
 
    def setup(self):
60
 
        SimpleHTTPServer.SimpleHTTPRequestHandler.setup(self)
61
 
        tcs = self.server.test_case_server
62
 
        if tcs.protocol_version is not None:
63
 
            # If the test server forced a protocol version, use it
64
 
            self.protocol_version = tcs.protocol_version
65
50
 
66
51
    def log_message(self, format, *args):
67
52
        tcs = self.server.test_case_server
79
64
        connection early to avoid polluting the test results.
80
65
        """
81
66
        try:
82
 
            SimpleHTTPServer.SimpleHTTPRequestHandler.handle_one_request(self)
 
67
            SimpleHTTPRequestHandler.handle_one_request(self)
83
68
        except socket.error, e:
84
 
            # Any socket error should close the connection, but some errors are
85
 
            # due to the client closing early and we don't want to pollute test
86
 
            # results, so we raise only the others.
87
 
            self.close_connection = 1
88
 
            if (len(e.args) == 0
89
 
                or e.args[0] not in (errno.EPIPE, errno.ECONNRESET,
90
 
                                     errno.ECONNABORTED, errno.EBADF)):
 
69
            if (len(e.args) > 0
 
70
                and e.args[0] in (errno.EPIPE, errno.ECONNRESET,
 
71
                                  errno.ECONNABORTED,)):
 
72
                self.close_connection = 1
 
73
                pass
 
74
            else:
91
75
                raise
92
76
 
93
77
    _range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)$')
126
110
                    return 0, []
127
111
        return tail, ranges
128
112
 
129
 
    def _header_line_length(self, keyword, value):
130
 
        header_line = '%s: %s\r\n' % (keyword, value)
131
 
        return len(header_line)
132
 
 
133
 
    def send_head(self):
134
 
        """Overrides base implementation to work around a bug in python2.5."""
135
 
        path = self.translate_path(self.path)
136
 
        if os.path.isdir(path) and not self.path.endswith('/'):
137
 
            # redirect browser - doing basically what apache does when
138
 
            # DirectorySlash option is On which is quite common (braindead, but
139
 
            # common)
140
 
            self.send_response(301)
141
 
            self.send_header("Location", self.path + "/")
142
 
            # Indicates that the body is empty for HTTP/1.1 clients 
143
 
            self.send_header('Content-Length', '0')
144
 
            self.end_headers()
145
 
            return None
146
 
 
147
 
        return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
148
 
 
149
113
    def send_range_content(self, file, start, length):
150
114
        file.seek(start)
151
115
        self.wfile.write(file.read(length))
166
130
    def get_multiple_ranges(self, file, file_size, ranges):
167
131
        self.send_response(206)
168
132
        self.send_header('Accept-Ranges', 'bytes')
169
 
        boundary = '%d' % random.randint(0,0x7FFFFFFF)
170
 
        self.send_header('Content-Type',
171
 
                         'multipart/byteranges; boundary=%s' % boundary)
172
 
        boundary_line = '--%s\r\n' % boundary
173
 
        # Calculate the Content-Length
174
 
        content_length = 0
175
 
        for (start, end) in ranges:
176
 
            content_length += len(boundary_line)
177
 
            content_length += self._header_line_length(
178
 
                'Content-type', 'application/octet-stream')
179
 
            content_length += self._header_line_length(
180
 
                'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
181
 
            content_length += len('\r\n') # end headers
182
 
            content_length += end - start # + 1
183
 
        content_length += len(boundary_line)
184
 
        self.send_header('Content-length', content_length)
 
133
        boundary = "%d" % random.randint(0,0x7FFFFFFF)
 
134
        self.send_header("Content-Type",
 
135
                         "multipart/byteranges; boundary=%s" % boundary)
185
136
        self.end_headers()
186
 
 
187
 
        # Send the multipart body
188
137
        for (start, end) in ranges:
189
 
            self.wfile.write(boundary_line)
190
 
            self.send_header('Content-type', 'application/octet-stream')
191
 
            self.send_header('Content-Range', 'bytes %d-%d/%d'
192
 
                             % (start, end, file_size))
 
138
            self.wfile.write("--%s\r\n" % boundary)
 
139
            self.send_header("Content-type", 'application/octet-stream')
 
140
            self.send_header("Content-Range", "bytes %d-%d/%d" % (start,
 
141
                                                                  end,
 
142
                                                                  file_size))
193
143
            self.end_headers()
194
144
            self.send_range_content(file, start, end - start + 1)
195
145
        # Final boundary
196
 
        self.wfile.write(boundary_line)
 
146
        self.wfile.write("--%s\r\n" % boundary)
197
147
 
198
148
    def do_GET(self):
199
149
        """Serve a GET request.
207
157
        ranges_header_value = self.headers.get('Range')
208
158
        if ranges_header_value is None or os.path.isdir(path):
209
159
            # Let the mother class handle most cases
210
 
            return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
 
160
            return SimpleHTTPRequestHandler.do_GET(self)
211
161
 
212
162
        try:
213
163
            # Always read in binary mode. Opening files in text
284
234
        return self._translate_path(path)
285
235
 
286
236
    def _translate_path(self, path):
287
 
        return SimpleHTTPServer.SimpleHTTPRequestHandler.translate_path(
288
 
            self, path)
 
237
        return SimpleHTTPRequestHandler.translate_path(self, path)
289
238
 
290
239
    if sys.platform == 'win32':
291
240
        # On win32 you cannot access non-ascii filenames without
316
265
            return path
317
266
 
318
267
 
319
 
class TestingHTTPServerMixin:
 
268
class TestingHTTPServer(BaseHTTPServer.HTTPServer):
320
269
 
321
 
    def __init__(self, test_case_server):
 
270
    def __init__(self, server_address, RequestHandlerClass,
 
271
                 test_case_server):
 
272
        BaseHTTPServer.HTTPServer.__init__(self, server_address,
 
273
                                           RequestHandlerClass)
322
274
        # test_case_server can be used to communicate between the
323
275
        # tests and the server (or the request handler and the
324
276
        # server), allowing dynamic behaviors to be defined from
325
277
        # the tests cases.
326
278
        self.test_case_server = test_case_server
327
279
 
328
 
    def tearDown(self):
329
 
         """Called to clean-up the server.
330
 
 
331
 
         Since the server may be (surely is, even) in a blocking listen, we
332
 
         shutdown its socket before closing it.
333
 
         """
334
 
         # Note that is this executed as part of the implicit tear down in the
335
 
         # main thread while the server runs in its own thread. The clean way
336
 
         # to tear down the server is to instruct him to stop accepting
337
 
         # connections and wait for the current connection(s) to end
338
 
         # naturally. To end the connection naturally, the http transports
339
 
         # should close their socket when they do not need to talk to the
340
 
         # server anymore. This happens naturally during the garbage collection
341
 
         # phase of the test transport objetcs (the server clients), so we
342
 
         # don't have to worry about them.  So, for the server, we must tear
343
 
         # down here, from the main thread, when the test have ended.  Note
344
 
         # that since the server is in a blocking operation and since python
345
 
         # use select internally, shutting down the socket is reliable and
346
 
         # relatively clean.
347
 
         try:
348
 
             self.socket.shutdown(socket.SHUT_RDWR)
349
 
         except socket.error, e:
350
 
             # WSAENOTCONN (10057) 'Socket is not connected' is harmless on
351
 
             # windows (occurs before the first connection attempt
352
 
             # vila--20071230)
353
 
             if not len(e.args) or e.args[0] != 10057:
354
 
                 raise
355
 
         # Let the server properly close the socket
356
 
         self.server_close()
357
 
 
358
 
class TestingHTTPServer(SocketServer.TCPServer, TestingHTTPServerMixin):
359
 
 
360
 
    def __init__(self, server_address, request_handler_class,
361
 
                 test_case_server):
362
 
        TestingHTTPServerMixin.__init__(self, test_case_server)
363
 
        SocketServer.TCPServer.__init__(self, server_address,
364
 
                                        request_handler_class)
365
 
 
366
 
 
367
 
class TestingThreadingHTTPServer(SocketServer.ThreadingTCPServer,
368
 
                                 TestingHTTPServerMixin):
369
 
    """A threading HTTP test server for HTTP 1.1.
370
 
 
371
 
    Since tests can initiate several concurrent connections to the same http
372
 
    server, we need an independent connection for each of them. We achieve that
373
 
    by spawning a new thread for each connection.
374
 
    """
375
 
 
376
 
    def __init__(self, server_address, request_handler_class,
377
 
                 test_case_server):
378
 
        TestingHTTPServerMixin.__init__(self, test_case_server)
379
 
        SocketServer.ThreadingTCPServer.__init__(self, server_address,
380
 
                                                 request_handler_class)
381
 
        # Decides how threads will act upon termination of the main
382
 
        # process. This is prophylactic as we should not leave the threads
383
 
        # lying around.
384
 
        self.daemon_threads = True
385
 
 
386
 
 
387
 
class HttpServer(transport.Server):
 
280
    def server_close(self):
 
281
        """Called to clean-up the server.
 
282
 
 
283
        Since the server may be in a blocking read, we shutdown the socket
 
284
        before closing it.
 
285
        """
 
286
        self.socket.shutdown(socket.SHUT_RDWR)
 
287
        BaseHTTPServer.HTTPServer.server_close(self)
 
288
 
 
289
 
 
290
class HttpServer(Server):
388
291
    """A test server for http transports.
389
292
 
390
293
    Subclasses can provide a specific request handler.
391
294
    """
392
295
 
393
 
    # The real servers depending on the protocol
394
 
    http_server_class = {'HTTP/1.0': TestingHTTPServer,
395
 
                         'HTTP/1.1': TestingThreadingHTTPServer,
396
 
                         }
397
 
 
398
296
    # Whether or not we proxy the requests (see
399
297
    # TestingHTTPRequestHandler.translate_path).
400
298
    proxy_requests = False
402
300
    # used to form the url that connects to this server
403
301
    _url_protocol = 'http'
404
302
 
405
 
    def __init__(self, request_handler=TestingHTTPRequestHandler,
406
 
                 protocol_version=None):
407
 
        """Constructor.
408
 
 
409
 
        :param request_handler: a class that will be instantiated to handle an
410
 
            http connection (one or several requests).
411
 
 
412
 
        :param protocol_version: if specified, will override the protocol
413
 
            version of the request handler.
414
 
        """
415
 
        transport.Server.__init__(self)
 
303
    # Subclasses can provide a specific request handler
 
304
    def __init__(self, request_handler=TestingHTTPRequestHandler):
 
305
        Server.__init__(self)
416
306
        self.request_handler = request_handler
417
307
        self.host = 'localhost'
418
308
        self.port = 0
419
309
        self._httpd = None
420
 
        self.protocol_version = protocol_version
421
310
        # Allows tests to verify number of GET requests issued
422
311
        self.GET_request_nb = 0
423
312
 
424
313
    def _get_httpd(self):
425
314
        if self._httpd is None:
426
 
            rhandler = self.request_handler
427
 
            # Depending on the protocol version, we will create the approriate
428
 
            # server
429
 
            if self.protocol_version is None:
430
 
                # Use the request handler one
431
 
                proto_vers = rhandler.protocol_version
432
 
            else:
433
 
                # Use our own, it will be used to override the request handler
434
 
                # one too.
435
 
                proto_vers = self.protocol_version
436
 
            # Create the appropriate server for the required protocol
437
 
            serv_cls = self.http_server_class.get(proto_vers, None)
438
 
            if serv_cls is None:
439
 
                raise httplib.UnknownProtocol(proto_vers)
440
 
            else:
441
 
                self._httpd = serv_cls((self.host, self.port), rhandler, self)
 
315
            self._httpd = TestingHTTPServer((self.host, self.port),
 
316
                                            self.request_handler,
 
317
                                            self)
442
318
            host, self.port = self._httpd.socket.getsockname()
443
319
        return self._httpd
444
320
 
445
321
    def _http_start(self):
446
 
        """Server thread main entry point. """
447
 
        self._http_running = False
448
 
        try:
449
 
            try:
450
 
                httpd = self._get_httpd()
451
 
                self._http_base_url = '%s://%s:%s/' % (self._url_protocol,
452
 
                                                       self.host, self.port)
453
 
                self._http_running = True
454
 
            except:
455
 
                # Whatever goes wrong, we save the exception for the main
456
 
                # thread. Note that since we are running in a thread, no signal
457
 
                # can be received, so we don't care about KeyboardInterrupt.
458
 
                self._http_exception = sys.exc_info()
459
 
        finally:
460
 
            # Release the lock or the main thread will block and the whole
461
 
            # process will hang.
462
 
            self._http_starting.release()
 
322
        httpd = self._get_httpd()
 
323
        self._http_base_url = '%s://%s:%s/' % (self._url_protocol,
 
324
                                               self.host,
 
325
                                               self.port)
 
326
        self._http_starting.release()
463
327
 
464
 
        # From now on, exceptions are taken care of by the
465
 
        # SocketServer.BaseServer or the request handler.
466
328
        while self._http_running:
467
329
            try:
468
 
                # Really an HTTP connection but the python framework is generic
469
 
                # and call them requests
470
330
                httpd.handle_request()
471
331
            except socket.timeout:
472
332
                pass
497
357
        # XXX: TODO: make the server back onto vfs_server rather than local
498
358
        # disk.
499
359
        assert backing_transport_server is None or \
500
 
            isinstance(backing_transport_server, local.LocalURLServer), \
 
360
            isinstance(backing_transport_server, LocalURLServer), \
501
361
            "HTTPServer currently assumes local transport, got %s" % \
502
362
            backing_transport_server
503
363
        self._home_dir = os.getcwdu()
504
364
        self._local_path_parts = self._home_dir.split(os.path.sep)
 
365
        self._http_starting = threading.Lock()
 
366
        self._http_starting.acquire()
 
367
        self._http_running = True
505
368
        self._http_base_url = None
506
 
 
507
 
        # Create the server thread
508
 
        self._http_starting = threading.Lock()
509
 
        self._http_starting.acquire()
510
369
        self._http_thread = threading.Thread(target=self._http_start)
511
370
        self._http_thread.setDaemon(True)
512
 
        self._http_exception = None
513
371
        self._http_thread.start()
514
 
 
515
372
        # Wait for the server thread to start (i.e release the lock)
516
373
        self._http_starting.acquire()
517
 
 
518
 
        if self._http_exception is not None:
519
 
            # Something went wrong during server start
520
 
            exc_class, exc_value, exc_tb = self._http_exception
521
 
            raise exc_class, exc_value, exc_tb
522
374
        self._http_starting.release()
523
375
        self.logs = []
524
376
 
525
377
    def tearDown(self):
526
378
        """See bzrlib.transport.Server.tearDown."""
527
 
        self._httpd.tearDown()
 
379
        self._httpd.server_close()
528
380
        self._http_running = False
529
 
        # We don't need to 'self._http_thread.join()' here since the thread is
530
 
        # a daemonic one and will be garbage collected anyway. Joining just
531
 
        # slows us down for no added benefit.
 
381
        self._http_thread.join()
532
382
 
533
383
    def get_url(self):
534
384
        """See bzrlib.transport.Server.get_url."""
538
388
        """See bzrlib.transport.Server.get_bogus_url."""
539
389
        # this is chosen to try to prevent trouble with proxies, weird dns,
540
390
        # etc
541
 
        return self._url_protocol + '://127.0.0.1:1/'
 
391
        return 'http://127.0.0.1:1/'
542
392
 
543
393
 
544
394
class HttpServer_urllib(HttpServer):