~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/http_server.py

  • Committer: Andrew Bennetts
  • Date: 2010-07-29 11:17:57 UTC
  • mfrom: (5050.3.17 2.2)
  • mto: This revision was merged to the branch mainline in revision 5365.
  • Revision ID: andrew.bennetts@canonical.com-20100729111757-018h3pcefo7z0dnq
Merge lp:bzr/2.2 into lp:bzr.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 Canonical Ltd
 
1
# Copyright (C) 2006-2010 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
20
20
import posixpath
21
21
import random
22
22
import re
 
23
import select
23
24
import SimpleHTTPServer
24
25
import socket
 
26
import SocketServer
 
27
import sys
 
28
import threading
 
29
import time
25
30
import urllib
26
31
import urlparse
27
32
 
 
33
from bzrlib import transport
28
34
from bzrlib.tests import test_server
 
35
from bzrlib.transport import local
29
36
 
30
37
 
31
38
class BadWebserverPath(ValueError):
64
71
                self.headers.get('referer', '-'),
65
72
                self.headers.get('user-agent', '-'))
66
73
 
67
 
    def handle(self):
68
 
        SimpleHTTPServer.SimpleHTTPRequestHandler.handle(self)
69
 
        # Some client (pycurl, I'm looking at you) are more picky than others
70
 
        # and require that the socket itself is closed
71
 
        # (SocketServer.StreamRequestHandler only close the two associated
72
 
        # 'makefile' objects)
73
 
        self.connection.close()
74
 
 
75
74
    def handle_one_request(self):
76
75
        """Handle a single HTTP request.
77
76
 
79
78
        connection early to avoid polluting the test results.
80
79
        """
81
80
        try:
82
 
            self._handle_one_request()
 
81
            SimpleHTTPServer.SimpleHTTPRequestHandler.handle_one_request(self)
83
82
        except socket.error, e:
84
83
            # Any socket error should close the connection, but some errors are
85
84
            # due to the client closing early and we don't want to pollute test
125
124
        if self.command != 'HEAD' and code >= 200 and code not in (204, 304):
126
125
            self.wfile.write(content)
127
126
 
128
 
    def _handle_one_request(self):
129
 
        SimpleHTTPServer.SimpleHTTPRequestHandler.handle_one_request(self)
130
 
 
131
 
    _range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)?$')
 
127
    _range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)$')
132
128
    _tail_regexp = re.compile(r'^-(?P<tail>\d+)$')
133
129
 
134
 
    def _parse_ranges(self, ranges_header, file_size):
135
 
        """Parse the range header value and returns ranges.
136
 
 
137
 
        RFC2616 14.35 says that syntactically invalid range specifiers MUST be
138
 
        ignored. In that case, we return None instead of a range list.
139
 
 
140
 
        :param ranges_header: The 'Range' header value.
141
 
 
142
 
        :param file_size: The size of the requested file.
143
 
 
144
 
        :return: A list of (start, end) tuples or None if some invalid range
145
 
            specifier is encountered.
 
130
    def parse_ranges(self, ranges_header):
 
131
        """Parse the range header value and returns ranges and tail.
 
132
 
 
133
        RFC2616 14.35 says that syntactically invalid range
 
134
        specifiers MUST be ignored. In that case, we return 0 for
 
135
        tail and [] for ranges.
146
136
        """
 
137
        tail = 0
 
138
        ranges = []
147
139
        if not ranges_header.startswith('bytes='):
148
140
            # Syntactically invalid header
149
 
            return None
 
141
            return 0, []
150
142
 
151
 
        tail = None
152
 
        ranges = []
153
143
        ranges_header = ranges_header[len('bytes='):]
154
144
        for range_str in ranges_header.split(','):
 
145
            # FIXME: RFC2616 says end is optional and default to file_size
155
146
            range_match = self._range_regexp.match(range_str)
156
147
            if range_match is not None:
157
148
                start = int(range_match.group('start'))
158
 
                end_match = range_match.group('end')
159
 
                if end_match is None:
160
 
                    # RFC2616 says end is optional and default to file_size
161
 
                    end = file_size
162
 
                else:
163
 
                    end = int(end_match)
 
149
                end = int(range_match.group('end'))
164
150
                if start > end:
165
151
                    # Syntactically invalid range
166
 
                    return None
 
152
                    return 0, []
167
153
                ranges.append((start, end))
168
154
            else:
169
155
                tail_match = self._tail_regexp.match(range_str)
171
157
                    tail = int(tail_match.group('tail'))
172
158
                else:
173
159
                    # Syntactically invalid range
174
 
                    return None
175
 
        if tail is not None:
176
 
            # Normalize tail into ranges
177
 
            ranges.append((max(0, file_size - tail), file_size))
178
 
 
179
 
        checked_ranges = []
180
 
        for start, end in ranges:
181
 
            if start >= file_size:
182
 
                # RFC2616 14.35, ranges are invalid if start >= file_size
183
 
                return None
184
 
            # RFC2616 14.35, end values should be truncated
185
 
            # to file_size -1 if they exceed it
186
 
            end = min(end, file_size - 1)
187
 
            checked_ranges.append((start, end))
188
 
        return checked_ranges
 
160
                    return 0, []
 
161
        return tail, ranges
189
162
 
190
163
    def _header_line_length(self, keyword, value):
191
164
        header_line = '%s: %s\r\n' % (keyword, value)
275
248
            # mode may cause newline translations, making the
276
249
            # actual size of the content transmitted *less* than
277
250
            # the content-length!
278
 
            f = open(path, 'rb')
 
251
            file = open(path, 'rb')
279
252
        except IOError:
280
253
            self.send_error(404, "File not found")
281
254
            return
282
255
 
283
 
        file_size = os.fstat(f.fileno())[6]
284
 
        ranges = self._parse_ranges(ranges_header_value, file_size)
285
 
        if not ranges:
 
256
        file_size = os.fstat(file.fileno())[6]
 
257
        tail, ranges = self.parse_ranges(ranges_header_value)
 
258
        # Normalize tail into ranges
 
259
        if tail != 0:
 
260
            ranges.append((file_size - tail, file_size))
 
261
 
 
262
        self._satisfiable_ranges = True
 
263
        if len(ranges) == 0:
 
264
            self._satisfiable_ranges = False
 
265
        else:
 
266
            def check_range(range_specifier):
 
267
                start, end = range_specifier
 
268
                # RFC2616 14.35, ranges are invalid if start >= file_size
 
269
                if start >= file_size:
 
270
                    self._satisfiable_ranges = False # Side-effect !
 
271
                    return 0, 0
 
272
                # RFC2616 14.35, end values should be truncated
 
273
                # to file_size -1 if they exceed it
 
274
                end = min(end, file_size - 1)
 
275
                return start, end
 
276
 
 
277
            ranges = map(check_range, ranges)
 
278
 
 
279
        if not self._satisfiable_ranges:
286
280
            # RFC2616 14.16 and 14.35 says that when a server
287
281
            # encounters unsatisfiable range specifiers, it
288
282
            # SHOULD return a 416.
289
 
            f.close()
 
283
            file.close()
290
284
            # FIXME: We SHOULD send a Content-Range header too,
291
285
            # but the implementation of send_error does not
292
286
            # allows that. So far.
295
289
 
296
290
        if len(ranges) == 1:
297
291
            (start, end) = ranges[0]
298
 
            self.get_single_range(f, file_size, start, end)
 
292
            self.get_single_range(file, file_size, start, end)
299
293
        else:
300
 
            self.get_multiple_ranges(f, file_size, ranges)
301
 
        f.close()
 
294
            self.get_multiple_ranges(file, file_size, ranges)
 
295
        file.close()
302
296
 
303
297
    def translate_path(self, path):
304
298
        """Translate a /-separated PATH to the local filename syntax.
360
354
        self.test_case_server = test_case_server
361
355
        self._home_dir = test_case_server._home_dir
362
356
 
363
 
 
364
 
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):
365
394
 
366
395
    def __init__(self, server_address, request_handler_class,
367
396
                 test_case_server):
368
 
        test_server.TestingTCPServer.__init__(self, server_address,
369
 
                                              request_handler_class)
370
397
        TestingHTTPServerMixin.__init__(self, test_case_server)
371
 
 
372
 
 
373
 
class TestingThreadingHTTPServer(test_server.TestingThreadingTCPServer,
 
398
        SocketServer.TCPServer.__init__(self, server_address,
 
399
                                        request_handler_class)
 
400
 
 
401
 
 
402
class TestingThreadingHTTPServer(SocketServer.ThreadingTCPServer,
374
403
                                 TestingHTTPServerMixin):
375
404
    """A threading HTTP test server for HTTP 1.1.
376
405
 
378
407
    server, we need an independent connection for each of them. We achieve that
379
408
    by spawning a new thread for each connection.
380
409
    """
 
410
 
381
411
    def __init__(self, server_address, request_handler_class,
382
412
                 test_case_server):
383
 
        test_server.TestingThreadingTCPServer.__init__(self, server_address,
384
 
                                                       request_handler_class)
385
413
        TestingHTTPServerMixin.__init__(self, test_case_server)
386
 
 
387
 
 
388
 
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):
389
440
    """A test server for http transports.
390
441
 
391
442
    Subclasses can provide a specific request handler.
413
464
        :param protocol_version: if specified, will override the protocol
414
465
            version of the request handler.
415
466
        """
416
 
        # Depending on the protocol version, we will create the approriate
417
 
        # server
418
 
        if protocol_version is None:
419
 
            # Use the request handler one
420
 
            proto_vers = request_handler.protocol_version
421
 
        else:
422
 
            # Use our own, it will be used to override the request handler
423
 
            # one too.
424
 
            proto_vers = protocol_version
425
 
        # Get the appropriate server class for the required protocol
426
 
        serv_cls = self.http_server_class.get(proto_vers, None)
427
 
        if serv_cls is None:
428
 
            raise httplib.UnknownProtocol(proto_vers)
 
467
        transport.Server.__init__(self)
 
468
        self.request_handler = request_handler
429
469
        self.host = 'localhost'
430
470
        self.port = 0
431
 
        super(HttpServer, self).__init__((self.host, self.port),
432
 
                                         serv_cls,
433
 
                                         request_handler)
434
 
        self.protocol_version = proto_vers
 
471
        self._httpd = None
 
472
        self.protocol_version = protocol_version
435
473
        # Allows tests to verify number of GET requests issued
436
474
        self.GET_request_nb = 0
437
 
        self._http_base_url = None
438
 
        self.logs = []
439
 
 
440
 
    def create_server(self):
441
 
        return self.server_class(
442
 
            (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
443
543
 
444
544
    def _get_remote_url(self, path):
445
545
        path_parts = path.split(os.path.sep)
470
570
                or isinstance(backing_transport_server,
471
571
                              test_server.LocalURLServer)):
472
572
            raise AssertionError(
473
 
                "HTTPServer currently assumes local transport, got %s" %
 
573
                "HTTPServer currently assumes local transport, got %s" % \
474
574
                backing_transport_server)
475
575
        self._home_dir = os.getcwdu()
476
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()
477
595
        self.logs = []
478
596
 
479
 
        super(HttpServer, self).start_server()
480
 
        self._http_base_url = '%s://%s:%s/' % (
481
 
            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.
482
603
 
483
604
    def get_url(self):
484
605
        """See bzrlib.transport.Server.get_url."""