264
289
ranges = self.offsets_to_ranges(offsets)
265
290
mutter('http readv of %s collapsed %s offsets => %s',
266
291
relpath, len(offsets), ranges)
267
code, f = self._get(relpath, ranges)
297
code, f = self._get(relpath, ranges)
298
except (errors.InvalidRange, errors.ShortReadvError), e:
299
try_again, code, f = self._retry_get(relpath, ranges,
268
302
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,
306
f.seek(start, (start < 0) and 2 or 0)
310
if len(data) != size:
311
raise errors.ShortReadvError(relpath, start, size,
313
except (errors.InvalidRange, errors.ShortReadvError), e:
314
# Note that we replace 'f' here and that it
315
# may need cleaning one day before being
317
try_again, code, f = self._retry_get(relpath, ranges,
319
# After one or more tries, we get the data.
275
320
yield start, data
409
455
return self.__class__(self.abspath(offset), self)
457
def attempted_range_header(self, ranges, tail_amount):
458
"""Prepare a HTTP Range header at a level the server should accept"""
460
if self._range_hint == 'multi':
462
return self.range_header(ranges, tail_amount)
463
elif self._range_hint == 'single':
464
# Combine all the requested ranges into a single
467
start, ignored = ranges[0]
468
ignored, end = ranges[-1]
469
if tail_amount not in (0, None):
470
# Nothing we can do here to combine ranges
471
# with tail_amount, just returns None. The
472
# whole file should be downloaded.
475
return self.range_header([(start, end)], 0)
477
# Only tail_amount, requested, leave range_header
479
return self.range_header(ranges, tail_amount)
412
484
def range_header(ranges, tail_amount):
413
485
"""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
487
:param ranges: A list of byte ranges, (start, end).
488
:param tail_amount: The amount to get from the end of the file.
418
490
:return: HTTP range header string.
492
At least a non-empty ranges *or* a tail_amount must be
421
496
for start, end in ranges:
449
524
def _read_bytes(self, count):
450
525
return self._response_body.read(count)
452
527
def _finished_reading(self):
453
528
"""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())