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