39
39
TransportError, ConnectionError, InvalidURL)
40
40
from bzrlib.branch import Branch
41
41
from bzrlib.trace import mutter
42
from bzrlib.transport import Transport, register_transport, Server
42
from bzrlib.transport import (
43
49
from bzrlib.transport.http.response import (HttpMultipartRangeResponse,
45
51
from bzrlib.ui import ui_factory
122
class HttpTransportBase(Transport):
128
class HttpTransportBase(Transport, smart.SmartClientMedium):
123
129
"""Base class for http implementations.
125
131
Does URL parsing, etc, but not any network IO.
170
176
# TODO: Don't call this with an array - no magic interfaces
171
177
relpath_parts = relpath[:]
172
if len(relpath_parts) > 1:
173
# TODO: Check that the "within branch" part of the
174
# error messages below is relevant in all contexts
175
if relpath_parts[0] == '':
176
raise ValueError("path %r within branch %r seems to be absolute"
177
% (relpath, self._path))
178
# read only transports never manipulate directories
179
if self.is_readonly() and relpath_parts[-1] == '':
178
if relpath.startswith('/'):
181
# Except for the root, no trailing slashes are allowed
182
if len(relpath_parts) > 1 and relpath_parts[-1] == '':
180
183
raise ValueError("path %r within branch %r seems to be a directory"
181
184
% (relpath, self._path))
182
basepath = self._path.split('/')
183
if len(basepath) > 0 and basepath[-1] == '':
184
basepath = basepath[:-1]
185
basepath = self._path.split('/')
186
if len(basepath) > 0 and basepath[-1] == '':
187
basepath = basepath[:-1]
185
189
for p in relpath_parts:
187
191
if len(basepath) == 0:
241
245
raise NotImplementedError(self._get)
247
def get_request(self):
248
return SmartClientHTTPMediumRequest(self)
250
def get_smart_medium(self):
251
"""See Transport.get_smart_medium.
253
HttpTransportBase directly implements the minimal interface of
254
SmartMediumClient, so this returns self.
243
258
def readv(self, relpath, offsets):
244
259
"""Get parts of the file at the given relative path.
304
def _post(self, body_bytes):
305
"""POST body_bytes to .bzr/smart on this transport.
307
:returns: (response code, response body file-like object).
309
# TODO: Requiring all the body_bytes to be available at the beginning of
310
# the POST may require large client buffers. It would be nice to have
311
# an interface that allows streaming via POST when possible (and
312
# degrades to a local buffer when not).
313
raise NotImplementedError(self._post)
287
315
def put_file(self, relpath, f, mode=None):
288
316
"""Copy the file-like object into the location.
372
400
def clone(self, offset=None):
373
401
"""Return a new HttpTransportBase with root at self.base + offset
374
For now HttpTransportBase does not actually connect, so just return
375
a new HttpTransportBase object.
403
We leave the daughter classes take advantage of the hint
404
that it's a cloning not a raw creation.
377
406
if offset is None:
378
return self.__class__(self.base)
407
return self.__class__(self.base, self)
380
return self.__class__(self.abspath(offset))
409
return self.__class__(self.abspath(offset), self)
383
412
def range_header(ranges, tail_amount):
398
427
return ','.join(strings)
429
def send_http_smart_request(self, bytes):
430
code, body_filelike = self._post(bytes)
431
assert code == 200, 'unexpected HTTP response code %r' % (code,)
435
class SmartClientHTTPMediumRequest(smart.SmartClientMediumRequest):
436
"""A SmartClientMediumRequest that works with an HTTP medium."""
438
def __init__(self, medium):
439
smart.SmartClientMediumRequest.__init__(self, medium)
442
def _accept_bytes(self, bytes):
443
self._buffer += bytes
445
def _finished_writing(self):
446
data = self._medium.send_http_smart_request(self._buffer)
447
self._response_body = data
449
def _read_bytes(self, count):
450
return self._response_body.read(count)
452
def _finished_reading(self):
453
"""See SmartClientMediumRequest._finished_reading."""
401
457
#---------------- test server facilities ----------------
402
458
# TODO: load these only when running tests
449
505
if not self.parse_request(): # An error code has been sent, just exit
451
507
mname = 'do_' + self.command
452
if not hasattr(self, mname):
508
if getattr(self, mname, None) is None:
453
509
self.send_error(501, "Unsupported method (%r)" % self.command)
455
511
method = getattr(self, mname)
502
558
Server.__init__(self)
503
559
self.request_handler = request_handler
505
def _http_start(self):
507
httpd = TestingHTTPServer(('localhost', 0),
561
def _get_httpd(self):
562
return TestingHTTPServer(('localhost', 0),
508
563
self.request_handler,
566
def _http_start(self):
567
httpd = self._get_httpd()
510
568
host, port = httpd.socket.getsockname()
511
569
self._http_base_url = '%s://localhost:%s/' % (self._url_protocol, port)
512
570
self._http_starting.release()
571
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())