~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

[merge] bzr.dev 2255, resolve conflicts, update copyrights

Show diffs side-by-side

added added

removed removed

Lines of Context:
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 errno
18
 
import urllib, urllib2
19
 
import errno
20
 
from StringIO import StringIO
 
17
from cStringIO import StringIO
21
18
 
22
 
import bzrlib  # for the version
23
 
from bzrlib.errors import (TransportNotPossible, NoSuchFile, BzrError,
24
 
                           TransportError, ConnectionError)
 
19
from bzrlib import ui
 
20
from bzrlib.errors import NoSuchFile
25
21
from bzrlib.trace import mutter
26
22
from bzrlib.transport import register_urlparse_netloc_protocol
27
 
from bzrlib.transport.http import (HttpTransportBase, HttpServer,
28
 
                                   extract_auth, response)
 
23
from bzrlib.transport.http import HttpTransportBase
 
24
# TODO: handle_response should be integrated into the _urllib2_wrappers
 
25
from bzrlib.transport.http.response import handle_response
 
26
from bzrlib.transport.http._urllib2_wrappers import (
 
27
    Opener,
 
28
    Request,
 
29
    )
 
30
 
29
31
 
30
32
register_urlparse_netloc_protocol('http+urllib')
31
33
 
32
34
 
33
 
class Request(urllib2.Request):
34
 
    """Request object for urllib2 that allows the method to be overridden."""
35
 
 
36
 
    method = None
37
 
 
38
 
    def get_method(self):
39
 
        if self.method is not None:
40
 
            return self.method
41
 
        else:
42
 
            return urllib2.Request.get_method(self)
43
 
 
44
 
 
45
35
class HttpTransport_urllib(HttpTransportBase):
46
36
    """Python urllib transport for http and https."""
47
37
 
48
 
    # TODO: Implement pipelined versions of all of the *_multi() functions.
 
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
49
43
 
50
44
    def __init__(self, base, from_transport=None):
51
45
        """Set the base path where files will be stored."""
52
 
        super(HttpTransport_urllib, self).__init__(base)
53
 
        # HttpTransport_urllib doesn't maintain any per-transport state yet
54
 
        # so nothing to do with from_transport
 
46
        super(HttpTransport_urllib, self).__init__(base, from_transport)
 
47
        if from_transport is not None:
 
48
            self._connection = from_transport._connection
 
49
            self._user = from_transport._user
 
50
            self._password = from_transport._password
 
51
            self._opener = from_transport._opener
 
52
        else:
 
53
            self._connection = None
 
54
            self._user = None
 
55
            self._password = None
 
56
            self._opener = self._opener_class()
 
57
 
 
58
    def ask_password(self, request):
 
59
        """Ask for a password if none is already provided in the request"""
 
60
        # TODO: jam 20060915 There should be a test that asserts we ask 
 
61
        #       for a password at the right time.
 
62
        if request.password is None:
 
63
            # We can't predict realm, let's try None, we'll get a
 
64
            # 401 if we are wrong anyway
 
65
            realm = None
 
66
            host = request.get_host()
 
67
            password_manager = self._opener.password_manager
 
68
            # Query the password manager first
 
69
            user, password = password_manager.find_user_password(None, host)
 
70
            if user == request.user and password is not None:
 
71
                request.password = password
 
72
            else:
 
73
                # Ask the user if we MUST
 
74
                http_pass = 'HTTP %(user)s@%(host)s password'
 
75
                request.password = ui.ui_factory.get_password(prompt=http_pass,
 
76
                                                              user=request.user,
 
77
                                                              host=host)
 
78
                password_manager.add_password(None, host,
 
79
                                              request.user, request.password)
 
80
 
 
81
    def _perform(self, request):
 
82
        """Send the request to the server and handles common errors.
 
83
 
 
84
        :returns: urllib2 Response object
 
85
        """
 
86
        if self._connection is not None:
 
87
            # Give back shared info
 
88
            request.connection = self._connection
 
89
            if self._user is not None:
 
90
                request.user = self._user
 
91
                request.password = self._password
 
92
        elif request.user is not None:
 
93
            # We will issue our first request, time to ask for a
 
94
            # password if needed
 
95
            self.ask_password(request)
 
96
 
 
97
        mutter('%s: [%s]' % (request.method, request.get_full_url()))
 
98
        if self._debuglevel > 0:
 
99
            print 'perform: %s base: %s, url: %s' % (request.method, self.base,
 
100
                                                     request.get_full_url())
 
101
 
 
102
        response = self._opener.open(request)
 
103
        if self._connection is None:
 
104
            # Acquire connection when the first request is able
 
105
            # to connect to the server
 
106
            self._connection = request.connection
 
107
            self._user = request.user
 
108
            self._password = request.password
 
109
 
 
110
        if request.redirected_to is not None:
 
111
            # TODO: Update the transport so that subsequent
 
112
            # requests goes directly to the right host
 
113
            mutter('redirected from: %s to: %s' % (request.get_full_url(),
 
114
                                                   request.redirected_to))
 
115
 
 
116
        return response
55
117
 
56
118
    def _get(self, relpath, ranges, tail_amount=0):
57
 
        path = relpath
58
 
        try:
59
 
            path = self._real_abspath(relpath)
60
 
            resp = self._get_url_impl(path, method='GET', ranges=ranges,
61
 
                                      tail_amount=tail_amount)
62
 
            return resp.code, response.handle_response(path,
63
 
                                resp.code, resp.headers, resp)
64
 
        except urllib2.HTTPError, e:
65
 
            mutter('url error code: %s for has url: %r', e.code, path)
66
 
            if e.code == 404:
67
 
                raise NoSuchFile(path, extra=e)
68
 
            raise
69
 
        except (BzrError, IOError), e:
70
 
            if getattr(e, 'errno', None) is not None:
71
 
                mutter('io error: %s %s for has url: %r',
72
 
                    e.errno, errno.errorcode.get(e.errno), path)
73
 
                if e.errno == errno.ENOENT:
74
 
                    raise NoSuchFile(path, extra=e)
75
 
            raise ConnectionError(msg = "Error retrieving %s: %s"
76
 
                                  % (self.abspath(relpath), str(e)),
77
 
                                  orig_error=e)
78
 
 
79
 
    def _get_url_impl(self, url, method, ranges, tail_amount=0):
80
 
        """Actually pass get request into urllib
81
 
 
82
 
        :returns: urllib Response object
83
 
        """
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',
93
 
                           'bzr/%s (urllib)' % (bzrlib.__version__,))
 
119
        """See HttpTransport._get"""
 
120
 
 
121
        abspath = self._real_abspath(relpath)
 
122
        headers = {}
94
123
        if ranges or tail_amount:
95
 
            bytes = 'bytes=' + self.range_header(ranges, tail_amount)
96
 
            request.add_header('Range', bytes)
97
 
        response = opener.open(request)
98
 
        return response
 
124
            range_header = self.attempted_range_header(ranges, tail_amount)
 
125
            if range_header is not None:
 
126
                bytes = 'bytes=' + range_header
 
127
                headers = {'Range': bytes}
 
128
 
 
129
        request = Request('GET', abspath, None, headers)
 
130
        response = self._perform(request)
 
131
 
 
132
        code = response.code
 
133
        if code == 404: # not found
 
134
            self._connection.fake_close()
 
135
            raise NoSuchFile(abspath)
 
136
 
 
137
        data = handle_response(abspath, code, response.headers, response)
 
138
        # Close response to free the httplib.HTTPConnection pipeline
 
139
        self._connection.fake_close()
 
140
        return code, data
 
141
 
 
142
    def _post(self, body_bytes):
 
143
        abspath = self._real_abspath('.bzr/smart')
 
144
        response = self._perform(Request('POST', abspath, body_bytes))
 
145
        code = response.code
 
146
        data = handle_response(abspath, code, response.headers, response)
 
147
        # Close response to free the httplib.HTTPConnection pipeline
 
148
        self._connection.fake_close()
 
149
        return code, data
99
150
 
100
151
    def should_cache(self):
101
152
        """Return True if the data pulled across should be cached locally.
102
153
        """
103
154
        return True
104
155
 
 
156
    def _head(self, relpath):
 
157
        """Request the HEAD of a file.
 
158
 
 
159
        Performs the request and leaves callers handle the results.
 
160
        """
 
161
        abspath = self._real_abspath(relpath)
 
162
        request = Request('HEAD', abspath)
 
163
        response = self._perform(request)
 
164
 
 
165
        self._connection.fake_close()
 
166
        return response
 
167
 
105
168
    def has(self, relpath):
106
169
        """Does the target location exist?
107
170
        """
108
 
        abspath = self._real_abspath(relpath)
109
 
        try:
110
 
            f = self._get_url_impl(abspath, 'HEAD', [])
111
 
            # Without the read and then close()
112
 
            # we tend to have busy sockets.
113
 
            f.read()
114
 
            f.close()
 
171
        response = self._head(relpath)
 
172
 
 
173
        code = response.code
 
174
        # FIXME: 302 MAY have been already processed by the
 
175
        # redirection handler
 
176
        if code in (200, 302): # "ok", "found"
115
177
            return True
116
 
        except urllib2.HTTPError, e:
117
 
            mutter('url error code: %s, for has url: %r', e.code, abspath)
118
 
            if e.code == 404:
119
 
                return False
120
 
            raise
121
 
        except urllib2.URLError, e:
122
 
            mutter('url error: %s, for has url: %r', e.reason, abspath)
123
 
            raise
124
 
        except IOError, e:
125
 
            mutter('io error: %s %s for has url: %r',
126
 
                e.errno, errno.errorcode.get(e.errno), abspath)
127
 
            if e.errno == errno.ENOENT:
128
 
                return False
129
 
            raise TransportError(orig_error=e)
130
 
 
131
 
    def copy_to(self, relpaths, other, mode=None, pb=None):
132
 
        """Copy a set of entries from self into another Transport.
133
 
 
134
 
        :param relpaths: A list/generator of entries to be copied.
135
 
 
136
 
        TODO: if other is LocalTransport, is it possible to
137
 
              do better than put(get())?
138
 
        """
139
 
        # At this point HttpTransport_urllib might be able to check and see if
140
 
        # the remote location is the same, and rather than download, and
141
 
        # then upload, it could just issue a remote copy_this command.
142
 
        if isinstance(other, HttpTransport_urllib):
143
 
            raise TransportNotPossible('http cannot be the target of copy_to()')
144
178
        else:
145
 
            return super(HttpTransport_urllib, self).copy_to(relpaths, other, mode=mode, pb=pb)
146
 
 
147
 
    def move(self, rel_from, rel_to):
148
 
        """Move the item at rel_from to the location at rel_to"""
149
 
        raise TransportNotPossible('http does not support move()')
150
 
 
151
 
    def delete(self, relpath):
152
 
        """Delete the item at relpath"""
153
 
        raise TransportNotPossible('http does not support delete()')
154
 
 
155
 
 
156
 
class HttpServer_urllib(HttpServer):
157
 
    """Subclass of HttpServer that gives http+urllib urls.
158
 
 
159
 
    This is for use in testing: connections to this server will always go
160
 
    through urllib where possible.
161
 
    """
162
 
 
163
 
    # urls returned by this server should require the urllib client impl
164
 
    _url_protocol = 'http+urllib'
 
179
            assert(code == 404, 'Only 200, 404 or may be 302 are correct')
 
180
            return False
165
181
 
166
182
 
167
183
def get_test_permutations():
168
184
    """Return the permutations to be used in testing."""
 
185
    from bzrlib.tests.HttpServer import HttpServer_urllib
169
186
    return [(HttpTransport_urllib, HttpServer_urllib),
170
187
            ]