41
42
return 'path %s is not in %s' % self.args
44
class TestingHTTPRequestHandler(SimpleHTTPRequestHandler):
45
class TestingHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
45
46
"""Handles one request.
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.
53
# Default protocol version
54
protocol_version = 'HTTP/1.1'
56
# The Message-like class used to parse the request headers
57
MessageClass = httplib.HTTPMessage
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
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.
67
SimpleHTTPRequestHandler.handle_one_request(self)
82
SimpleHTTPServer.SimpleHTTPRequestHandler.handle_one_request(self)
68
83
except socket.error, e:
70
and e.args[0] in (errno.EPIPE, errno.ECONNRESET,
71
errno.ECONNABORTED,)):
72
self.close_connection = 1
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
89
or e.args[0] not in (errno.EPIPE, errno.ECONNRESET,
90
errno.ECONNABORTED, errno.EBADF)):
77
93
_range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)$')
111
127
return tail, ranges
129
def _header_line_length(self, keyword, value):
130
header_line = '%s: %s\r\n' % (keyword, value)
131
return len(header_line)
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
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')
147
return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
113
149
def send_range_content(self, file, start, length):
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
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()
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,
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)
196
self.wfile.write(boundary_line)
147
198
def do_GET(self):
148
199
"""Serve a GET request.
150
201
Handles the Range header.
204
self.server.test_case_server.GET_request_nb += 1
153
206
path = self.translate_path(self.path)
154
207
ranges_header_value = self.headers.get('Range')
155
208
if ranges_header_value is None or os.path.isdir(path):
156
209
# Let the mother class handle most cases
157
return SimpleHTTPRequestHandler.do_GET(self)
210
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
160
213
# Always read in binary mode. Opening files in text
265
class TestingHTTPServer(BaseHTTPServer.HTTPServer):
319
class TestingHTTPServerMixin:
267
def __init__(self, server_address, RequestHandlerClass,
269
BaseHTTPServer.HTTPServer.__init__(self, server_address,
321
def __init__(self, test_case_server):
271
322
# test_case_server can be used to communicate between the
272
323
# tests and the server (or the request handler and the
273
324
# server), allowing dynamic behaviors to be defined from
274
325
# the tests cases.
275
326
self.test_case_server = test_case_server
277
def server_close(self):
278
"""Called to clean-up the server.
280
Since the server may be in a blocking read, we shutdown the socket
283
self.socket.shutdown(socket.SHUT_RDWR)
284
BaseHTTPServer.HTTPServer.server_close(self)
287
class HttpServer(Server):
329
"""Called to clean-up the server.
331
Since the server may be (surely is, even) in a blocking listen, we
332
shutdown its socket before closing it.
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
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
353
if not len(e.args) or e.args[0] != 10057:
355
# Let the server properly close the socket
358
class TestingHTTPServer(SocketServer.TCPServer, TestingHTTPServerMixin):
360
def __init__(self, server_address, request_handler_class,
362
TestingHTTPServerMixin.__init__(self, test_case_server)
363
SocketServer.TCPServer.__init__(self, server_address,
364
request_handler_class)
367
class TestingThreadingHTTPServer(SocketServer.ThreadingTCPServer,
368
TestingHTTPServerMixin):
369
"""A threading HTTP test server for HTTP 1.1.
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.
376
def __init__(self, server_address, request_handler_class,
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
384
self.daemon_threads = True
387
class HttpServer(transport.Server):
288
388
"""A test server for http transports.
290
390
Subclasses can provide a specific request handler.
393
# The real servers depending on the protocol
394
http_server_class = {'HTTP/1.0': TestingHTTPServer,
395
'HTTP/1.1': TestingThreadingHTTPServer,
293
398
# Whether or not we proxy the requests (see
294
399
# TestingHTTPRequestHandler.translate_path).
295
400
proxy_requests = False
297
402
# used to form the url that connects to this server
298
403
_url_protocol = 'http'
300
# Subclasses can provide a specific request handler
301
def __init__(self, request_handler=TestingHTTPRequestHandler):
302
Server.__init__(self)
405
def __init__(self, request_handler=TestingHTTPRequestHandler,
406
protocol_version=None):
409
:param request_handler: a class that will be instantiated to handle an
410
http connection (one or several requests).
412
:param protocol_version: if specified, will override the protocol
413
version of the request handler.
415
transport.Server.__init__(self)
303
416
self.request_handler = request_handler
304
417
self.host = 'localhost'
306
419
self._httpd = None
420
self.protocol_version = protocol_version
421
# Allows tests to verify number of GET requests issued
422
self.GET_request_nb = 0
308
424
def _get_httpd(self):
309
425
if self._httpd is None:
310
self._httpd = TestingHTTPServer((self.host, self.port),
311
self.request_handler,
426
rhandler = self.request_handler
427
# Depending on the protocol version, we will create the approriate
429
if self.protocol_version is None:
430
# Use the request handler one
431
proto_vers = rhandler.protocol_version
433
# Use our own, it will be used to override the request handler
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)
439
raise httplib.UnknownProtocol(proto_vers)
441
self._httpd = serv_cls((self.host, self.port), rhandler, self)
313
442
host, self.port = self._httpd.socket.getsockname()
314
443
return self._httpd
316
445
def _http_start(self):
317
httpd = self._get_httpd()
318
self._http_base_url = '%s://%s:%s/' % (self._url_protocol,
321
self._http_starting.release()
446
"""Server thread main entry point. """
447
self._http_running = False
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
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()
460
# Release the lock or the main thread will block and the whole
462
self._http_starting.release()
464
# From now on, exceptions are taken care of by the
465
# SocketServer.BaseServer or the request handler.
323
466
while self._http_running:
468
# Really an HTTP connection but the python framework is generic
469
# and call them requests
325
470
httpd.handle_request()
326
471
except socket.timeout:
352
497
# XXX: TODO: make the server back onto vfs_server rather than local
354
499
assert backing_transport_server is None or \
355
isinstance(backing_transport_server, LocalURLServer), \
500
isinstance(backing_transport_server, local.LocalURLServer), \
356
501
"HTTPServer currently assumes local transport, got %s" % \
357
502
backing_transport_server
358
503
self._home_dir = os.getcwdu()
359
504
self._local_path_parts = self._home_dir.split(os.path.sep)
505
self._http_base_url = None
507
# Create the server thread
360
508
self._http_starting = threading.Lock()
361
509
self._http_starting.acquire()
362
self._http_running = True
363
self._http_base_url = None
364
510
self._http_thread = threading.Thread(target=self._http_start)
365
511
self._http_thread.setDaemon(True)
512
self._http_exception = None
366
513
self._http_thread.start()
367
515
# Wait for the server thread to start (i.e release the lock)
368
516
self._http_starting.acquire()
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
369
522
self._http_starting.release()
372
525
def tearDown(self):
373
526
"""See bzrlib.transport.Server.tearDown."""
374
self._httpd.server_close()
527
self._httpd.tearDown()
375
528
self._http_running = False
376
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.
378
533
def get_url(self):
379
534
"""See bzrlib.transport.Server.get_url."""