/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/http_server.py

  • Committer: Andrew Bennetts
  • Date: 2008-01-04 03:12:11 UTC
  • mfrom: (3164 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3320.
  • Revision ID: andrew.bennetts@canonical.com-20080104031211-wy4uxo2j4elvip1j
Merge from bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 Canonical Ltd
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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
18
17
import errno
 
18
import httplib
19
19
import os
20
 
from SimpleHTTPServer import SimpleHTTPRequestHandler
21
 
import socket
22
20
import posixpath
23
21
import random
24
22
import re
 
23
import SimpleHTTPServer
 
24
import socket
 
25
import SocketServer
25
26
import sys
26
27
import threading
27
28
import time
28
29
import urllib
29
30
import urlparse
30
31
 
31
 
from bzrlib.transport import Server
32
 
from bzrlib.transport.local import LocalURLServer
 
32
from bzrlib import transport
 
33
from bzrlib.transport import local
33
34
 
34
35
 
35
36
class WebserverNotAvailable(Exception):
41
42
        return 'path %s is not in %s' % self.args
42
43
 
43
44
 
44
 
class TestingHTTPRequestHandler(SimpleHTTPRequestHandler):
 
45
class TestingHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
45
46
    """Handles one request.
46
47
 
47
 
    A TestingHTTPRequestHandler is instantiated for every request
48
 
    received by the associated server.
 
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.
49
52
    """
 
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
50
65
 
51
66
    def log_message(self, format, *args):
52
67
        tcs = self.server.test_case_server
64
79
        connection early to avoid polluting the test results.
65
80
        """
66
81
        try:
67
 
            SimpleHTTPRequestHandler.handle_one_request(self)
 
82
            SimpleHTTPServer.SimpleHTTPRequestHandler.handle_one_request(self)
68
83
        except socket.error, e:
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:
 
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)):
75
91
                raise
76
92
 
77
93
    _range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)$')
110
126
                    return 0, []
111
127
        return tail, ranges
112
128
 
 
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
 
113
149
    def send_range_content(self, file, start, length):
114
150
        file.seek(start)
115
151
        self.wfile.write(file.read(length))
130
166
    def get_multiple_ranges(self, file, file_size, ranges):
131
167
        self.send_response(206)
132
168
        self.send_header('Accept-Ranges', 'bytes')
133
 
        boundary = "%d" % random.randint(0,0x7FFFFFFF)
134
 
        self.send_header("Content-Type",
135
 
                         "multipart/byteranges; boundary=%s" % boundary)
 
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)
136
185
        self.end_headers()
 
186
 
 
187
        # Send the multipart body
137
188
        for (start, end) in ranges:
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))
 
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))
143
193
            self.end_headers()
144
194
            self.send_range_content(file, start, end - start + 1)
145
 
            self.wfile.write("--%s\r\n" % boundary)
 
195
        # Final boundary
 
196
        self.wfile.write(boundary_line)
146
197
 
147
198
    def do_GET(self):
148
199
        """Serve a GET request.
156
207
        ranges_header_value = self.headers.get('Range')
157
208
        if ranges_header_value is None or os.path.isdir(path):
158
209
            # Let the mother class handle most cases
159
 
            return SimpleHTTPRequestHandler.do_GET(self)
 
210
            return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
160
211
 
161
212
        try:
162
213
            # Always read in binary mode. Opening files in text
233
284
        return self._translate_path(path)
234
285
 
235
286
    def _translate_path(self, path):
236
 
        return SimpleHTTPRequestHandler.translate_path(self, path)
 
287
        return SimpleHTTPServer.SimpleHTTPRequestHandler.translate_path(
 
288
            self, path)
237
289
 
238
290
    if sys.platform == 'win32':
239
291
        # On win32 you cannot access non-ascii filenames without
264
316
            return path
265
317
 
266
318
 
267
 
class TestingHTTPServer(BaseHTTPServer.HTTPServer):
 
319
class TestingHTTPServerMixin:
268
320
 
269
 
    def __init__(self, server_address, RequestHandlerClass,
270
 
                 test_case_server):
271
 
        BaseHTTPServer.HTTPServer.__init__(self, server_address,
272
 
                                           RequestHandlerClass)
 
321
    def __init__(self, test_case_server):
273
322
        # test_case_server can be used to communicate between the
274
323
        # tests and the server (or the request handler and the
275
324
        # server), allowing dynamic behaviors to be defined from
276
325
        # the tests cases.
277
326
        self.test_case_server = test_case_server
278
327
 
279
 
    def server_close(self):
280
 
        """Called to clean-up the server.
281
 
 
282
 
        Since the server may be in a blocking read, we shutdown the socket
283
 
        before closing it.
284
 
        """
285
 
        self.socket.shutdown(socket.SHUT_RDWR)
286
 
        BaseHTTPServer.HTTPServer.server_close(self)
287
 
 
288
 
 
289
 
class HttpServer(Server):
 
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):
290
388
    """A test server for http transports.
291
389
 
292
390
    Subclasses can provide a specific request handler.
293
391
    """
294
392
 
 
393
    # The real servers depending on the protocol
 
394
    http_server_class = {'HTTP/1.0': TestingHTTPServer,
 
395
                         'HTTP/1.1': TestingThreadingHTTPServer,
 
396
                         }
 
397
 
295
398
    # Whether or not we proxy the requests (see
296
399
    # TestingHTTPRequestHandler.translate_path).
297
400
    proxy_requests = False
299
402
    # used to form the url that connects to this server
300
403
    _url_protocol = 'http'
301
404
 
302
 
    # Subclasses can provide a specific request handler
303
 
    def __init__(self, request_handler=TestingHTTPRequestHandler):
304
 
        Server.__init__(self)
 
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)
305
416
        self.request_handler = request_handler
306
417
        self.host = 'localhost'
307
418
        self.port = 0
308
419
        self._httpd = None
 
420
        self.protocol_version = protocol_version
309
421
        # Allows tests to verify number of GET requests issued
310
422
        self.GET_request_nb = 0
311
423
 
312
424
    def _get_httpd(self):
313
425
        if self._httpd is None:
314
 
            self._httpd = TestingHTTPServer((self.host, self.port),
315
 
                                            self.request_handler,
316
 
                                            self)
 
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)
317
442
            host, self.port = self._httpd.socket.getsockname()
318
443
        return self._httpd
319
444
 
320
445
    def _http_start(self):
321
 
        httpd = self._get_httpd()
322
 
        self._http_base_url = '%s://%s:%s/' % (self._url_protocol,
323
 
                                               self.host,
324
 
                                               self.port)
325
 
        self._http_starting.release()
 
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()
326
463
 
 
464
        # From now on, exceptions are taken care of by the
 
465
        # SocketServer.BaseServer or the request handler.
327
466
        while self._http_running:
328
467
            try:
 
468
                # Really an HTTP connection but the python framework is generic
 
469
                # and call them requests
329
470
                httpd.handle_request()
330
471
            except socket.timeout:
331
472
                pass
356
497
        # XXX: TODO: make the server back onto vfs_server rather than local
357
498
        # disk.
358
499
        assert backing_transport_server is None or \
359
 
            isinstance(backing_transport_server, LocalURLServer), \
 
500
            isinstance(backing_transport_server, local.LocalURLServer), \
360
501
            "HTTPServer currently assumes local transport, got %s" % \
361
502
            backing_transport_server
362
503
        self._home_dir = os.getcwdu()
363
504
        self._local_path_parts = self._home_dir.split(os.path.sep)
 
505
        self._http_base_url = None
 
506
 
 
507
        # Create the server thread
364
508
        self._http_starting = threading.Lock()
365
509
        self._http_starting.acquire()
366
 
        self._http_running = True
367
 
        self._http_base_url = None
368
510
        self._http_thread = threading.Thread(target=self._http_start)
369
511
        self._http_thread.setDaemon(True)
 
512
        self._http_exception = None
370
513
        self._http_thread.start()
 
514
 
371
515
        # Wait for the server thread to start (i.e release the lock)
372
516
        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
373
522
        self._http_starting.release()
374
523
        self.logs = []
375
524
 
376
525
    def tearDown(self):
377
526
        """See bzrlib.transport.Server.tearDown."""
378
 
        self._httpd.server_close()
 
527
        self._httpd.tearDown()
379
528
        self._http_running = False
380
 
        self._http_thread.join()
 
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
532
 
382
533
    def get_url(self):
383
534
        """See bzrlib.transport.Server.get_url."""
387
538
        """See bzrlib.transport.Server.get_bogus_url."""
388
539
        # this is chosen to try to prevent trouble with proxies, weird dns,
389
540
        # etc
390
 
        return 'http://127.0.0.1:1/'
 
541
        return self._url_protocol + '://127.0.0.1:1/'
391
542
 
392
543
 
393
544
class HttpServer_urllib(HttpServer):