78
64
connection early to avoid polluting the test results.
81
SimpleHTTPServer.SimpleHTTPRequestHandler.handle_one_request(self)
67
SimpleHTTPRequestHandler.handle_one_request(self)
82
68
except socket.error, e:
83
# Any socket error should close the connection, but some errors are
84
# due to the client closing early and we don't want to pollute test
85
# results, so we raise only the others.
86
self.close_connection = 1
88
or e.args[0] not in (errno.EPIPE, errno.ECONNRESET,
89
errno.ECONNABORTED, errno.EBADF)):
70
and e.args[0] in (errno.EPIPE, errno.ECONNRESET,
71
errno.ECONNABORTED,)):
72
self.close_connection = 1
92
error_content_type = 'text/plain'
93
error_message_format = '''\
98
def send_error(self, code, message=None):
99
"""Send and log an error reply.
101
We redefine the python-provided version to be able to set a
102
``Content-Length`` header as some http/1.1 clients complain otherwise
105
:param code: The HTTP error code.
107
:param message: The explanation of the error code, Defaults to a short
113
message = self.responses[code][0]
116
self.log_error("code %d, message %s", code, message)
117
content = (self.error_message_format %
118
{'code': code, 'message': message})
119
self.send_response(code, message)
120
self.send_header("Content-Type", self.error_content_type)
121
self.send_header("Content-Length", "%d" % len(content))
122
self.send_header('Connection', 'close')
124
if self.command != 'HEAD' and code >= 200 and code not in (204, 304):
125
self.wfile.write(content)
127
77
_range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)$')
128
78
_tail_regexp = re.compile(r'^-(?P<tail>\d+)$')
200
130
def get_multiple_ranges(self, file, file_size, ranges):
201
131
self.send_response(206)
202
132
self.send_header('Accept-Ranges', 'bytes')
203
boundary = '%d' % random.randint(0,0x7FFFFFFF)
204
self.send_header('Content-Type',
205
'multipart/byteranges; boundary=%s' % boundary)
206
boundary_line = '--%s\r\n' % boundary
207
# Calculate the Content-Length
209
for (start, end) in ranges:
210
content_length += len(boundary_line)
211
content_length += self._header_line_length(
212
'Content-type', 'application/octet-stream')
213
content_length += self._header_line_length(
214
'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
215
content_length += len('\r\n') # end headers
216
content_length += end - start + 1
217
content_length += len(boundary_line)
218
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)
219
136
self.end_headers()
221
# Send the multipart body
222
137
for (start, end) in ranges:
223
self.wfile.write(boundary_line)
224
self.send_header('Content-type', 'application/octet-stream')
225
self.send_header('Content-Range', 'bytes %d-%d/%d'
226
% (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,
227
143
self.end_headers()
228
144
self.send_range_content(file, start, end - start + 1)
230
self.wfile.write(boundary_line)
145
self.wfile.write("--%s\r\n" % boundary)
232
147
def do_GET(self):
233
148
"""Serve a GET request.
235
150
Handles the Range header.
238
self.server.test_case_server.GET_request_nb += 1
240
153
path = self.translate_path(self.path)
241
154
ranges_header_value = self.headers.get('Range')
242
155
if ranges_header_value is None or os.path.isdir(path):
243
156
# Let the mother class handle most cases
244
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
157
return SimpleHTTPRequestHandler.do_GET(self)
247
160
# Always read in binary mode. Opening files in text
318
231
return self._translate_path(path)
320
233
def _translate_path(self, path):
321
"""Translate a /-separated PATH to the local filename syntax.
323
Note that we're translating http URLs here, not file URLs.
324
The URL root location is the server's startup directory.
325
Components that mean special things to the local file system
326
(e.g. drive or directory names) are ignored. (XXX They should
327
probably be diagnosed.)
329
Override from python standard library to stop it calling os.getcwd()
331
# abandon query parameters
332
path = urlparse.urlparse(path)[2]
333
path = posixpath.normpath(urllib.unquote(path))
334
path = path.decode('utf-8')
335
words = path.split('/')
336
words = filter(None, words)
338
for num, word in enumerate(words):
234
return SimpleHTTPRequestHandler.translate_path(self, path)
236
if sys.platform == 'win32':
237
# On win32 you cannot access non-ascii filenames without
238
# decoding them into unicode first.
239
# However, under Linux, you can access bytestream paths
240
# without any problems. If this function was always active
241
# it would probably break tests when LANG=C was set
242
def _translate_path(self, path):
243
"""Translate a /-separated PATH to the local filename syntax.
245
For bzr, all url paths are considered to be utf8 paths.
246
On Linux, you can access these paths directly over the bytestream
247
request, but on win32, you must decode them, and access them
250
# abandon query parameters
251
path = urlparse.urlparse(path)[2]
252
path = posixpath.normpath(urllib.unquote(path))
253
path = path.decode('utf-8')
254
words = path.split('/')
255
words = filter(None, words)
340
258
drive, word = os.path.splitdrive(word)
341
head, word = os.path.split(word)
342
if word in (os.curdir, os.pardir): continue
343
path = os.path.join(path, word)
347
class TestingHTTPServerMixin:
349
def __init__(self, test_case_server):
259
head, word = os.path.split(word)
260
if word in (os.curdir, os.pardir): continue
261
path = os.path.join(path, word)
265
class TestingHTTPServer(BaseHTTPServer.HTTPServer):
267
def __init__(self, server_address, RequestHandlerClass,
269
BaseHTTPServer.HTTPServer.__init__(self, server_address,
350
271
# test_case_server can be used to communicate between the
351
272
# tests and the server (or the request handler and the
352
273
# server), allowing dynamic behaviors to be defined from
353
274
# the tests cases.
354
275
self.test_case_server = test_case_server
355
self._home_dir = test_case_server._home_dir
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):
395
def __init__(self, server_address, request_handler_class,
397
TestingHTTPServerMixin.__init__(self, test_case_server)
398
SocketServer.TCPServer.__init__(self, server_address,
399
request_handler_class)
402
class TestingThreadingHTTPServer(SocketServer.ThreadingTCPServer,
403
TestingHTTPServerMixin):
404
"""A threading HTTP test server for HTTP 1.1.
406
Since tests can initiate several concurrent connections to the same http
407
server, we need an independent connection for each of them. We achieve that
408
by spawning a new thread for each connection.
411
def __init__(self, server_address, request_handler_class,
413
TestingHTTPServerMixin.__init__(self, test_case_server)
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):
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):
440
288
"""A test server for http transports.
442
290
Subclasses can provide a specific request handler.
445
# The real servers depending on the protocol
446
http_server_class = {'HTTP/1.0': TestingHTTPServer,
447
'HTTP/1.1': TestingThreadingHTTPServer,
450
293
# Whether or not we proxy the requests (see
451
294
# TestingHTTPRequestHandler.translate_path).
452
295
proxy_requests = False
454
297
# used to form the url that connects to this server
455
298
_url_protocol = 'http'
457
def __init__(self, request_handler=TestingHTTPRequestHandler,
458
protocol_version=None):
461
:param request_handler: a class that will be instantiated to handle an
462
http connection (one or several requests).
464
:param protocol_version: if specified, will override the protocol
465
version of the request handler.
467
transport.Server.__init__(self)
300
# Subclasses can provide a specific request handler
301
def __init__(self, request_handler=TestingHTTPRequestHandler):
302
Server.__init__(self)
468
303
self.request_handler = request_handler
469
304
self.host = 'localhost'
471
306
self._httpd = None
472
self.protocol_version = protocol_version
473
# Allows tests to verify number of GET requests issued
474
self.GET_request_nb = 0
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
308
def _get_httpd(self):
484
309
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()
310
self._httpd = TestingHTTPServer((self.host, self.port),
311
self.request_handler,
313
host, self.port = self._httpd.socket.getsockname()
502
314
return self._httpd
504
316
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()
317
httpd = self._get_httpd()
318
self._http_base_url = '%s://%s:%s/' % (self._url_protocol,
321
self._http_starting.release()
523
# From now on, exceptions are taken care of by the
524
# SocketServer.BaseServer or the request handler.
525
323
while self._http_running:
527
# Really an HTTP connection but the python framework is generic
528
# and call them requests
529
325
httpd.handle_request()
530
326
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
544
329
def _get_remote_url(self, path):
545
330
path_parts = path.split(os.path.sep)
557
342
"""Capture Server log output."""
558
343
self.logs.append(format % args)
560
def start_server(self, backing_transport_server=None):
561
"""See bzrlib.transport.Server.start_server.
345
def setUp(self, backing_transport_server=None):
346
"""See bzrlib.transport.Server.setUp.
563
348
:param backing_transport_server: The transport that requests over this
564
349
protocol should be forwarded to. Note that this is currently not
565
350
supported for HTTP.
567
352
# XXX: TODO: make the server back onto vfs_server rather than local
569
if not (backing_transport_server is None
570
or isinstance(backing_transport_server,
571
test_server.LocalURLServer)):
572
raise AssertionError(
573
"HTTPServer currently assumes local transport, got %s" % \
574
backing_transport_server)
354
assert backing_transport_server is None or \
355
isinstance(backing_transport_server, LocalURLServer), \
356
"HTTPServer currently assumes local transport, got %s" % \
357
backing_transport_server
575
358
self._home_dir = os.getcwdu()
576
359
self._local_path_parts = self._home_dir.split(os.path.sep)
360
self._http_starting = threading.Lock()
361
self._http_starting.acquire()
362
self._http_running = True
577
363
self._http_base_url = None
579
# Create the server thread
580
self._http_starting = threading.Lock()
581
self._http_starting.acquire()
582
364
self._http_thread = threading.Thread(target=self._http_start)
583
365
self._http_thread.setDaemon(True)
584
self._http_exception = None
585
366
self._http_thread.start()
587
367
# Wait for the server thread to start (i.e release the lock)
588
368
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
369
self._http_starting.release()
597
def stop_server(self):
598
self._httpd.stop_server()
373
"""See bzrlib.transport.Server.tearDown."""
374
self._httpd.server_close()
599
375
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.
376
self._http_thread.join()
604
378
def get_url(self):
605
379
"""See bzrlib.transport.Server.get_url."""