64
52
self.headers.get('referer', '-'),
65
53
self.headers.get('user-agent', '-'))
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
73
self.connection.close()
75
55
def handle_one_request(self):
76
56
"""Handle a single HTTP request.
78
We catch all socket errors occurring when the client close the
79
connection early to avoid polluting the test results.
58
You normally don't need to override this method; see the class
59
__doc__ string for information on how to handle specific HTTP
60
commands such as GET and POST.
82
self._handle_one_request()
83
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.
63
for i in xrange(1,11): # Don't try more than 10 times
65
self.raw_requestline = self.rfile.readline()
66
except socket.error, e:
67
if e.args[0] in (errno.EAGAIN, errno.EWOULDBLOCK):
68
# omitted for now because some tests look at the log of
69
# the server and expect to see no errors. see recent
70
# email thread. -- mbp 20051021.
71
## self.log_message('EAGAIN (%d) while reading from raw_requestline' % i)
77
if not self.raw_requestline:
87
78
self.close_connection = 1
89
or e.args[0] not in (errno.EPIPE, errno.ECONNRESET,
90
errno.ECONNABORTED, errno.EBADF)):
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)
80
if not self.parse_request(): # An error code has been sent, just exit
82
mname = 'do_' + self.command
83
if getattr(self, mname, None) is None:
84
self.send_error(501, "Unsupported method (%r)" % self.command)
86
method = getattr(self, mname)
131
89
_range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)$')
132
90
_tail_regexp = re.compile(r'^-(?P<tail>\d+)$')
204
142
def get_multiple_ranges(self, file, file_size, ranges):
205
143
self.send_response(206)
206
144
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)
145
boundary = "%d" % random.randint(0,0x7FFFFFFF)
146
self.send_header("Content-Type",
147
"multipart/byteranges; boundary=%s" % boundary)
223
148
self.end_headers()
225
# Send the multipart body
226
149
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))
150
self.wfile.write("--%s\r\n" % boundary)
151
self.send_header("Content-type", 'application/octet-stream')
152
self.send_header("Content-Range", "bytes %d-%d/%d" % (start,
231
155
self.end_headers()
232
156
self.send_range_content(file, start, end - start + 1)
234
self.wfile.write(boundary_line)
157
self.wfile.write("--%s\r\n" % boundary)
236
159
def do_GET(self):
237
160
"""Serve a GET request.
239
162
Handles the Range header.
242
self.server.test_case_server.GET_request_nb += 1
244
165
path = self.translate_path(self.path)
245
166
ranges_header_value = self.headers.get('Range')
246
167
if ranges_header_value is None or os.path.isdir(path):
247
168
# Let the mother class handle most cases
248
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
169
return SimpleHTTPRequestHandler.do_GET(self)
251
172
# Always read in binary mode. Opening files in text
252
173
# mode may cause newline translations, making the
253
174
# actual size of the content transmitted *less* than
254
175
# the content-length!
176
file = open(path, 'rb')
257
178
self.send_error(404, "File not found")
260
file_size = os.fstat(f.fileno())[6]
181
file_size = os.fstat(file.fileno())[6]
261
182
tail, ranges = self.parse_ranges(ranges_header_value)
262
183
# Normalize tail into ranges
294
215
if len(ranges) == 1:
295
216
(start, end) = ranges[0]
296
self.get_single_range(f, file_size, start, end)
217
self.get_single_range(file, file_size, start, end)
298
self.get_multiple_ranges(f, file_size, ranges)
301
def translate_path(self, path):
302
"""Translate a /-separated PATH to the local filename syntax.
304
If the server requires it, proxy the path before the usual translation
306
if self.server.test_case_server.proxy_requests:
307
# We need to act as a proxy and accept absolute urls,
308
# which SimpleHTTPRequestHandler (parent) is not
309
# ready for. So we just drop the protocol://host:port
310
# part in front of the request-url (because we know
311
# we would not forward the request to *another*
314
# So we do what SimpleHTTPRequestHandler.translate_path
315
# do beginning with python 2.4.3: abandon query
316
# parameters, scheme, host port, etc (which ensure we
317
# provide the right behaviour on all python versions).
219
self.get_multiple_ranges(file, file_size, ranges)
222
if sys.platform == 'win32':
223
# On win32 you cannot access non-ascii filenames without
224
# decoding them into unicode first.
225
# However, under Linux, you can access bytestream paths
226
# without any problems. If this function was always active
227
# it would probably break tests when LANG=C was set
228
def translate_path(self, path):
229
"""Translate a /-separated PATH to the local filename syntax.
231
For bzr, all url paths are considered to be utf8 paths.
232
On Linux, you can access these paths directly over the bytestream
233
request, but on win32, you must decode them, and access them
236
# abandon query parameters
318
237
path = urlparse.urlparse(path)[2]
319
# And now, we can apply *our* trick to proxy files
322
return self._translate_path(path)
324
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):
238
path = posixpath.normpath(urllib.unquote(path))
239
path = path.decode('utf-8')
240
words = path.split('/')
241
words = filter(None, words)
344
244
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):
245
head, word = os.path.split(word)
246
if word in (os.curdir, os.pardir): continue
247
path = os.path.join(path, word)
251
class TestingHTTPServer(BaseHTTPServer.HTTPServer):
253
def __init__(self, server_address, RequestHandlerClass,
255
BaseHTTPServer.HTTPServer.__init__(self, server_address,
354
257
# test_case_server can be used to communicate between the
355
258
# tests and the server (or the request handler and the
356
259
# server), allowing dynamic behaviors to be defined from
357
260
# the tests cases.
358
261
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):
264
class HttpServer(Server):
387
265
"""A test server for http transports.
389
267
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
# Whether or not we proxy the requests (see
398
# TestingHTTPRequestHandler.translate_path).
399
proxy_requests = False
401
270
# used to form the url that connects to this server
402
271
_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)
273
# Subclasses can provide a specific request handler
274
def __init__(self, request_handler=TestingHTTPRequestHandler):
275
Server.__init__(self)
276
self.request_handler = request_handler
427
277
self.host = 'localhost'
429
super(HttpServer, self).__init__((self.host, self.port),
432
self.protocol_version = proto_vers
433
# Allows tests to verify number of GET requests issued
434
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)
281
def _get_httpd(self):
282
if self._httpd is None:
283
self._httpd = TestingHTTPServer((self.host, self.port),
284
self.request_handler,
286
host, self.port = self._httpd.socket.getsockname()
289
def _http_start(self):
290
httpd = self._get_httpd()
291
self._http_base_url = '%s://%s:%s/' % (self._url_protocol,
294
self._http_starting.release()
295
httpd.socket.settimeout(0.1)
297
while self._http_running:
299
httpd.handle_request()
300
except socket.timeout:
442
303
def _get_remote_url(self, path):
443
304
path_parts = path.split(os.path.sep)
455
316
"""Capture Server log output."""
456
317
self.logs.append(format % args)
458
def start_server(self, backing_transport_server=None):
459
"""See bzrlib.transport.Server.start_server.
319
def setUp(self, backing_transport_server=None):
320
"""See bzrlib.transport.Server.setUp.
461
322
:param backing_transport_server: The transport that requests over this
462
323
protocol should be forwarded to. Note that this is currently not
463
324
supported for HTTP.
465
326
# 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)
328
assert backing_transport_server is None or \
329
isinstance(backing_transport_server, LocalURLServer), \
330
"HTTPServer currently assumes local transport, got %s" % \
331
backing_transport_server
473
332
self._home_dir = os.getcwdu()
474
333
self._local_path_parts = self._home_dir.split(os.path.sep)
334
self._http_starting = threading.Lock()
335
self._http_starting.acquire()
336
self._http_running = True
337
self._http_base_url = None
338
self._http_thread = threading.Thread(target=self._http_start)
339
self._http_thread.setDaemon(True)
340
self._http_thread.start()
341
# Wait for the server thread to start (i.e release the lock)
342
self._http_starting.acquire()
343
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)
347
"""See bzrlib.transport.Server.tearDown."""
348
self._http_running = False
349
self._http_thread.join()
481
351
def get_url(self):
482
352
"""See bzrlib.transport.Server.get_url."""