~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/http_server.py

  • Committer: John Arbash Meinel
  • Date: 2010-08-24 19:21:32 UTC
  • mto: This revision was merged to the branch mainline in revision 5390.
  • Revision ID: john@arbash-meinel.com-20100824192132-2ktt5adkbk5bk1ct
Handle test_source and extensions. Also define an 'extern' protocol, to allow
the test suite to recognize that returning an object of that type is a Python object.

Show diffs side-by-side

added added

removed removed

Lines of Context:
30
30
import urllib
31
31
import urlparse
32
32
 
33
 
from bzrlib import (
34
 
    osutils,
35
 
    tests,
36
 
    transport,
37
 
    )
 
33
from bzrlib import transport
38
34
from bzrlib.tests import test_server
39
35
from bzrlib.transport import local
40
36
 
75
71
                self.headers.get('referer', '-'),
76
72
                self.headers.get('user-agent', '-'))
77
73
 
78
 
    def handle(self):
79
 
        SimpleHTTPServer.SimpleHTTPRequestHandler.handle(self)
80
 
        # Some client (pycurl, I'm looking at you) are more picky than others
81
 
        # and require that the socket itself is closed
82
 
        # (SocketServer.StreamRequestHandler only close the two associated
83
 
        # 'makefile' objects)
84
 
        self.connection.close()
85
 
 
86
74
    def handle_one_request(self):
87
75
        """Handle a single HTTP request.
88
76
 
90
78
        connection early to avoid polluting the test results.
91
79
        """
92
80
        try:
93
 
            self._handle_one_request()
 
81
            SimpleHTTPServer.SimpleHTTPRequestHandler.handle_one_request(self)
94
82
        except socket.error, e:
95
83
            # Any socket error should close the connection, but some errors are
96
84
            # due to the client closing early and we don't want to pollute test
136
124
        if self.command != 'HEAD' and code >= 200 and code not in (204, 304):
137
125
            self.wfile.write(content)
138
126
 
139
 
    def _handle_one_request(self):
140
 
        SimpleHTTPServer.SimpleHTTPRequestHandler.handle_one_request(self)
141
 
 
142
127
    _range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)$')
143
128
    _tail_regexp = re.compile(r'^-(?P<tail>\d+)$')
144
129
 
263
248
            # mode may cause newline translations, making the
264
249
            # actual size of the content transmitted *less* than
265
250
            # the content-length!
266
 
            f = open(path, 'rb')
 
251
            file = open(path, 'rb')
267
252
        except IOError:
268
253
            self.send_error(404, "File not found")
269
254
            return
270
255
 
271
 
        file_size = os.fstat(f.fileno())[6]
 
256
        file_size = os.fstat(file.fileno())[6]
272
257
        tail, ranges = self.parse_ranges(ranges_header_value)
273
258
        # Normalize tail into ranges
274
259
        if tail != 0:
295
280
            # RFC2616 14.16 and 14.35 says that when a server
296
281
            # encounters unsatisfiable range specifiers, it
297
282
            # SHOULD return a 416.
298
 
            f.close()
 
283
            file.close()
299
284
            # FIXME: We SHOULD send a Content-Range header too,
300
285
            # but the implementation of send_error does not
301
286
            # allows that. So far.
304
289
 
305
290
        if len(ranges) == 1:
306
291
            (start, end) = ranges[0]
307
 
            self.get_single_range(f, file_size, start, end)
 
292
            self.get_single_range(file, file_size, start, end)
308
293
        else:
309
 
            self.get_multiple_ranges(f, file_size, ranges)
310
 
        f.close()
 
294
            self.get_multiple_ranges(file, file_size, ranges)
 
295
        file.close()
311
296
 
312
297
    def translate_path(self, path):
313
298
        """Translate a /-separated PATH to the local filename syntax.
369
354
        self.test_case_server = test_case_server
370
355
        self._home_dir = test_case_server._home_dir
371
356
 
372
 
 
373
 
class TestingHTTPServer(test_server.TestingTCPServer, TestingHTTPServerMixin):
 
357
    def stop_server(self):
 
358
         """Called to clean-up the server.
 
359
 
 
360
         Since the server may be (surely is, even) in a blocking listen, we
 
361
         shutdown its socket before closing it.
 
362
         """
 
363
         # Note that is this executed as part of the implicit tear down in the
 
364
         # main thread while the server runs in its own thread. The clean way
 
365
         # to tear down the server is to instruct him to stop accepting
 
366
         # connections and wait for the current connection(s) to end
 
367
         # naturally. To end the connection naturally, the http transports
 
368
         # should close their socket when they do not need to talk to the
 
369
         # server anymore. This happens naturally during the garbage collection
 
370
         # phase of the test transport objetcs (the server clients), so we
 
371
         # don't have to worry about them.  So, for the server, we must tear
 
372
         # down here, from the main thread, when the test have ended.  Note
 
373
         # that since the server is in a blocking operation and since python
 
374
         # use select internally, shutting down the socket is reliable and
 
375
         # relatively clean.
 
376
         try:
 
377
             self.socket.shutdown(socket.SHUT_RDWR)
 
378
         except socket.error, e:
 
379
             # WSAENOTCONN (10057) 'Socket is not connected' is harmless on
 
380
             # windows (occurs before the first connection attempt
 
381
             # vila--20071230)
 
382
 
 
383
             # 'Socket is not connected' can also occur on OSX, with a
 
384
             # "regular" ENOTCONN (when something went wrong during test case
 
385
             # setup leading to self.setUp() *not* being called but
 
386
             # self.stop_server() still being called -- vila20081106
 
387
             if not len(e.args) or e.args[0] not in (errno.ENOTCONN, 10057):
 
388
                 raise
 
389
         # Let the server properly close the socket
 
390
         self.server_close()
 
391
 
 
392
 
 
393
class TestingHTTPServer(SocketServer.TCPServer, TestingHTTPServerMixin):
374
394
 
375
395
    def __init__(self, server_address, request_handler_class,
376
396
                 test_case_server):
377
 
        test_server.TestingTCPServer.__init__(self, server_address,
378
 
                                              request_handler_class)
379
397
        TestingHTTPServerMixin.__init__(self, test_case_server)
380
 
 
381
 
 
382
 
class TestingThreadingHTTPServer(test_server.TestingThreadingTCPServer,
 
398
        SocketServer.TCPServer.__init__(self, server_address,
 
399
                                        request_handler_class)
 
400
 
 
401
 
 
402
class TestingThreadingHTTPServer(SocketServer.ThreadingTCPServer,
383
403
                                 TestingHTTPServerMixin):
384
404
    """A threading HTTP test server for HTTP 1.1.
385
405
 
387
407
    server, we need an independent connection for each of them. We achieve that
388
408
    by spawning a new thread for each connection.
389
409
    """
 
410
 
390
411
    def __init__(self, server_address, request_handler_class,
391
412
                 test_case_server):
392
 
        test_server.TestingThreadingTCPServer.__init__(self, server_address,
393
 
                                                       request_handler_class)
394
413
        TestingHTTPServerMixin.__init__(self, test_case_server)
395
 
 
396
 
 
397
 
class HttpServer(test_server.TestingTCPServerInAThread):
 
414
        SocketServer.ThreadingTCPServer.__init__(self, server_address,
 
415
                                                 request_handler_class)
 
416
        # Decides how threads will act upon termination of the main
 
417
        # process. This is prophylactic as we should not leave the threads
 
418
        # lying around.
 
419
        self.daemon_threads = True
 
420
 
 
421
    def process_request_thread(self, request, client_address):
 
422
        SocketServer.ThreadingTCPServer.process_request_thread(
 
423
            self, request, client_address)
 
424
        # Under some circumstances (as in bug #383920), we need to force the
 
425
        # shutdown as python delays it until gc occur otherwise and the client
 
426
        # may hang.
 
427
        try:
 
428
            # The request process has been completed, the thread is about to
 
429
            # die, let's shutdown the socket if we can.
 
430
            request.shutdown(socket.SHUT_RDWR)
 
431
        except (socket.error, select.error), e:
 
432
            if e[0] in (errno.EBADF, errno.ENOTCONN):
 
433
                # Right, the socket is already down
 
434
                pass
 
435
            else:
 
436
                raise
 
437
 
 
438
 
 
439
class HttpServer(transport.Server):
398
440
    """A test server for http transports.
399
441
 
400
442
    Subclasses can provide a specific request handler.
422
464
        :param protocol_version: if specified, will override the protocol
423
465
            version of the request handler.
424
466
        """
425
 
        # Depending on the protocol version, we will create the approriate
426
 
        # server
427
 
        if protocol_version is None:
428
 
            # Use the request handler one
429
 
            proto_vers = request_handler.protocol_version
430
 
        else:
431
 
            # Use our own, it will be used to override the request handler
432
 
            # one too.
433
 
            proto_vers = protocol_version
434
 
        # Get the appropriate server class for the required protocol
435
 
        serv_cls = self.http_server_class.get(proto_vers, None)
436
 
        if serv_cls is None:
437
 
            raise httplib.UnknownProtocol(proto_vers)
 
467
        transport.Server.__init__(self)
 
468
        self.request_handler = request_handler
438
469
        self.host = 'localhost'
439
470
        self.port = 0
440
 
        super(HttpServer, self).__init__((self.host, self.port),
441
 
                                         serv_cls,
442
 
                                         request_handler)
443
 
        self.protocol_version = proto_vers
 
471
        self._httpd = None
 
472
        self.protocol_version = protocol_version
444
473
        # Allows tests to verify number of GET requests issued
445
474
        self.GET_request_nb = 0
446
 
        self._http_base_url = None
447
 
        self.logs = []
448
 
 
449
 
    def create_server(self):
450
 
        return self.server_class(
451
 
            (self.host, self.port), self.request_handler_class, self)
 
475
 
 
476
    def create_httpd(self, serv_cls, rhandler_cls):
 
477
        return serv_cls((self.host, self.port), self.request_handler, self)
 
478
 
 
479
    def __repr__(self):
 
480
        return "%s(%s:%s)" % \
 
481
            (self.__class__.__name__, self.host, self.port)
 
482
 
 
483
    def _get_httpd(self):
 
484
        if self._httpd is None:
 
485
            rhandler = self.request_handler
 
486
            # Depending on the protocol version, we will create the approriate
 
487
            # server
 
488
            if self.protocol_version is None:
 
489
                # Use the request handler one
 
490
                proto_vers = rhandler.protocol_version
 
491
            else:
 
492
                # Use our own, it will be used to override the request handler
 
493
                # one too.
 
494
                proto_vers = self.protocol_version
 
495
            # Create the appropriate server for the required protocol
 
496
            serv_cls = self.http_server_class.get(proto_vers, None)
 
497
            if serv_cls is None:
 
498
                raise httplib.UnknownProtocol(proto_vers)
 
499
            else:
 
500
                self._httpd = self.create_httpd(serv_cls, rhandler)
 
501
            self.host, self.port = self._httpd.socket.getsockname()
 
502
        return self._httpd
 
503
 
 
504
    def _http_start(self):
 
505
        """Server thread main entry point. """
 
506
        self._http_running = False
 
507
        try:
 
508
            try:
 
509
                httpd = self._get_httpd()
 
510
                self._http_base_url = '%s://%s:%s/' % (self._url_protocol,
 
511
                                                       self.host, self.port)
 
512
                self._http_running = True
 
513
            except:
 
514
                # Whatever goes wrong, we save the exception for the main
 
515
                # thread. Note that since we are running in a thread, no signal
 
516
                # can be received, so we don't care about KeyboardInterrupt.
 
517
                self._http_exception = sys.exc_info()
 
518
        finally:
 
519
            # Release the lock or the main thread will block and the whole
 
520
            # process will hang.
 
521
            self._http_starting.release()
 
522
 
 
523
        # From now on, exceptions are taken care of by the
 
524
        # SocketServer.BaseServer or the request handler.
 
525
        while self._http_running:
 
526
            try:
 
527
                # Really an HTTP connection but the python framework is generic
 
528
                # and call them requests
 
529
                httpd.handle_request()
 
530
            except socket.timeout:
 
531
                pass
 
532
            except (socket.error, select.error), e:
 
533
                if (e[0] == errno.EBADF
 
534
                    or (sys.platform == 'win32' and e[0] == 10038)):
 
535
                    # Starting with python-2.6, handle_request may raise socket
 
536
                    # or select exceptions when the server is shut down (as we
 
537
                    # do).
 
538
                    # 10038 = WSAENOTSOCK
 
539
                    # http://msdn.microsoft.com/en-us/library/ms740668%28VS.85%29.aspx
 
540
                    pass
 
541
                else:
 
542
                    raise
452
543
 
453
544
    def _get_remote_url(self, path):
454
545
        path_parts = path.split(os.path.sep)
479
570
                or isinstance(backing_transport_server,
480
571
                              test_server.LocalURLServer)):
481
572
            raise AssertionError(
482
 
                "HTTPServer currently assumes local transport, got %s" %
 
573
                "HTTPServer currently assumes local transport, got %s" % \
483
574
                backing_transport_server)
484
575
        self._home_dir = os.getcwdu()
485
576
        self._local_path_parts = self._home_dir.split(os.path.sep)
 
577
        self._http_base_url = None
 
578
 
 
579
        # Create the server thread
 
580
        self._http_starting = threading.Lock()
 
581
        self._http_starting.acquire()
 
582
        self._http_thread = threading.Thread(target=self._http_start)
 
583
        self._http_thread.setDaemon(True)
 
584
        self._http_exception = None
 
585
        self._http_thread.start()
 
586
 
 
587
        # Wait for the server thread to start (i.e release the lock)
 
588
        self._http_starting.acquire()
 
589
 
 
590
        if self._http_exception is not None:
 
591
            # Something went wrong during server start
 
592
            exc_class, exc_value, exc_tb = self._http_exception
 
593
            raise exc_class, exc_value, exc_tb
 
594
        self._http_starting.release()
486
595
        self.logs = []
487
596
 
488
 
        super(HttpServer, self).start_server()
489
 
        self._http_base_url = '%s://%s:%s/' % (
490
 
            self._url_protocol, self.host, self.port)
 
597
    def stop_server(self):
 
598
        self._httpd.stop_server()
 
599
        self._http_running = False
 
600
        # We don't need to 'self._http_thread.join()' here since the thread is
 
601
        # a daemonic one and will be garbage collected anyway. Joining just
 
602
        # slows us down for no added benefit.
491
603
 
492
604
    def get_url(self):
493
605
        """See bzrlib.transport.Server.get_url."""