266
262
relpath, len(offsets), ranges)
267
263
code, f = self._get(relpath, ranges)
268
264
for start, size in offsets:
269
f.seek(start, (start < 0) and 2 or 0)
272
if len(data) != size:
273
raise errors.ShortReadvError(relpath, start, size,
268
f.seek(start, (start < 0) and 2 or 0)
272
if len(data) != size:
273
raise errors.ShortReadvError(relpath, start, size,
275
except (errors.InvalidRange, errors.ShortReadvError):
276
# The server does not gives us enough data or
277
# bogus-looking result, let's try again with
278
# a simpler request if possible.
279
if self._range_hint == 'multi':
280
self._range_hint = 'single'
281
mutter('Retry %s with single range request' % relpath)
283
elif self._range_hint == 'single':
284
self._range_hint = None
285
mutter('Retry %s without ranges' % relpath)
288
# Note that since the offsets and the
289
# ranges may not be in the same order we
290
# dont't try to calculate a restricted
291
# single range encompassing unprocessed
292
# offsets. Note that we replace 'f' here
293
# and that it may need cleaning one day
294
# before being thrown that way.
295
code, f = self._get(relpath, ranges)
297
# We tried all the tricks, nothing worked
275
300
yield start, data
409
435
return self.__class__(self.abspath(offset), self)
437
def attempted_range_header(self, ranges, tail_amount):
438
"""Prepare a HTTP Range header at a level the server should accept"""
440
if self._range_hint == 'multi':
442
return self.range_header(ranges, tail_amount)
443
elif self._range_hint == 'single':
444
# Combine all the requested ranges into a single
447
start, ignored = ranges[0]
448
ignored, end = ranges[-1]
449
if tail_amount not in (0, None):
450
# Nothing we can do here to combine ranges
451
# with tail_amount, just returns None. The
452
# whole file should be downloaded.
455
return self.range_header([(start, end)], 0)
457
# Only tail_amount, requested, leave range_header
459
return self.range_header(ranges, tail_amount)
412
464
def range_header(ranges, tail_amount):
413
465
"""Turn a list of bytes ranges into a HTTP Range header value.
415
:param offsets: A list of byte ranges, (start, end). An empty list
467
:param ranges: A list of byte ranges, (start, end).
468
:param tail_amount: The amount to get from the end of the file.
418
470
:return: HTTP range header string.
472
At least a non-empty ranges *or* a tail_amount must be
421
476
for start, end in ranges:
449
504
def _read_bytes(self, count):
450
505
return self._response_body.read(count)
452
507
def _finished_reading(self):
453
508
"""See SmartClientMediumRequest._finished_reading."""
457
#---------------- test server facilities ----------------
458
# TODO: load these only when running tests
461
class WebserverNotAvailable(Exception):
465
class BadWebserverPath(ValueError):
467
return 'path %s is not in %s' % self.args
470
class TestingHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
472
def log_message(self, format, *args):
473
self.server.test_case.log('webserver - %s - - [%s] %s "%s" "%s"',
474
self.address_string(),
475
self.log_date_time_string(),
477
self.headers.get('referer', '-'),
478
self.headers.get('user-agent', '-'))
480
def handle_one_request(self):
481
"""Handle a single HTTP request.
483
You normally don't need to override this method; see the class
484
__doc__ string for information on how to handle specific HTTP
485
commands such as GET and POST.
488
for i in xrange(1,11): # Don't try more than 10 times
490
self.raw_requestline = self.rfile.readline()
491
except socket.error, e:
492
if e.args[0] in (errno.EAGAIN, errno.EWOULDBLOCK):
493
# omitted for now because some tests look at the log of
494
# the server and expect to see no errors. see recent
495
# email thread. -- mbp 20051021.
496
## self.log_message('EAGAIN (%d) while reading from raw_requestline' % i)
502
if not self.raw_requestline:
503
self.close_connection = 1
505
if not self.parse_request(): # An error code has been sent, just exit
507
mname = 'do_' + self.command
508
if getattr(self, mname, None) is None:
509
self.send_error(501, "Unsupported method (%r)" % self.command)
511
method = getattr(self, mname)
514
if sys.platform == 'win32':
515
# On win32 you cannot access non-ascii filenames without
516
# decoding them into unicode first.
517
# However, under Linux, you can access bytestream paths
518
# without any problems. If this function was always active
519
# it would probably break tests when LANG=C was set
520
def translate_path(self, path):
521
"""Translate a /-separated PATH to the local filename syntax.
523
For bzr, all url paths are considered to be utf8 paths.
524
On Linux, you can access these paths directly over the bytestream
525
request, but on win32, you must decode them, and access them
528
# abandon query parameters
529
path = urlparse.urlparse(path)[2]
530
path = posixpath.normpath(urllib.unquote(path))
531
path = path.decode('utf-8')
532
words = path.split('/')
533
words = filter(None, words)
536
drive, word = os.path.splitdrive(word)
537
head, word = os.path.split(word)
538
if word in (os.curdir, os.pardir): continue
539
path = os.path.join(path, word)
543
class TestingHTTPServer(BaseHTTPServer.HTTPServer):
544
def __init__(self, server_address, RequestHandlerClass, test_case):
545
BaseHTTPServer.HTTPServer.__init__(self, server_address,
547
self.test_case = test_case
550
class HttpServer(Server):
551
"""A test server for http transports."""
553
# used to form the url that connects to this server
554
_url_protocol = 'http'
556
# Subclasses can provide a specific request handler
557
def __init__(self, request_handler=TestingHTTPRequestHandler):
558
Server.__init__(self)
559
self.request_handler = request_handler
561
def _get_httpd(self):
562
return TestingHTTPServer(('localhost', 0),
563
self.request_handler,
566
def _http_start(self):
567
httpd = self._get_httpd()
568
host, port = httpd.socket.getsockname()
569
self._http_base_url = '%s://localhost:%s/' % (self._url_protocol, port)
570
self._http_starting.release()
571
httpd.socket.settimeout(0.1)
573
while self._http_running:
575
httpd.handle_request()
576
except socket.timeout:
579
def _get_remote_url(self, path):
580
path_parts = path.split(os.path.sep)
581
if os.path.isabs(path):
582
if path_parts[:len(self._local_path_parts)] != \
583
self._local_path_parts:
584
raise BadWebserverPath(path, self.test_dir)
585
remote_path = '/'.join(path_parts[len(self._local_path_parts):])
587
remote_path = '/'.join(path_parts)
589
self._http_starting.acquire()
590
self._http_starting.release()
591
return self._http_base_url + remote_path
593
def log(self, format, *args):
594
"""Capture Server log output."""
595
self.logs.append(format % args)
598
"""See bzrlib.transport.Server.setUp."""
599
self._home_dir = os.getcwdu()
600
self._local_path_parts = self._home_dir.split(os.path.sep)
601
self._http_starting = threading.Lock()
602
self._http_starting.acquire()
603
self._http_running = True
604
self._http_base_url = None
605
self._http_thread = threading.Thread(target=self._http_start)
606
self._http_thread.setDaemon(True)
607
self._http_thread.start()
608
self._http_proxy = os.environ.get("http_proxy")
609
if self._http_proxy is not None:
610
del os.environ["http_proxy"]
614
"""See bzrlib.transport.Server.tearDown."""
615
self._http_running = False
616
self._http_thread.join()
617
if self._http_proxy is not None:
619
os.environ["http_proxy"] = self._http_proxy
622
"""See bzrlib.transport.Server.get_url."""
623
return self._get_remote_url(self._home_dir)
625
def get_bogus_url(self):
626
"""See bzrlib.transport.Server.get_bogus_url."""
627
# this is chosen to try to prevent trouble with proxies, weird dns,
629
return 'http://127.0.0.1:1/'
632
class HTTPServerWithSmarts(HttpServer):
633
"""HTTPServerWithSmarts extends the HttpServer with POST methods that will
634
trigger a smart server to execute with a transport rooted at the rootdir of
639
HttpServer.__init__(self, SmartRequestHandler)
642
class SmartRequestHandler(TestingHTTPRequestHandler):
643
"""Extend TestingHTTPRequestHandler to support smart client POSTs."""
646
"""Hand the request off to a smart server instance."""
647
self.send_response(200)
648
self.send_header("Content-type", "application/octet-stream")
649
transport = get_transport(self.server.test_case._home_dir)
650
# TODO: We might like to support streaming responses. 1.0 allows no
651
# Content-length in this case, so for integrity we should perform our
652
# own chunking within the stream.
653
# 1.1 allows chunked responses, and in this case we could chunk using
654
# the HTTP chunking as this will allow HTTP persistence safely, even if
655
# we have to stop early due to error, but we would also have to use the
656
# HTTP trailer facility which may not be widely available.
657
out_buffer = StringIO()
658
smart_protocol_request = smart.SmartServerRequestProtocolOne(
659
transport, out_buffer.write)
660
# if this fails, we should return 400 bad request, but failure is
661
# failure for now - RBC 20060919
662
data_length = int(self.headers['Content-Length'])
663
# Perhaps there should be a SmartServerHTTPMedium that takes care of
664
# feeding the bytes in the http request to the smart_protocol_request,
665
# but for now it's simpler to just feed the bytes directly.
666
smart_protocol_request.accept_bytes(self.rfile.read(data_length))
667
assert smart_protocol_request.next_read_size() == 0, (
668
"not finished reading, but all data sent to protocol.")
669
self.send_header("Content-Length", str(len(out_buffer.getvalue())))
671
self.wfile.write(out_buffer.getvalue())