/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/_urllib.py

  • Committer: Robert Collins
  • Date: 2008-08-20 02:07:36 UTC
  • mfrom: (3640 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3682.
  • Revision ID: robertc@robertcollins.net-20080820020736-g2xe4921zzxtymle
Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
import urllib, urllib2
18
 
 
19
 
import bzrlib  # for the version
20
 
from bzrlib.errors import BzrError
21
 
from bzrlib.trace import mutter
22
 
from bzrlib.transport import register_urlparse_netloc_protocol
23
 
from bzrlib.transport.http import HttpTransportBase, extract_auth, HttpServer
24
 
from bzrlib.errors import (TransportNotPossible, NoSuchFile,
25
 
                           TransportError, ConnectionError)
26
 
 
27
 
 
28
 
register_urlparse_netloc_protocol('http+urllib')
29
 
 
30
 
 
31
 
class Request(urllib2.Request):
32
 
    """Request object for urllib2 that allows the method to be overridden."""
33
 
 
34
 
    method = None
35
 
 
36
 
    def get_method(self):
37
 
        if self.method is not None:
38
 
            return self.method
39
 
        else:
40
 
            return urllib2.Request.get_method(self)
41
 
 
42
 
 
43
 
class HttpTransport_urllib(HttpTransportBase):
44
 
    """Python urllib transport for http and https.
45
 
    """
46
 
 
47
 
    # TODO: Implement pipelined versions of all of the *_multi() functions.
48
 
 
49
 
    def __init__(self, base):
50
 
        """Set the base path where files will be stored."""
51
 
        super(HttpTransport_urllib, self).__init__(base)
52
 
 
53
 
    def _get(self, relpath, ranges):
54
 
        path = relpath
55
 
        try:
56
 
            path = self._real_abspath(relpath)
57
 
            response = self._get_url_impl(path, method='GET', ranges=ranges)
58
 
            return response.code, response
59
 
        except urllib2.HTTPError, e:
60
 
            mutter('url error code: %s for has url: %r', e.code, path)
61
 
            if e.code == 404:
62
 
                raise NoSuchFile(path, extra=e)
63
 
            raise
64
 
        except (BzrError, IOError), e:
65
 
            if hasattr(e, 'errno'):
66
 
                mutter('io error: %s %s for has url: %r',
67
 
                    e.errno, errno.errorcode.get(e.errno), path)
68
 
                if e.errno == errno.ENOENT:
69
 
                    raise NoSuchFile(path, extra=e)
70
 
            raise ConnectionError(msg = "Error retrieving %s: %s" 
71
 
                             % (self.abspath(relpath), str(e)),
72
 
                             orig_error=e)
73
 
 
74
 
    def _get_url_impl(self, url, method, ranges):
75
 
        """Actually pass get request into urllib
76
 
 
77
 
        :returns: urllib Response object
78
 
        """
79
 
        if ranges:
80
 
            range_string = ranges
81
 
        else:
82
 
            range_string = 'all'
83
 
        mutter("get_url %s [%s]" % (url, range_string))
84
 
        manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
85
 
        url = extract_auth(url, manager)
86
 
        auth_handler = urllib2.HTTPBasicAuthHandler(manager)
87
 
        opener = urllib2.build_opener(auth_handler)
88
 
        request = Request(url)
89
 
        request.method = method
90
 
        request.add_header('Pragma', 'no-cache')
91
 
        request.add_header('Cache-control', 'max-age=0')
92
 
        request.add_header('User-Agent', 'bzr/%s (urllib)' % bzrlib.__version__)
93
 
        if ranges:
94
 
            assert len(ranges) == 1
95
 
            request.add_header('Range', 'bytes=%d-%d' % ranges[0])
96
 
        response = opener.open(request)
97
 
        return response
98
 
 
99
 
    def should_cache(self):
100
 
        """Return True if the data pulled across should be cached locally.
101
 
        """
102
 
        return True
 
17
from cStringIO import StringIO
 
18
import urllib
 
19
import urlparse
 
20
 
 
21
from bzrlib import (
 
22
    errors,
 
23
    trace,
 
24
    urlutils,
 
25
    )
 
26
from bzrlib.transport import http
 
27
# TODO: handle_response should be integrated into the http/__init__.py
 
28
from bzrlib.transport.http.response import handle_response
 
29
from bzrlib.transport.http._urllib2_wrappers import (
 
30
    Opener,
 
31
    Request,
 
32
    )
 
33
 
 
34
 
 
35
class HttpTransport_urllib(http.HttpTransportBase):
 
36
    """Python urllib transport for http and https."""
 
37
 
 
38
    # In order to debug we have to issue our traces in sync with
 
39
    # httplib, which use print :(
 
40
    _debuglevel = 0
 
41
 
 
42
    _opener_class = Opener
 
43
 
 
44
    def __init__(self, base, _from_transport=None):
 
45
        super(HttpTransport_urllib, self).__init__(
 
46
            base, _from_transport=_from_transport)
 
47
        if _from_transport is not None:
 
48
            self._opener = _from_transport._opener
 
49
        else:
 
50
            self._opener = self._opener_class()
 
51
 
 
52
    def _perform(self, request):
 
53
        """Send the request to the server and handles common errors.
 
54
 
 
55
        :returns: urllib2 Response object
 
56
        """
 
57
        connection = self._get_connection()
 
58
        if connection is not None:
 
59
            # Give back shared info
 
60
            request.connection = connection
 
61
            (auth, proxy_auth) = self._get_credentials()
 
62
            # Clean the httplib.HTTPConnection pipeline in case the previous
 
63
            # request couldn't do it
 
64
            connection.cleanup_pipe()
 
65
        else:
 
66
            # First request, initialize credentials.
 
67
            # scheme and realm will be set by the _urllib2_wrappers.AuthHandler
 
68
            auth = self._create_auth()
 
69
            # Proxy initialization will be done by the first proxied request
 
70
            proxy_auth = dict()
 
71
        # Ensure authentication info is provided
 
72
        request.auth = auth
 
73
        request.proxy_auth = proxy_auth
 
74
 
 
75
        if self._debuglevel > 0:
 
76
            print 'perform: %s base: %s, url: %s' % (request.method, self.base,
 
77
                                                     request.get_full_url())
 
78
        response = self._opener.open(request)
 
79
        if self._get_connection() is not request.connection:
 
80
            # First connection or reconnection
 
81
            self._set_connection(request.connection,
 
82
                                 (request.auth, request.proxy_auth))
 
83
        else:
 
84
            # http may change the credentials while keeping the
 
85
            # connection opened
 
86
            self._update_credentials((request.auth, request.proxy_auth))
 
87
 
 
88
        code = response.code
 
89
        if request.follow_redirections is False \
 
90
                and code in (301, 302, 303, 307):
 
91
            raise errors.RedirectRequested(request.get_full_url(),
 
92
                                           request.redirected_to,
 
93
                                           is_permanent=(code == 301),
 
94
                                           qual_proto=self._scheme)
 
95
 
 
96
        if request.redirected_to is not None:
 
97
            trace.mutter('redirected from: %s to: %s' % (request.get_full_url(),
 
98
                                                         request.redirected_to))
 
99
 
 
100
        return response
 
101
 
 
102
    def _get(self, relpath, offsets, tail_amount=0):
 
103
        """See HttpTransport._get"""
 
104
 
 
105
        abspath = self._remote_path(relpath)
 
106
        headers = {}
 
107
        accepted_errors = [200, 404]
 
108
        if offsets or tail_amount:
 
109
            range_header = self._attempted_range_header(offsets, tail_amount)
 
110
            if range_header is not None:
 
111
                accepted_errors.append(206)
 
112
                accepted_errors.append(400)
 
113
                accepted_errors.append(416)
 
114
                bytes = 'bytes=' + range_header
 
115
                headers = {'Range': bytes}
 
116
 
 
117
        request = Request('GET', abspath, None, headers,
 
118
                          accepted_errors=accepted_errors)
 
119
        response = self._perform(request)
 
120
 
 
121
        code = response.code
 
122
        if code == 404: # not found
 
123
            raise errors.NoSuchFile(abspath)
 
124
        elif code in (400, 416):
 
125
            # We don't know which, but one of the ranges we specified was
 
126
            # wrong.
 
127
            raise errors.InvalidHttpRange(abspath, range_header,
 
128
                                          'Server return code %d' % code)
 
129
 
 
130
        data = handle_response(abspath, code, response.info(), response)
 
131
        return code, data
 
132
 
 
133
    def _post(self, body_bytes):
 
134
        abspath = self._remote_path('.bzr/smart')
 
135
        # We include 403 in accepted_errors so that send_http_smart_request can
 
136
        # handle a 403.  Otherwise a 403 causes an unhandled TransportError.
 
137
        response = self._perform(Request('POST', abspath, body_bytes,
 
138
                                         accepted_errors=[200, 403]))
 
139
        code = response.code
 
140
        data = handle_response(abspath, code, response.info(), response)
 
141
        return code, data
 
142
 
 
143
    def _head(self, relpath):
 
144
        """Request the HEAD of a file.
 
145
 
 
146
        Performs the request and leaves callers handle the results.
 
147
        """
 
148
        abspath = self._remote_path(relpath)
 
149
        request = Request('HEAD', abspath,
 
150
                          accepted_errors=[200, 404])
 
151
        response = self._perform(request)
 
152
 
 
153
        return response
103
154
 
104
155
    def has(self, relpath):
105
156
        """Does the target location exist?
106
157
        """
107
 
        abspath = self._real_abspath(relpath)
108
 
        try:
109
 
            f = self._get_url_impl(abspath, 'HEAD', [])
110
 
            # Without the read and then close()
111
 
            # we tend to have busy sockets.
112
 
            f.read()
113
 
            f.close()
 
158
        response = self._head(relpath)
 
159
 
 
160
        code = response.code
 
161
        if code == 200: # "ok",
114
162
            return True
115
 
        except urllib2.URLError, e:
116
 
            mutter('url error code: %s for has url: %r', e.code, abspath)
117
 
            if e.code == 404:
118
 
                return False
119
 
            raise
120
 
        except IOError, e:
121
 
            mutter('io error: %s %s for has url: %r',
122
 
                e.errno, errno.errorcode.get(e.errno), abspath)
123
 
            if e.errno == errno.ENOENT:
124
 
                return False
125
 
            raise TransportError(orig_error=e)
126
 
 
127
 
    def copy_to(self, relpaths, other, mode=None, pb=None):
128
 
        """Copy a set of entries from self into another Transport.
129
 
 
130
 
        :param relpaths: A list/generator of entries to be copied.
131
 
 
132
 
        TODO: if other is LocalTransport, is it possible to
133
 
              do better than put(get())?
134
 
        """
135
 
        # At this point HttpTransport_urllib might be able to check and see if
136
 
        # the remote location is the same, and rather than download, and
137
 
        # then upload, it could just issue a remote copy_this command.
138
 
        if isinstance(other, HttpTransport_urllib):
139
 
            raise TransportNotPossible('http cannot be the target of copy_to()')
140
163
        else:
141
 
            return super(HttpTransport_urllib, self).copy_to(relpaths, other, mode=mode, pb=pb)
142
 
 
143
 
    def move(self, rel_from, rel_to):
144
 
        """Move the item at rel_from to the location at rel_to"""
145
 
        raise TransportNotPossible('http does not support move()')
146
 
 
147
 
    def delete(self, relpath):
148
 
        """Delete the item at relpath"""
149
 
        raise TransportNotPossible('http does not support delete()')
150
 
 
151
 
 
152
 
class HttpServer_urllib(HttpServer):
153
 
    """Subclass of HttpServer that gives http+urllib urls.
154
 
 
155
 
    This is for use in testing: connections to this server will always go
156
 
    through urllib where possible.
157
 
    """
158
 
 
159
 
    # urls returned by this server should require the urllib client impl
160
 
    _url_protocol = 'http+urllib'
 
164
            return False
161
165
 
162
166
 
163
167
def get_test_permutations():
164
168
    """Return the permutations to be used in testing."""
 
169
    from bzrlib.tests.http_server import HttpServer_urllib
165
170
    return [(HttpTransport_urllib, HttpServer_urllib),
166
171
            ]