13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
72
64
self.headers.get('referer', '-'),
73
65
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
75
def handle_one_request(self):
76
76
"""Handle a single HTTP request.
90
90
errno.ECONNABORTED, errno.EBADF)):
93
_range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)$')
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
_range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)?$')
94
132
_tail_regexp = re.compile(r'^-(?P<tail>\d+)$')
96
def parse_ranges(self, ranges_header):
97
"""Parse the range header value and returns ranges and tail.
99
RFC2616 14.35 says that syntactically invalid range
100
specifiers MUST be ignored. In that case, we return 0 for
101
tail and [] for ranges.
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.
105
147
if not ranges_header.startswith('bytes='):
106
148
# Syntactically invalid header
109
153
ranges_header = ranges_header[len('bytes='):]
110
154
for range_str in ranges_header.split(','):
111
# FIXME: RFC2616 says end is optional and default to file_size
112
155
range_match = self._range_regexp.match(range_str)
113
156
if range_match is not None:
114
157
start = int(range_match.group('start'))
115
end = int(range_match.group('end'))
158
end_match = range_match.group('end')
159
if end_match is None:
160
# RFC2616 says end is optional and default to file_size
117
165
# Syntactically invalid range
119
167
ranges.append((start, end))
121
169
tail_match = self._tail_regexp.match(range_str)
123
171
tail = int(tail_match.group('tail'))
125
173
# 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
129
190
def _header_line_length(self, keyword, value):
130
191
header_line = '%s: %s\r\n' % (keyword, value)
179
240
content_length += self._header_line_length(
180
241
'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
181
242
content_length += len('\r\n') # end headers
182
content_length += end - start # + 1
243
content_length += end - start + 1
183
244
content_length += len(boundary_line)
184
245
self.send_header('Content-length', content_length)
185
246
self.end_headers()
214
275
# mode may cause newline translations, making the
215
276
# actual size of the content transmitted *less* than
216
277
# the content-length!
217
file = open(path, 'rb')
219
280
self.send_error(404, "File not found")
222
file_size = os.fstat(file.fileno())[6]
223
tail, ranges = self.parse_ranges(ranges_header_value)
224
# Normalize tail into ranges
226
ranges.append((file_size - tail, file_size))
228
self._satisfiable_ranges = True
230
self._satisfiable_ranges = False
232
def check_range(range_specifier):
233
start, end = range_specifier
234
# RFC2616 14.35, ranges are invalid if start >= file_size
235
if start >= file_size:
236
self._satisfiable_ranges = False # Side-effect !
238
# RFC2616 14.35, end values should be truncated
239
# to file_size -1 if they exceed it
240
end = min(end, file_size - 1)
243
ranges = map(check_range, ranges)
245
if not self._satisfiable_ranges:
283
file_size = os.fstat(f.fileno())[6]
284
ranges = self._parse_ranges(ranges_header_value, file_size)
246
286
# RFC2616 14.16 and 14.35 says that when a server
247
287
# encounters unsatisfiable range specifiers, it
248
288
# SHOULD return a 416.
250
290
# FIXME: We SHOULD send a Content-Range header too,
251
291
# but the implementation of send_error does not
252
292
# allows that. So far.
256
296
if len(ranges) == 1:
257
297
(start, end) = ranges[0]
258
self.get_single_range(file, file_size, start, end)
298
self.get_single_range(f, file_size, start, end)
260
self.get_multiple_ranges(file, file_size, ranges)
300
self.get_multiple_ranges(f, file_size, ranges)
263
303
def translate_path(self, path):
264
304
"""Translate a /-separated PATH to the local filename syntax.
284
324
return self._translate_path(path)
286
326
def _translate_path(self, path):
287
return SimpleHTTPServer.SimpleHTTPRequestHandler.translate_path(
290
if sys.platform == 'win32':
291
# On win32 you cannot access non-ascii filenames without
292
# decoding them into unicode first.
293
# However, under Linux, you can access bytestream paths
294
# without any problems. If this function was always active
295
# it would probably break tests when LANG=C was set
296
def _translate_path(self, path):
297
"""Translate a /-separated PATH to the local filename syntax.
299
For bzr, all url paths are considered to be utf8 paths.
300
On Linux, you can access these paths directly over the bytestream
301
request, but on win32, you must decode them, and access them
304
# abandon query parameters
305
path = urlparse.urlparse(path)[2]
306
path = posixpath.normpath(urllib.unquote(path))
307
path = path.decode('utf-8')
308
words = path.split('/')
309
words = filter(None, words)
327
"""Translate a /-separated PATH to the local filename syntax.
329
Note that we're translating http URLs here, not file URLs.
330
The URL root location is the server's startup directory.
331
Components that mean special things to the local file system
332
(e.g. drive or directory names) are ignored. (XXX They should
333
probably be diagnosed.)
335
Override from python standard library to stop it calling os.getcwd()
337
# abandon query parameters
338
path = urlparse.urlparse(path)[2]
339
path = posixpath.normpath(urllib.unquote(path))
340
path = path.decode('utf-8')
341
words = path.split('/')
342
words = filter(None, words)
344
for num, word in enumerate(words):
312
346
drive, word = os.path.splitdrive(word)
313
head, word = os.path.split(word)
314
if word in (os.curdir, os.pardir): continue
315
path = os.path.join(path, word)
347
head, word = os.path.split(word)
348
if word in (os.curdir, os.pardir): continue
349
path = os.path.join(path, word)
319
353
class TestingHTTPServerMixin:
324
358
# server), allowing dynamic behaviors to be defined from
325
359
# the tests cases.
326
360
self.test_case_server = test_case_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):
361
self._home_dir = test_case_server._home_dir
364
class TestingHTTPServer(test_server.TestingTCPServer, TestingHTTPServerMixin):
360
366
def __init__(self, server_address, request_handler_class,
361
367
test_case_server):
368
test_server.TestingTCPServer.__init__(self, server_address,
369
request_handler_class)
362
370
TestingHTTPServerMixin.__init__(self, test_case_server)
363
SocketServer.TCPServer.__init__(self, server_address,
364
request_handler_class)
367
class TestingThreadingHTTPServer(SocketServer.ThreadingTCPServer,
373
class TestingThreadingHTTPServer(test_server.TestingThreadingTCPServer,
368
374
TestingHTTPServerMixin):
369
375
"""A threading HTTP test server for HTTP 1.1.
372
378
server, we need an independent connection for each of them. We achieve that
373
379
by spawning a new thread for each connection.
376
381
def __init__(self, server_address, request_handler_class,
377
382
test_case_server):
383
test_server.TestingThreadingTCPServer.__init__(self, server_address,
384
request_handler_class)
378
385
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):
388
class HttpServer(test_server.TestingTCPServerInAThread):
388
389
"""A test server for http transports.
390
391
Subclasses can provide a specific request handler.
412
413
:param protocol_version: if specified, will override the protocol
413
414
version of the request handler.
415
transport.Server.__init__(self)
416
self.request_handler = 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)
417
429
self.host = 'localhost'
420
self.protocol_version = protocol_version
431
super(HttpServer, self).__init__((self.host, self.port),
434
self.protocol_version = proto_vers
421
435
# Allows tests to verify number of GET requests issued
422
436
self.GET_request_nb = 0
424
def _get_httpd(self):
425
if self._httpd is None:
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)
442
host, self.port = self._httpd.socket.getsockname()
445
def _http_start(self):
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.
466
while self._http_running:
468
# Really an HTTP connection but the python framework is generic
469
# and call them requests
470
httpd.handle_request()
471
except socket.timeout:
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)
474
444
def _get_remote_url(self, path):
475
445
path_parts = path.split(os.path.sep)
487
457
"""Capture Server log output."""
488
458
self.logs.append(format % args)
490
def setUp(self, backing_transport_server=None):
491
"""See bzrlib.transport.Server.setUp.
460
def start_server(self, backing_transport_server=None):
461
"""See bzrlib.transport.Server.start_server.
493
463
:param backing_transport_server: The transport that requests over this
494
464
protocol should be forwarded to. Note that this is currently not
495
465
supported for HTTP.
497
467
# XXX: TODO: make the server back onto vfs_server rather than local
499
assert backing_transport_server is None or \
500
isinstance(backing_transport_server, local.LocalURLServer), \
501
"HTTPServer currently assumes local transport, got %s" % \
502
backing_transport_server
469
if not (backing_transport_server is None
470
or isinstance(backing_transport_server,
471
test_server.LocalURLServer)):
472
raise AssertionError(
473
"HTTPServer currently assumes local transport, got %s" %
474
backing_transport_server)
503
475
self._home_dir = os.getcwdu()
504
476
self._local_path_parts = self._home_dir.split(os.path.sep)
505
self._http_base_url = None
507
# Create the server thread
508
self._http_starting = threading.Lock()
509
self._http_starting.acquire()
510
self._http_thread = threading.Thread(target=self._http_start)
511
self._http_thread.setDaemon(True)
512
self._http_exception = None
513
self._http_thread.start()
515
# Wait for the server thread to start (i.e release the lock)
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
522
self._http_starting.release()
526
"""See bzrlib.transport.Server.tearDown."""
527
self._httpd.tearDown()
528
self._http_running = False
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.
479
super(HttpServer, self).start_server()
480
self._http_base_url = '%s://%s:%s/' % (
481
self._url_protocol, self.host, self.port)
533
483
def get_url(self):
534
484
"""See bzrlib.transport.Server.get_url."""