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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
64
73
self.headers.get('referer', '-'),
65
74
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
76
def handle_one_request(self):
76
77
"""Handle a single HTTP request.
90
91
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)
131
_range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)?$')
94
_range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)$')
132
95
_tail_regexp = re.compile(r'^-(?P<tail>\d+)$')
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.
97
def parse_ranges(self, ranges_header):
98
"""Parse the range header value and returns ranges and tail.
100
RFC2616 14.35 says that syntactically invalid range
101
specifiers MUST be ignored. In that case, we return 0 for
102
tail and [] for ranges.
147
106
if not ranges_header.startswith('bytes='):
148
107
# Syntactically invalid header
153
110
ranges_header = ranges_header[len('bytes='):]
154
111
for range_str in ranges_header.split(','):
112
# FIXME: RFC2616 says end is optional and default to file_size
155
113
range_match = self._range_regexp.match(range_str)
156
114
if range_match is not None:
157
115
start = int(range_match.group('start'))
158
end_match = range_match.group('end')
159
if end_match is None:
160
# RFC2616 says end is optional and default to file_size
116
end = int(range_match.group('end'))
165
118
# Syntactically invalid range
167
120
ranges.append((start, end))
169
122
tail_match = self._tail_regexp.match(range_str)
171
124
tail = int(tail_match.group('tail'))
173
126
# 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
190
130
def _header_line_length(self, keyword, value):
191
131
header_line = '%s: %s\r\n' % (keyword, value)
240
180
content_length += self._header_line_length(
241
181
'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
242
182
content_length += len('\r\n') # end headers
243
content_length += end - start + 1
183
content_length += end - start # + 1
244
184
content_length += len(boundary_line)
245
185
self.send_header('Content-length', content_length)
246
186
self.end_headers()
275
215
# mode may cause newline translations, making the
276
216
# actual size of the content transmitted *less* than
277
217
# the content-length!
218
file = open(path, 'rb')
280
220
self.send_error(404, "File not found")
283
file_size = os.fstat(f.fileno())[6]
284
ranges = self._parse_ranges(ranges_header_value, file_size)
223
file_size = os.fstat(file.fileno())[6]
224
tail, ranges = self.parse_ranges(ranges_header_value)
225
# Normalize tail into ranges
227
ranges.append((file_size - tail, file_size))
229
self._satisfiable_ranges = True
231
self._satisfiable_ranges = False
233
def check_range(range_specifier):
234
start, end = range_specifier
235
# RFC2616 14.35, ranges are invalid if start >= file_size
236
if start >= file_size:
237
self._satisfiable_ranges = False # Side-effect !
239
# RFC2616 14.35, end values should be truncated
240
# to file_size -1 if they exceed it
241
end = min(end, file_size - 1)
244
ranges = map(check_range, ranges)
246
if not self._satisfiable_ranges:
286
247
# RFC2616 14.16 and 14.35 says that when a server
287
248
# encounters unsatisfiable range specifiers, it
288
249
# SHOULD return a 416.
290
251
# FIXME: We SHOULD send a Content-Range header too,
291
252
# but the implementation of send_error does not
292
253
# allows that. So far.
296
257
if len(ranges) == 1:
297
258
(start, end) = ranges[0]
298
self.get_single_range(f, file_size, start, end)
259
self.get_single_range(file, file_size, start, end)
300
self.get_multiple_ranges(f, file_size, ranges)
261
self.get_multiple_ranges(file, file_size, ranges)
303
264
def translate_path(self, path):
304
265
"""Translate a /-separated PATH to the local filename syntax.
360
321
self.test_case_server = test_case_server
361
322
self._home_dir = test_case_server._home_dir
364
class TestingHTTPServer(test_server.TestingTCPServer, TestingHTTPServerMixin):
325
"""Called to clean-up the server.
327
Since the server may be (surely is, even) in a blocking listen, we
328
shutdown its socket before closing it.
330
# Note that is this executed as part of the implicit tear down in the
331
# main thread while the server runs in its own thread. The clean way
332
# to tear down the server is to instruct him to stop accepting
333
# connections and wait for the current connection(s) to end
334
# naturally. To end the connection naturally, the http transports
335
# should close their socket when they do not need to talk to the
336
# server anymore. This happens naturally during the garbage collection
337
# phase of the test transport objetcs (the server clients), so we
338
# don't have to worry about them. So, for the server, we must tear
339
# down here, from the main thread, when the test have ended. Note
340
# that since the server is in a blocking operation and since python
341
# use select internally, shutting down the socket is reliable and
344
self.socket.shutdown(socket.SHUT_RDWR)
345
except socket.error, e:
346
# WSAENOTCONN (10057) 'Socket is not connected' is harmless on
347
# windows (occurs before the first connection attempt
349
if not len(e.args) or e.args[0] != 10057:
351
# Let the server properly close the socket
355
class TestingHTTPServer(SocketServer.TCPServer, TestingHTTPServerMixin):
366
357
def __init__(self, server_address, request_handler_class,
367
358
test_case_server):
368
test_server.TestingTCPServer.__init__(self, server_address,
369
request_handler_class)
370
359
TestingHTTPServerMixin.__init__(self, test_case_server)
373
class TestingThreadingHTTPServer(test_server.TestingThreadingTCPServer,
360
SocketServer.TCPServer.__init__(self, server_address,
361
request_handler_class)
364
class TestingThreadingHTTPServer(SocketServer.ThreadingTCPServer,
374
365
TestingHTTPServerMixin):
375
366
"""A threading HTTP test server for HTTP 1.1.
378
369
server, we need an independent connection for each of them. We achieve that
379
370
by spawning a new thread for each connection.
381
373
def __init__(self, server_address, request_handler_class,
382
374
test_case_server):
383
test_server.TestingThreadingTCPServer.__init__(self, server_address,
384
request_handler_class)
385
375
TestingHTTPServerMixin.__init__(self, test_case_server)
388
class HttpServer(test_server.TestingTCPServerInAThread):
376
SocketServer.ThreadingTCPServer.__init__(self, server_address,
377
request_handler_class)
378
# Decides how threads will act upon termination of the main
379
# process. This is prophylactic as we should not leave the threads
381
self.daemon_threads = True
384
class HttpServer(transport.Server):
389
385
"""A test server for http transports.
391
387
Subclasses can provide a specific request handler.
413
409
:param protocol_version: if specified, will override the protocol
414
410
version of the 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)
412
transport.Server.__init__(self)
413
self.request_handler = request_handler
429
414
self.host = 'localhost'
431
super(HttpServer, self).__init__((self.host, self.port),
434
self.protocol_version = proto_vers
417
self.protocol_version = protocol_version
435
418
# Allows tests to verify number of GET requests issued
436
419
self.GET_request_nb = 0
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)
421
def _get_httpd(self):
422
if self._httpd is None:
423
rhandler = self.request_handler
424
# Depending on the protocol version, we will create the approriate
426
if self.protocol_version is None:
427
# Use the request handler one
428
proto_vers = rhandler.protocol_version
430
# Use our own, it will be used to override the request handler
432
proto_vers = self.protocol_version
433
# Create the appropriate server for the required protocol
434
serv_cls = self.http_server_class.get(proto_vers, None)
436
raise httplib.UnknownProtocol(proto_vers)
438
self._httpd = serv_cls((self.host, self.port), rhandler, self)
439
host, self.port = self._httpd.socket.getsockname()
442
def _http_start(self):
443
"""Server thread main entry point. """
444
self._http_running = False
447
httpd = self._get_httpd()
448
self._http_base_url = '%s://%s:%s/' % (self._url_protocol,
449
self.host, self.port)
450
self._http_running = True
452
# Whatever goes wrong, we save the exception for the main
453
# thread. Note that since we are running in a thread, no signal
454
# can be received, so we don't care about KeyboardInterrupt.
455
self._http_exception = sys.exc_info()
457
# Release the lock or the main thread will block and the whole
459
self._http_starting.release()
461
# From now on, exceptions are taken care of by the
462
# SocketServer.BaseServer or the request handler.
463
while self._http_running:
465
# Really an HTTP connection but the python framework is generic
466
# and call them requests
467
httpd.handle_request()
468
except socket.timeout:
444
471
def _get_remote_url(self, path):
445
472
path_parts = path.split(os.path.sep)
457
484
"""Capture Server log output."""
458
485
self.logs.append(format % args)
460
def start_server(self, backing_transport_server=None):
461
"""See bzrlib.transport.Server.start_server.
487
def setUp(self, backing_transport_server=None):
488
"""See bzrlib.transport.Server.setUp.
463
490
:param backing_transport_server: The transport that requests over this
464
491
protocol should be forwarded to. Note that this is currently not
465
492
supported for HTTP.
467
494
# XXX: TODO: make the server back onto vfs_server rather than local
469
if not (backing_transport_server is None
470
or isinstance(backing_transport_server,
471
test_server.LocalURLServer)):
496
if not (backing_transport_server is None or \
497
isinstance(backing_transport_server, local.LocalURLServer)):
472
498
raise AssertionError(
473
"HTTPServer currently assumes local transport, got %s" %
499
"HTTPServer currently assumes local transport, got %s" % \
474
500
backing_transport_server)
475
501
self._home_dir = os.getcwdu()
476
502
self._local_path_parts = self._home_dir.split(os.path.sep)
503
self._http_base_url = None
505
# Create the server thread
506
self._http_starting = threading.Lock()
507
self._http_starting.acquire()
508
self._http_thread = threading.Thread(target=self._http_start)
509
self._http_thread.setDaemon(True)
510
self._http_exception = None
511
self._http_thread.start()
513
# Wait for the server thread to start (i.e release the lock)
514
self._http_starting.acquire()
516
if self._http_exception is not None:
517
# Something went wrong during server start
518
exc_class, exc_value, exc_tb = self._http_exception
519
raise exc_class, exc_value, exc_tb
520
self._http_starting.release()
479
super(HttpServer, self).start_server()
480
self._http_base_url = '%s://%s:%s/' % (
481
self._url_protocol, self.host, self.port)
524
"""See bzrlib.transport.Server.tearDown."""
525
self._httpd.tearDown()
526
self._http_running = False
527
# We don't need to 'self._http_thread.join()' here since the thread is
528
# a daemonic one and will be garbage collected anyway. Joining just
529
# slows us down for no added benefit.
483
531
def get_url(self):
484
532
"""See bzrlib.transport.Server.get_url."""