15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
import http.client as http_client
20
import http.server as http_server
22
import httplib as http_client
23
import SimpleHTTPServer as http_server
24
import SimpleHTTPServer
31
from urlparse import urlparse
33
from urllib.parse import urlparse
39
from . import test_server
33
from bzrlib import transport
34
from bzrlib.tests import test_server
35
from bzrlib.transport import local
42
38
class BadWebserverPath(ValueError):
93
89
errno.ECONNABORTED, errno.EBADF)):
96
error_content_type = 'text/plain'
97
error_message_format = '''\
102
def send_error(self, code, message=None):
103
"""Send and log an error reply.
105
We redefine the python-provided version to be able to set a
106
``Content-Length`` header as some http/1.1 clients complain otherwise
109
:param code: The HTTP error code.
111
:param message: The explanation of the error code, Defaults to a short
117
message = self.responses[code][0]
120
self.log_error("code %d, message %s", code, message)
121
content = (self.error_message_format %
122
{'code': code, 'message': message})
123
self.send_response(code, message)
124
self.send_header("Content-Type", self.error_content_type)
125
self.send_header("Content-Length", "%d" % len(content))
126
self.send_header('Connection', 'close')
128
if self.command != 'HEAD' and code >= 200 and code not in (204, 304):
129
self.wfile.write(content.encode('utf-8'))
131
def _handle_one_request(self):
132
http_server.SimpleHTTPRequestHandler.handle_one_request(self)
134
_range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)?$')
92
_range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)$')
135
93
_tail_regexp = re.compile(r'^-(?P<tail>\d+)$')
137
def _parse_ranges(self, ranges_header, file_size):
138
"""Parse the range header value and returns ranges.
140
RFC2616 14.35 says that syntactically invalid range specifiers MUST be
141
ignored. In that case, we return None instead of a range list.
143
:param ranges_header: The 'Range' header value.
145
:param file_size: The size of the requested file.
147
:return: A list of (start, end) tuples or None if some invalid range
148
specifier is encountered.
95
def parse_ranges(self, ranges_header):
96
"""Parse the range header value and returns ranges and tail.
98
RFC2616 14.35 says that syntactically invalid range
99
specifiers MUST be ignored. In that case, we return 0 for
100
tail and [] for ranges.
150
104
if not ranges_header.startswith('bytes='):
151
105
# Syntactically invalid header
156
108
ranges_header = ranges_header[len('bytes='):]
157
109
for range_str in ranges_header.split(','):
110
# FIXME: RFC2616 says end is optional and default to file_size
158
111
range_match = self._range_regexp.match(range_str)
159
112
if range_match is not None:
160
113
start = int(range_match.group('start'))
161
end_match = range_match.group('end')
162
if end_match is None:
163
# RFC2616 says end is optional and default to file_size
114
end = int(range_match.group('end'))
168
116
# Syntactically invalid range
170
118
ranges.append((start, end))
172
120
tail_match = self._tail_regexp.match(range_str)
174
122
tail = int(tail_match.group('tail'))
176
124
# Syntactically invalid range
179
# Normalize tail into ranges
180
ranges.append((max(0, file_size - tail), file_size))
183
for start, end in ranges:
184
if start >= file_size:
185
# RFC2616 14.35, ranges are invalid if start >= file_size
187
# RFC2616 14.35, end values should be truncated
188
# to file_size -1 if they exceed it
189
end = min(end, file_size - 1)
190
checked_ranges.append((start, end))
191
return checked_ranges
193
128
def _header_line_length(self, keyword, value):
194
129
header_line = '%s: %s\r\n' % (keyword, value)
230
165
def get_multiple_ranges(self, file, file_size, ranges):
231
166
self.send_response(206)
232
167
self.send_header('Accept-Ranges', 'bytes')
233
boundary = '%d' % random.randint(0, 0x7FFFFFFF)
168
boundary = '%d' % random.randint(0,0x7FFFFFFF)
234
169
self.send_header('Content-Type',
235
170
'multipart/byteranges; boundary=%s' % boundary)
236
boundary_line = b'--%s\r\n' % boundary.encode('ascii')
171
boundary_line = '--%s\r\n' % boundary
237
172
# Calculate the Content-Length
238
173
content_length = 0
239
174
for (start, end) in ranges:
271
206
ranges_header_value = self.headers.get('Range')
272
207
if ranges_header_value is None or os.path.isdir(path):
273
208
# Let the mother class handle most cases
274
return http_server.SimpleHTTPRequestHandler.do_GET(self)
209
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
277
212
# Always read in binary mode. Opening files in text
278
213
# mode may cause newline translations, making the
279
214
# actual size of the content transmitted *less* than
280
215
# the content-length!
216
file = open(path, 'rb')
283
218
self.send_error(404, "File not found")
286
file_size = os.fstat(f.fileno())[6]
287
ranges = self._parse_ranges(ranges_header_value, file_size)
221
file_size = os.fstat(file.fileno())[6]
222
tail, ranges = self.parse_ranges(ranges_header_value)
223
# Normalize tail into ranges
225
ranges.append((file_size - tail, file_size))
227
self._satisfiable_ranges = True
229
self._satisfiable_ranges = False
231
def check_range(range_specifier):
232
start, end = range_specifier
233
# RFC2616 14.35, ranges are invalid if start >= file_size
234
if start >= file_size:
235
self._satisfiable_ranges = False # Side-effect !
237
# RFC2616 14.35, end values should be truncated
238
# to file_size -1 if they exceed it
239
end = min(end, file_size - 1)
242
ranges = map(check_range, ranges)
244
if not self._satisfiable_ranges:
289
245
# RFC2616 14.16 and 14.35 says that when a server
290
246
# encounters unsatisfiable range specifiers, it
291
247
# SHOULD return a 416.
293
249
# FIXME: We SHOULD send a Content-Range header too,
294
250
# but the implementation of send_error does not
295
251
# allows that. So far.
338
294
Override from python standard library to stop it calling os.getcwd()
340
296
# abandon query parameters
341
path = urlparse(path)[2]
342
path = posixpath.normpath(urlutils.unquote(path))
343
if sys.version_info[0] == 2:
344
path = path.decode('utf-8')
297
path = urlparse.urlparse(path)[2]
298
path = posixpath.normpath(urllib.unquote(path))
299
path = path.decode('utf-8')
345
300
words = path.split('/')
301
words = filter(None, words)
347
for num, word in enumerate(w for w in words if w):
303
for num, word in enumerate(words):
349
305
drive, word = os.path.splitdrive(word)
350
306
head, word = os.path.split(word)
351
if word in (os.curdir, os.pardir):
307
if word in (os.curdir, os.pardir): continue
353
308
path = os.path.join(path, word)
364
319
self.test_case_server = test_case_server
365
320
self._home_dir = test_case_server._home_dir
368
class TestingHTTPServer(test_server.TestingTCPServer, TestingHTTPServerMixin):
322
def stop_server(self):
323
"""Called to clean-up the server.
325
Since the server may be (surely is, even) in a blocking listen, we
326
shutdown its socket before closing it.
328
# Note that is this executed as part of the implicit tear down in the
329
# main thread while the server runs in its own thread. The clean way
330
# to tear down the server is to instruct him to stop accepting
331
# connections and wait for the current connection(s) to end
332
# naturally. To end the connection naturally, the http transports
333
# should close their socket when they do not need to talk to the
334
# server anymore. This happens naturally during the garbage collection
335
# phase of the test transport objetcs (the server clients), so we
336
# don't have to worry about them. So, for the server, we must tear
337
# down here, from the main thread, when the test have ended. Note
338
# that since the server is in a blocking operation and since python
339
# use select internally, shutting down the socket is reliable and
342
self.socket.shutdown(socket.SHUT_RDWR)
343
except socket.error, e:
344
# WSAENOTCONN (10057) 'Socket is not connected' is harmless on
345
# windows (occurs before the first connection attempt
348
# 'Socket is not connected' can also occur on OSX, with a
349
# "regular" ENOTCONN (when something went wrong during test case
350
# setup leading to self.setUp() *not* being called but
351
# self.stop_server() still being called -- vila20081106
352
if not len(e.args) or e.args[0] not in (errno.ENOTCONN, 10057):
354
# Let the server properly close the socket
358
class TestingHTTPServer(SocketServer.TCPServer, TestingHTTPServerMixin):
370
360
def __init__(self, server_address, request_handler_class,
371
361
test_case_server):
372
test_server.TestingTCPServer.__init__(self, server_address,
373
request_handler_class)
374
362
TestingHTTPServerMixin.__init__(self, test_case_server)
377
class TestingThreadingHTTPServer(test_server.TestingThreadingTCPServer,
363
SocketServer.TCPServer.__init__(self, server_address,
364
request_handler_class)
367
class TestingThreadingHTTPServer(SocketServer.ThreadingTCPServer,
378
368
TestingHTTPServerMixin):
379
369
"""A threading HTTP test server for HTTP 1.1.
386
376
def __init__(self, server_address, request_handler_class,
387
377
test_case_server):
388
test_server.TestingThreadingTCPServer.__init__(self, server_address,
389
request_handler_class)
390
378
TestingHTTPServerMixin.__init__(self, test_case_server)
393
class HttpServer(test_server.TestingTCPServerInAThread):
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
386
def process_request_thread(self, request, client_address):
387
SocketServer.ThreadingTCPServer.process_request_thread(
388
self, request, client_address)
389
# Under some circumstances (as in bug #383920), we need to force the
390
# shutdown as python delays it until gc occur otherwise and the client
393
# The request process has been completed, the thread is about to
394
# die, let's shutdown the socket if we can.
395
request.shutdown(socket.SHUT_RDWR)
396
except (socket.error, select.error), e:
397
if e[0] in (errno.EBADF, errno.ENOTCONN):
398
# Right, the socket is already down
404
class HttpServer(transport.Server):
394
405
"""A test server for http transports.
396
407
Subclasses can provide a specific request handler.
418
429
:param protocol_version: if specified, will override the protocol
419
430
version of the request handler.
421
# Depending on the protocol version, we will create the approriate
423
if protocol_version is None:
424
# Use the request handler one
425
proto_vers = request_handler.protocol_version
427
# Use our own, it will be used to override the request handler
429
proto_vers = protocol_version
430
# Get the appropriate server class for the required protocol
431
serv_cls = self.http_server_class.get(proto_vers, None)
433
raise http_client.UnknownProtocol(proto_vers)
432
transport.Server.__init__(self)
433
self.request_handler = request_handler
434
434
self.host = 'localhost'
436
super(HttpServer, self).__init__((self.host, self.port),
439
self.protocol_version = proto_vers
437
self.protocol_version = protocol_version
440
438
# Allows tests to verify number of GET requests issued
441
439
self.GET_request_nb = 0
442
self._http_base_url = None
445
def create_server(self):
446
return self.server_class(
447
(self.host, self.port), self.request_handler_class, self)
441
def create_httpd(self, serv_cls, rhandler_cls):
442
return serv_cls((self.host, self.port), self.request_handler, self)
445
return "%s(%s:%s)" % \
446
(self.__class__.__name__, self.host, self.port)
448
def _get_httpd(self):
449
if self._httpd is None:
450
rhandler = self.request_handler
451
# Depending on the protocol version, we will create the approriate
453
if self.protocol_version is None:
454
# Use the request handler one
455
proto_vers = rhandler.protocol_version
457
# Use our own, it will be used to override the request handler
459
proto_vers = self.protocol_version
460
# Create the appropriate server for the required protocol
461
serv_cls = self.http_server_class.get(proto_vers, None)
463
raise httplib.UnknownProtocol(proto_vers)
465
self._httpd = self.create_httpd(serv_cls, rhandler)
466
self.host, self.port = self._httpd.socket.getsockname()
469
def _http_start(self):
470
"""Server thread main entry point. """
471
self._http_running = False
474
httpd = self._get_httpd()
475
self._http_base_url = '%s://%s:%s/' % (self._url_protocol,
476
self.host, self.port)
477
self._http_running = True
479
# Whatever goes wrong, we save the exception for the main
480
# thread. Note that since we are running in a thread, no signal
481
# can be received, so we don't care about KeyboardInterrupt.
482
self._http_exception = sys.exc_info()
484
# Release the lock or the main thread will block and the whole
486
self._http_starting.release()
488
# From now on, exceptions are taken care of by the
489
# SocketServer.BaseServer or the request handler.
490
while self._http_running:
492
# Really an HTTP connection but the python framework is generic
493
# and call them requests
494
httpd.handle_request()
495
except socket.timeout:
497
except (socket.error, select.error), e:
498
if (e[0] == errno.EBADF
499
or (sys.platform == 'win32' and e[0] == 10038)):
500
# Starting with python-2.6, handle_request may raise socket
501
# or select exceptions when the server is shut down (as we
503
# 10038 = WSAENOTSOCK
504
# http://msdn.microsoft.com/en-us/library/ms740668%28VS.85%29.aspx
449
509
def _get_remote_url(self, path):
450
510
path_parts = path.split(os.path.sep)
451
511
if os.path.isabs(path):
452
512
if path_parts[:len(self._local_path_parts)] != \
453
self._local_path_parts:
513
self._local_path_parts:
454
514
raise BadWebserverPath(path, self.test_dir)
455
515
remote_path = '/'.join(path_parts[len(self._local_path_parts):])
475
535
or isinstance(backing_transport_server,
476
536
test_server.LocalURLServer)):
477
537
raise AssertionError(
478
"HTTPServer currently assumes local transport, got %s" %
538
"HTTPServer currently assumes local transport, got %s" % \
479
539
backing_transport_server)
480
self._home_dir = osutils.getcwd()
540
self._home_dir = os.getcwdu()
481
541
self._local_path_parts = self._home_dir.split(os.path.sep)
542
self._http_base_url = None
544
# Create the server thread
545
self._http_starting = threading.Lock()
546
self._http_starting.acquire()
547
self._http_thread = threading.Thread(target=self._http_start)
548
self._http_thread.setDaemon(True)
549
self._http_exception = None
550
self._http_thread.start()
552
# Wait for the server thread to start (i.e release the lock)
553
self._http_starting.acquire()
555
if self._http_exception is not None:
556
# Something went wrong during server start
557
exc_class, exc_value, exc_tb = self._http_exception
558
raise exc_class, exc_value, exc_tb
559
self._http_starting.release()
484
super(HttpServer, self).start_server()
485
self._http_base_url = '%s://%s:%s/' % (
486
self._url_protocol, self.host, self.port)
562
def stop_server(self):
563
self._httpd.stop_server()
564
self._http_running = False
565
# We don't need to 'self._http_thread.join()' here since the thread is
566
# a daemonic one and will be garbage collected anyway. Joining just
567
# slows us down for no added benefit.
488
569
def get_url(self):
489
"""See breezy.transport.Server.get_url."""
570
"""See bzrlib.transport.Server.get_url."""
490
571
return self._get_remote_url(self._home_dir)
492
573
def get_bogus_url(self):
493
"""See breezy.transport.Server.get_bogus_url."""
574
"""See bzrlib.transport.Server.get_bogus_url."""
494
575
# this is chosen to try to prevent trouble with proxies, weird dns,
496
577
return self._url_protocol + '://127.0.0.1:1/'
580
class HttpServer_urllib(HttpServer):
581
"""Subclass of HttpServer that gives http+urllib urls.
583
This is for use in testing: connections to this server will always go
584
through urllib where possible.
587
# urls returned by this server should require the urllib client impl
588
_url_protocol = 'http+urllib'
591
class HttpServer_PyCurl(HttpServer):
592
"""Subclass of HttpServer that gives http+pycurl urls.
594
This is for use in testing: connections to this server will always go
595
through pycurl where possible.
598
# We don't care about checking the pycurl availability as
599
# this server will be required only when pycurl is present
601
# urls returned by this server should require the pycurl client impl
602
_url_protocol = 'http+pycurl'