90
93
# _unqualified_scheme: "http" or "https"
91
94
# _scheme: may have "+pycurl", etc
93
def __init__(self, base, _from_transport=None):
96
def __init__(self, base, _impl_name, _from_transport=None):
94
97
"""Set the base path where files will be stored."""
95
98
proto_match = re.match(r'^(https?)(\+\w+)?://', base)
96
99
if not proto_match:
97
100
raise AssertionError("not a http url: %r" % base)
98
101
self._unqualified_scheme = proto_match.group(1)
99
impl_name = proto_match.group(2)
101
impl_name = impl_name[1:]
102
self._impl_name = impl_name
102
self._impl_name = _impl_name
103
103
super(HttpTransportBase, self).__init__(base,
104
104
_from_transport=_from_transport)
105
106
# range hint is handled dynamically throughout the life
106
107
# of the transport object. We start by trying multi-range
107
108
# requests and if the server returns bogus results, we
163
def get_request(self):
164
return SmartClientHTTPMediumRequest(self)
166
164
def get_smart_medium(self):
167
"""See Transport.get_smart_medium.
165
"""See Transport.get_smart_medium."""
166
if self._medium is None:
167
# Since medium holds some state (smart server probing at least), we
168
# need to keep it around. Note that this is needed because medium
169
# has the same 'base' attribute as the transport so it can't be
170
# shared between transports having different bases.
171
self._medium = SmartClientHTTPMedium(self)
169
HttpTransportBase directly implements the minimal interface of
170
SmartMediumClient, so this returns self.
174
175
def _degrade_range_hint(self, relpath, ranges, exc_info):
175
176
if self._range_hint == 'multi':
448
454
raise errors.TransportNotPossible('http does not support lock_write()')
450
def clone(self, offset=None):
451
"""Return a new HttpTransportBase with root at self.base + offset
453
We leave the daughter classes take advantage of the hint
454
that it's a cloning not a raw creation.
457
return self.__class__(self.base, self)
459
return self.__class__(self.abspath(offset), self)
461
456
def _attempted_range_header(self, offsets, tail_amount):
462
457
"""Prepare a HTTP Range header at a level the server should accept.
514
509
return ','.join(strings)
516
def send_http_smart_request(self, bytes):
518
code, body_filelike = self._post(bytes)
520
raise InvalidHttpResponse(
521
self._remote_path('.bzr/smart'),
522
'Expected 200 response code, got %r' % (code,))
523
except errors.InvalidHttpResponse, e:
524
raise errors.SmartProtocolError(str(e))
511
def _redirected_to(self, source, target):
512
"""Returns a transport suitable to re-issue a redirected request.
514
:param source: The source url as returned by the server.
515
:param target: The target url as returned by the server.
517
The redirection can be handled only if the relpath involved is not
518
renamed by the redirection.
520
:returns: A transport or None.
522
def relpath(abspath):
523
"""Returns the path relative to our base.
525
The constraints are weaker than the real relpath method because the
526
abspath is coming from the server and may slightly differ from our
527
base. We don't check the scheme, host, port, user, password parts,
528
relying on the caller to give us a proper url (i.e. one returned by
529
the server mirroring the one we sent).
534
path) = self._split_url(abspath)
536
return path[pl:].strip('/')
538
relpath = relpath(source)
539
if not target.endswith(relpath):
540
# The final part of the url has been renamed, we can't handle the
547
path) = self._split_url(target)
548
# Recalculate base path. This is needed to ensure that when the
549
# redirected tranport will be used to re-try whatever request was
550
# redirected, we end up with the same url
551
base_path = path[:-len(relpath)]
552
if scheme in ('http', 'https'):
553
# Same protocol family (i.e. http[s]), we will preserve the same
554
# http client implementation when a redirection occurs from one to
555
# the other (otherwise users may be surprised that bzr switches
556
# from one implementation to the other, and devs may suffer
558
if (scheme == self._unqualified_scheme
559
and host == self._host
560
and port == self._port
561
and (user is None or user == self._user)):
562
# If a user is specified, it should match, we don't care about
563
# passwords, wrong passwords will be rejected anyway.
564
new_transport = self.clone(base_path)
566
# Rebuild the url preserving the scheme qualification and the
567
# credentials (if they don't apply, the redirected to server
568
# will tell us, but if they do apply, we avoid prompting the
570
redir_scheme = scheme + '+' + self._impl_name
571
new_url = self._unsplit_url(redir_scheme,
572
self._user, self._password,
575
new_transport = get_transport(new_url)
577
# Redirected to a different protocol
578
new_url = self._unsplit_url(scheme,
582
new_transport = get_transport(new_url)
586
# TODO: May be better located in smart/medium.py with the other
587
# SmartMedium classes
588
class SmartClientHTTPMedium(medium.SmartClientMedium):
590
def __init__(self, http_transport):
591
super(SmartClientHTTPMedium, self).__init__(http_transport.base)
592
# We don't want to create a circular reference between the http
593
# transport and its associated medium. Since the transport will live
594
# longer than the medium, the medium keep only a weak reference to its
596
self._http_transport_ref = weakref.ref(http_transport)
598
def get_request(self):
599
return SmartClientHTTPMediumRequest(self)
527
601
def should_probe(self):
536
610
rel_url = urlutils.relative_url(self.base, transport_base)
537
611
return urllib.unquote(rel_url)
613
def send_http_smart_request(self, bytes):
615
# Get back the http_transport hold by the weak reference
616
t = self._http_transport_ref()
617
code, body_filelike = t._post(bytes)
619
raise InvalidHttpResponse(
620
t._remote_path('.bzr/smart'),
621
'Expected 200 response code, got %r' % (code,))
622
except errors.InvalidHttpResponse, e:
623
raise errors.SmartProtocolError(str(e))
627
# TODO: May be better located in smart/medium.py with the other
628
# SmartMediumRequest classes
540
629
class SmartClientHTTPMediumRequest(medium.SmartClientMediumRequest):
541
630
"""A SmartClientMediumRequest that works with an HTTP medium."""