/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/transport/http/__init__.py

  • Committer: Ian Clatworthy
  • Date: 2008-12-15 06:18:29 UTC
  • mfrom: (3905 +trunk)
  • mto: (3586.1.23 views-ui)
  • mto: This revision was merged to the branch mainline in revision 4030.
  • Revision ID: ian.clatworthy@canonical.com-20081215061829-c8qwa93g71u9fsh5
merge bzr.dev 3905

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
import urlparse
26
26
import urllib
27
27
import sys
 
28
import weakref
28
29
 
29
30
from bzrlib import (
 
31
    debug,
30
32
    errors,
31
33
    ui,
32
34
    urlutils,
39
41
from bzrlib.transport import (
40
42
    ConnectedTransport,
41
43
    _CoalescedOffset,
 
44
    get_transport,
42
45
    Transport,
43
46
    )
44
47
 
78
81
    return url
79
82
 
80
83
 
81
 
class HttpTransportBase(ConnectedTransport, medium.SmartClientMedium):
 
84
class HttpTransportBase(ConnectedTransport):
82
85
    """Base class for http implementations.
83
86
 
84
87
    Does URL parsing, etc, but not any network IO.
90
93
    # _unqualified_scheme: "http" or "https"
91
94
    # _scheme: may have "+pycurl", etc
92
95
 
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)
100
 
        if impl_name:
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
        self._medium = None
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
160
161
                    path=self._path)
161
162
        return auth
162
163
 
163
 
    def get_request(self):
164
 
        return SmartClientHTTPMediumRequest(self)
165
 
 
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)
 
172
        return self._medium
168
173
 
169
 
        HttpTransportBase directly implements the minimal interface of
170
 
        SmartMediumClient, so this returns self.
171
 
        """
172
 
        return self
173
174
 
174
175
    def _degrade_range_hint(self, relpath, ranges, exc_info):
175
176
        if self._range_hint == 'multi':
230
231
 
231
232
            # Turn it into a list, we will iterate it several times
232
233
            coalesced = list(coalesced)
233
 
            mutter('http readv of %s  offsets => %s collapsed %s',
 
234
            if 'http' in debug.debug_flags:
 
235
                mutter('http readv of %s  offsets => %s collapsed %s',
234
236
                    relpath, len(offsets), len(coalesced))
235
237
 
236
238
            # Cache the data read, but only until it's been used
410
412
 
411
413
    def external_url(self):
412
414
        """See bzrlib.transport.Transport.external_url."""
413
 
        # HTTP URL's are externally usable.
414
 
        return self.base
 
415
        # HTTP URL's are externally usable as long as they don't mention their
 
416
        # implementation qualifier
 
417
        return self._unsplit_url(self._unqualified_scheme,
 
418
                                 self._user, self._password,
 
419
                                 self._host, self._port,
 
420
                                 self._path)
415
421
 
416
422
    def is_readonly(self):
417
423
        """See Transport.is_readonly."""
447
453
        """
448
454
        raise errors.TransportNotPossible('http does not support lock_write()')
449
455
 
450
 
    def clone(self, offset=None):
451
 
        """Return a new HttpTransportBase with root at self.base + offset
452
 
 
453
 
        We leave the daughter classes take advantage of the hint
454
 
        that it's a cloning not a raw creation.
455
 
        """
456
 
        if offset is None:
457
 
            return self.__class__(self.base, self)
458
 
        else:
459
 
            return self.__class__(self.abspath(offset), self)
460
 
 
461
456
    def _attempted_range_header(self, offsets, tail_amount):
462
457
        """Prepare a HTTP Range header at a level the server should accept.
463
458
 
513
508
 
514
509
        return ','.join(strings)
515
510
 
516
 
    def send_http_smart_request(self, bytes):
517
 
        try:
518
 
            code, body_filelike = self._post(bytes)
519
 
            if code != 200:
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))
525
 
        return body_filelike
 
511
    def _redirected_to(self, source, target):
 
512
        """Returns a transport suitable to re-issue a redirected request.
 
513
 
 
514
        :param source: The source url as returned by the server.
 
515
        :param target: The target url as returned by the server.
 
516
 
 
517
        The redirection can be handled only if the relpath involved is not
 
518
        renamed by the redirection.
 
519
 
 
520
        :returns: A transport or None.
 
521
        """
 
522
        def relpath(abspath):
 
523
            """Returns the path relative to our base.
 
524
 
 
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).
 
530
            """
 
531
            (scheme,
 
532
             user, password,
 
533
             host, port,
 
534
             path) = self._split_url(abspath)
 
535
            pl = len(self._path)
 
536
            return path[pl:].strip('/')
 
537
 
 
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
 
541
            # redirection.
 
542
            return None
 
543
        new_transport = None
 
544
        (scheme,
 
545
         user, password,
 
546
         host, port,
 
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
 
557
            # debugging it).
 
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)
 
565
            else:
 
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
 
569
                # user)
 
570
                redir_scheme = scheme + '+' + self._impl_name
 
571
                new_url = self._unsplit_url(redir_scheme,
 
572
                                            self._user, self._password,
 
573
                                            host, port,
 
574
                                            base_path)
 
575
                new_transport = get_transport(new_url)
 
576
        else:
 
577
            # Redirected to a different protocol
 
578
            new_url = self._unsplit_url(scheme,
 
579
                                        user, password,
 
580
                                        host, port,
 
581
                                        base_path)
 
582
            new_transport = get_transport(new_url)
 
583
        return new_transport
 
584
 
 
585
 
 
586
# TODO: May be better located in smart/medium.py with the other
 
587
# SmartMedium classes
 
588
class SmartClientHTTPMedium(medium.SmartClientMedium):
 
589
 
 
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
 
595
        # transport.
 
596
        self._http_transport_ref = weakref.ref(http_transport)
 
597
 
 
598
    def get_request(self):
 
599
        return SmartClientHTTPMediumRequest(self)
526
600
 
527
601
    def should_probe(self):
528
602
        return True
536
610
        rel_url = urlutils.relative_url(self.base, transport_base)
537
611
        return urllib.unquote(rel_url)
538
612
 
539
 
 
 
613
    def send_http_smart_request(self, bytes):
 
614
        try:
 
615
            # Get back the http_transport hold by the weak reference
 
616
            t = self._http_transport_ref()
 
617
            code, body_filelike = t._post(bytes)
 
618
            if code != 200:
 
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))
 
624
        return body_filelike
 
625
 
 
626
 
 
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."""
542
631
 
555
644
        """See SmartClientMediumRequest._read_bytes."""
556
645
        return self._response_body.read(count)
557
646
 
 
647
    def _read_line(self):
 
648
        line, excess = medium._get_line(self._response_body.read)
 
649
        if excess != '':
 
650
            raise AssertionError(
 
651
                '_get_line returned excess bytes, but this mediumrequest '
 
652
                'cannot handle excess. (%r)' % (excess,))
 
653
        return line
 
654
 
558
655
    def _finished_reading(self):
559
656
        """See SmartClientMediumRequest._finished_reading."""
560
657
        pass