125
124
if self.command != 'HEAD' and code >= 200 and code not in (204, 304):
126
125
self.wfile.write(content)
128
def _handle_one_request(self):
129
SimpleHTTPServer.SimpleHTTPRequestHandler.handle_one_request(self)
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+)$')
134
def _parse_ranges(self, ranges_header, file_size):
135
"""Parse the range header value and returns ranges.
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.
140
:param ranges_header: The 'Range' header value.
142
:param file_size: The size of the requested file.
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.
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.
147
139
if not ranges_header.startswith('bytes='):
148
140
# Syntactically invalid header
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
149
end = int(range_match.group('end'))
165
151
# Syntactically invalid range
167
153
ranges.append((start, end))
169
155
tail_match = self._tail_regexp.match(range_str)
171
157
tail = int(tail_match.group('tail'))
173
159
# Syntactically invalid range
176
# Normalize tail into ranges
177
ranges.append((max(0, file_size - tail), file_size))
180
for start, end in ranges:
181
if start >= file_size:
182
# RFC2616 14.35, ranges are invalid if start >= file_size
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
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!
251
file = open(path, 'rb')
280
253
self.send_error(404, "File not found")
283
file_size = os.fstat(f.fileno())[6]
284
ranges = self._parse_ranges(ranges_header_value, file_size)
256
file_size = os.fstat(file.fileno())[6]
257
tail, ranges = self.parse_ranges(ranges_header_value)
258
# Normalize tail into ranges
260
ranges.append((file_size - tail, file_size))
262
self._satisfiable_ranges = True
264
self._satisfiable_ranges = False
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 !
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)
277
ranges = map(check_range, ranges)
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.
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.
360
354
self.test_case_server = test_case_server
361
355
self._home_dir = test_case_server._home_dir
364
class TestingHTTPServer(test_server.TestingTCPServer, TestingHTTPServerMixin):
357
def stop_server(self):
358
"""Called to clean-up the server.
360
Since the server may be (surely is, even) in a blocking listen, we
361
shutdown its socket before closing it.
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
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
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):
389
# Let the server properly close the socket
393
class TestingHTTPServer(SocketServer.TCPServer, TestingHTTPServerMixin):
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)
373
class TestingThreadingHTTPServer(test_server.TestingThreadingTCPServer,
398
SocketServer.TCPServer.__init__(self, server_address,
399
request_handler_class)
402
class TestingThreadingHTTPServer(SocketServer.ThreadingTCPServer,
374
403
TestingHTTPServerMixin):
375
404
"""A threading HTTP test server for HTTP 1.1.
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.
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)
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
419
self.daemon_threads = True
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
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
439
class HttpServer(transport.Server):
389
440
"""A test server for http transports.
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.
416
# Depending on the protocol version, we will create the approriate
418
if protocol_version is None:
419
# Use the request handler one
420
proto_vers = request_handler.protocol_version
422
# Use our own, it will be used to override the request handler
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)
428
raise httplib.UnknownProtocol(proto_vers)
467
transport.Server.__init__(self)
468
self.request_handler = request_handler
429
469
self.host = 'localhost'
431
super(HttpServer, self).__init__((self.host, self.port),
434
self.protocol_version = proto_vers
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
440
def create_server(self):
441
return self.server_class(
442
(self.host, self.port), self.request_handler_class, self)
476
def create_httpd(self, serv_cls, rhandler_cls):
477
return serv_cls((self.host, self.port), self.request_handler, self)
480
return "%s(%s:%s)" % \
481
(self.__class__.__name__, self.host, self.port)
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
488
if self.protocol_version is None:
489
# Use the request handler one
490
proto_vers = rhandler.protocol_version
492
# Use our own, it will be used to override the request handler
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)
498
raise httplib.UnknownProtocol(proto_vers)
500
self._httpd = self.create_httpd(serv_cls, rhandler)
501
self.host, self.port = self._httpd.socket.getsockname()
504
def _http_start(self):
505
"""Server thread main entry point. """
506
self._http_running = False
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
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()
519
# Release the lock or the main thread will block and the whole
521
self._http_starting.release()
523
# From now on, exceptions are taken care of by the
524
# SocketServer.BaseServer or the request handler.
525
while self._http_running:
527
# Really an HTTP connection but the python framework is generic
528
# and call them requests
529
httpd.handle_request()
530
except socket.timeout:
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
538
# 10038 = WSAENOTSOCK
539
# http://msdn.microsoft.com/en-us/library/ms740668%28VS.85%29.aspx
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
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()
587
# Wait for the server thread to start (i.e release the lock)
588
self._http_starting.acquire()
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()
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.
483
604
def get_url(self):
484
605
"""See bzrlib.transport.Server.get_url."""