33
41
return 'path %s is not in %s' % self.args
36
class TestingHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
44
class TestingHTTPRequestHandler(SimpleHTTPRequestHandler):
37
45
"""Handles one request.
39
A TestingHTTPRequestHandler is instantiated for every request received by
40
the associated server. Note that 'request' here is inherited from the base
41
TCPServer class, for the HTTP server it is really a connection which itself
42
will handle one or several HTTP requests.
47
A TestingHTTPRequestHandler is instantiated for every request
48
received by the associated server.
44
# Default protocol version
45
protocol_version = 'HTTP/1.1'
47
# The Message-like class used to parse the request headers
48
MessageClass = httplib.HTTPMessage
51
SimpleHTTPServer.SimpleHTTPRequestHandler.setup(self)
52
self._cwd = self.server._home_dir
53
tcs = self.server.test_case_server
54
if tcs.protocol_version is not None:
55
# If the test server forced a protocol version, use it
56
self.protocol_version = tcs.protocol_version
58
51
def log_message(self, format, *args):
59
52
tcs = self.server.test_case_server
79
64
connection early to avoid polluting the test results.
82
self._handle_one_request()
67
SimpleHTTPRequestHandler.handle_one_request(self)
83
68
except socket.error, e:
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)):
70
and e.args[0] in (errno.EPIPE, errno.ECONNRESET,
71
errno.ECONNABORTED,)):
72
self.close_connection = 1
93
error_content_type = 'text/plain'
94
error_message_format = '''\
99
def send_error(self, code, message=None):
100
"""Send and log an error reply.
102
We redefine the python-provided version to be able to set a
103
``Content-Length`` header as some http/1.1 clients complain otherwise
106
:param code: The HTTP error code.
108
:param message: The explanation of the error code, Defaults to a short
114
message = self.responses[code][0]
117
self.log_error("code %d, message %s", code, message)
118
content = (self.error_message_format %
119
{'code': code, 'message': message})
120
self.send_response(code, message)
121
self.send_header("Content-Type", self.error_content_type)
122
self.send_header("Content-Length", "%d" % len(content))
123
self.send_header('Connection', 'close')
125
if self.command != 'HEAD' and code >= 200 and code not in (204, 304):
126
self.wfile.write(content)
128
def _handle_one_request(self):
129
SimpleHTTPServer.SimpleHTTPRequestHandler.handle_one_request(self)
131
77
_range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)$')
132
78
_tail_regexp = re.compile(r'^-(?P<tail>\d+)$')
165
111
return tail, ranges
167
def _header_line_length(self, keyword, value):
168
header_line = '%s: %s\r\n' % (keyword, value)
169
return len(header_line)
172
"""Overrides base implementation to work around a bug in python2.5."""
173
path = self.translate_path(self.path)
174
if os.path.isdir(path) and not self.path.endswith('/'):
175
# redirect browser - doing basically what apache does when
176
# DirectorySlash option is On which is quite common (braindead, but
178
self.send_response(301)
179
self.send_header("Location", self.path + "/")
180
# Indicates that the body is empty for HTTP/1.1 clients
181
self.send_header('Content-Length', '0')
185
return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
187
113
def send_range_content(self, file, start, length):
189
115
self.wfile.write(file.read(length))
204
130
def get_multiple_ranges(self, file, file_size, ranges):
205
131
self.send_response(206)
206
132
self.send_header('Accept-Ranges', 'bytes')
207
boundary = '%d' % random.randint(0,0x7FFFFFFF)
208
self.send_header('Content-Type',
209
'multipart/byteranges; boundary=%s' % boundary)
210
boundary_line = '--%s\r\n' % boundary
211
# Calculate the Content-Length
213
for (start, end) in ranges:
214
content_length += len(boundary_line)
215
content_length += self._header_line_length(
216
'Content-type', 'application/octet-stream')
217
content_length += self._header_line_length(
218
'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
219
content_length += len('\r\n') # end headers
220
content_length += end - start + 1
221
content_length += len(boundary_line)
222
self.send_header('Content-length', content_length)
133
boundary = "%d" % random.randint(0,0x7FFFFFFF)
134
self.send_header("Content-Type",
135
"multipart/byteranges; boundary=%s" % boundary)
223
136
self.end_headers()
225
# Send the multipart body
226
137
for (start, end) in ranges:
227
self.wfile.write(boundary_line)
228
self.send_header('Content-type', 'application/octet-stream')
229
self.send_header('Content-Range', 'bytes %d-%d/%d'
230
% (start, end, file_size))
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,
231
143
self.end_headers()
232
144
self.send_range_content(file, start, end - start + 1)
234
self.wfile.write(boundary_line)
146
self.wfile.write("--%s\r\n" % boundary)
236
148
def do_GET(self):
237
149
"""Serve a GET request.
322
234
return self._translate_path(path)
324
236
def _translate_path(self, path):
325
"""Translate a /-separated PATH to the local filename syntax.
327
Note that we're translating http URLs here, not file URLs.
328
The URL root location is the server's startup directory.
329
Components that mean special things to the local file system
330
(e.g. drive or directory names) are ignored. (XXX They should
331
probably be diagnosed.)
333
Override from python standard library to stop it calling os.getcwd()
335
# abandon query parameters
336
path = urlparse.urlparse(path)[2]
337
path = posixpath.normpath(urllib.unquote(path))
338
path = path.decode('utf-8')
339
words = path.split('/')
340
words = filter(None, words)
342
for num, word in enumerate(words):
237
return SimpleHTTPRequestHandler.translate_path(self, path)
239
if sys.platform == 'win32':
240
# On win32 you cannot access non-ascii filenames without
241
# decoding them into unicode first.
242
# However, under Linux, you can access bytestream paths
243
# without any problems. If this function was always active
244
# it would probably break tests when LANG=C was set
245
def _translate_path(self, path):
246
"""Translate a /-separated PATH to the local filename syntax.
248
For bzr, all url paths are considered to be utf8 paths.
249
On Linux, you can access these paths directly over the bytestream
250
request, but on win32, you must decode them, and access them
253
# abandon query parameters
254
path = urlparse.urlparse(path)[2]
255
path = posixpath.normpath(urllib.unquote(path))
256
path = path.decode('utf-8')
257
words = path.split('/')
258
words = filter(None, words)
344
261
drive, word = os.path.splitdrive(word)
345
head, word = os.path.split(word)
346
if word in (os.curdir, os.pardir): continue
347
path = os.path.join(path, word)
351
class TestingHTTPServerMixin:
353
def __init__(self, test_case_server):
262
head, word = os.path.split(word)
263
if word in (os.curdir, os.pardir): continue
264
path = os.path.join(path, word)
268
class TestingHTTPServer(BaseHTTPServer.HTTPServer):
270
def __init__(self, server_address, RequestHandlerClass,
272
BaseHTTPServer.HTTPServer.__init__(self, server_address,
354
274
# test_case_server can be used to communicate between the
355
275
# tests and the server (or the request handler and the
356
276
# server), allowing dynamic behaviors to be defined from
357
277
# the tests cases.
358
278
self.test_case_server = test_case_server
359
self._home_dir = test_case_server._home_dir
362
class TestingHTTPServer(test_server.TestingTCPServer, TestingHTTPServerMixin):
364
def __init__(self, server_address, request_handler_class,
366
test_server.TestingTCPServer.__init__(self, server_address,
367
request_handler_class)
368
TestingHTTPServerMixin.__init__(self, test_case_server)
371
class TestingThreadingHTTPServer(test_server.TestingThreadingTCPServer,
372
TestingHTTPServerMixin):
373
"""A threading HTTP test server for HTTP 1.1.
375
Since tests can initiate several concurrent connections to the same http
376
server, we need an independent connection for each of them. We achieve that
377
by spawning a new thread for each connection.
379
def __init__(self, server_address, request_handler_class,
381
test_server.TestingThreadingTCPServer.__init__(self, server_address,
382
request_handler_class)
383
TestingHTTPServerMixin.__init__(self, test_case_server)
386
class HttpServer(test_server.TestingTCPServerInAThread):
280
def server_close(self):
281
"""Called to clean-up the server.
283
Since the server may be in a blocking read, we shutdown the socket
286
self.socket.shutdown(socket.SHUT_RDWR)
287
BaseHTTPServer.HTTPServer.server_close(self)
290
class HttpServer(Server):
387
291
"""A test server for http transports.
389
293
Subclasses can provide a specific request handler.
392
# The real servers depending on the protocol
393
http_server_class = {'HTTP/1.0': TestingHTTPServer,
394
'HTTP/1.1': TestingThreadingHTTPServer,
397
296
# Whether or not we proxy the requests (see
398
297
# TestingHTTPRequestHandler.translate_path).
399
298
proxy_requests = False
401
300
# used to form the url that connects to this server
402
301
_url_protocol = 'http'
404
def __init__(self, request_handler=TestingHTTPRequestHandler,
405
protocol_version=None):
408
:param request_handler: a class that will be instantiated to handle an
409
http connection (one or several requests).
411
:param protocol_version: if specified, will override the protocol
412
version of the request handler.
414
# Depending on the protocol version, we will create the approriate
416
if protocol_version is None:
417
# Use the request handler one
418
proto_vers = request_handler.protocol_version
420
# Use our own, it will be used to override the request handler
422
proto_vers = protocol_version
423
# Get the appropriate server class for the required protocol
424
serv_cls = self.http_server_class.get(proto_vers, None)
426
raise httplib.UnknownProtocol(proto_vers)
303
# Subclasses can provide a specific request handler
304
def __init__(self, request_handler=TestingHTTPRequestHandler):
305
Server.__init__(self)
306
self.request_handler = request_handler
427
307
self.host = 'localhost'
429
super(HttpServer, self).__init__((self.host, self.port),
432
self.protocol_version = proto_vers
433
310
# Allows tests to verify number of GET requests issued
434
311
self.GET_request_nb = 0
435
self._http_base_url = None
438
def create_server(self):
439
return self.server_class(
440
(self.host, self.port), self.request_handler_class, self)
313
def _get_httpd(self):
314
if self._httpd is None:
315
self._httpd = TestingHTTPServer((self.host, self.port),
316
self.request_handler,
318
host, self.port = self._httpd.socket.getsockname()
321
def _http_start(self):
322
httpd = self._get_httpd()
323
self._http_base_url = '%s://%s:%s/' % (self._url_protocol,
326
self._http_starting.release()
328
while self._http_running:
330
httpd.handle_request()
331
except socket.timeout:
442
334
def _get_remote_url(self, path):
443
335
path_parts = path.split(os.path.sep)
455
347
"""Capture Server log output."""
456
348
self.logs.append(format % args)
458
def start_server(self, backing_transport_server=None):
459
"""See bzrlib.transport.Server.start_server.
350
def setUp(self, backing_transport_server=None):
351
"""See bzrlib.transport.Server.setUp.
461
353
:param backing_transport_server: The transport that requests over this
462
354
protocol should be forwarded to. Note that this is currently not
463
355
supported for HTTP.
465
357
# XXX: TODO: make the server back onto vfs_server rather than local
467
if not (backing_transport_server is None
468
or isinstance(backing_transport_server,
469
test_server.LocalURLServer)):
470
raise AssertionError(
471
"HTTPServer currently assumes local transport, got %s" %
472
backing_transport_server)
359
assert backing_transport_server is None or \
360
isinstance(backing_transport_server, LocalURLServer), \
361
"HTTPServer currently assumes local transport, got %s" % \
362
backing_transport_server
473
363
self._home_dir = os.getcwdu()
474
364
self._local_path_parts = self._home_dir.split(os.path.sep)
365
self._http_starting = threading.Lock()
366
self._http_starting.acquire()
367
self._http_running = True
368
self._http_base_url = None
369
self._http_thread = threading.Thread(target=self._http_start)
370
self._http_thread.setDaemon(True)
371
self._http_thread.start()
372
# Wait for the server thread to start (i.e release the lock)
373
self._http_starting.acquire()
374
self._http_starting.release()
477
super(HttpServer, self).start_server()
478
self._http_base_url = '%s://%s:%s/' % (
479
self._url_protocol, self.host, self.port)
378
"""See bzrlib.transport.Server.tearDown."""
379
self._httpd.server_close()
380
self._http_running = False
381
self._http_thread.join()
481
383
def get_url(self):
482
384
"""See bzrlib.transport.Server.get_url."""