~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: mbp at sourcefrog
  • Date: 2006-11-21 01:08:52 UTC
  • mfrom: (2004.1.42 bzr.urllib.keepalive)
  • mto: This revision was merged to the branch mainline in revision 2146.
  • Revision ID: mbp@sourcefrog.net-20061121010852-838b560632acc36c
merge urllib keepalive etc

Show diffs side-by-side

added added

removed removed

Lines of Context:
26
26
import os
27
27
from cStringIO import StringIO
28
28
 
29
 
from bzrlib import errors
 
29
from bzrlib import (
 
30
    errors,
 
31
    __version__ as bzrlib_version,
 
32
    )
30
33
import bzrlib
31
 
from bzrlib.errors import (TransportNotPossible, NoSuchFile,
32
 
                           TransportError, ConnectionError,
 
34
from bzrlib.errors import (NoSuchFile,
 
35
                           ConnectionError,
33
36
                           DependencyNotPresent)
34
37
from bzrlib.trace import mutter
35
38
from bzrlib.transport import register_urlparse_netloc_protocol
36
 
from bzrlib.transport.http import (HttpTransportBase, HttpServer,
37
 
                                   _extract_headers,
38
 
                                   response, _pycurl_errors)
 
39
from bzrlib.transport.http import (
 
40
    _extract_headers,
 
41
    HttpTransportBase,
 
42
    _pycurl_errors,
 
43
    response,
 
44
    )
39
45
 
40
46
try:
41
47
    import pycurl
67
73
 
68
74
    PyCurl is a Python binding to the C "curl" multiprotocol client.
69
75
 
70
 
    This transport can be significantly faster than the builtin Python client. 
71
 
    Advantages include: DNS caching, connection keepalive, and ability to 
72
 
    set headers to allow caching.
 
76
    This transport can be significantly faster than the builtin
 
77
    Python client.  Advantages include: DNS caching.
73
78
    """
74
79
 
75
80
    def __init__(self, base, from_transport=None):
99
104
        # don't want the body - ie just do a HEAD request
100
105
        # This means "NO BODY" not 'nobody'
101
106
        curl.setopt(pycurl.NOBODY, 1)
 
107
        # In some erroneous cases, pycurl will emit text on
 
108
        # stdout if we don't catch it (see InvalidStatus tests
 
109
        # for one such occurrence).
 
110
        blackhole = StringIO()
 
111
        curl.setopt(pycurl.WRITEFUNCTION, blackhole.write)
102
112
        self._curl_perform(curl)
103
113
        code = curl.getinfo(pycurl.HTTP_CODE)
104
114
        if code == 404: # not found
107
117
            return True
108
118
        else:
109
119
            self._raise_curl_http_error(curl)
110
 
        
 
120
 
111
121
    def _get(self, relpath, ranges, tail_amount=0):
112
122
        # This just switches based on the type of request
113
123
        if ranges is not None or tail_amount not in (0, None):
114
124
            return self._get_ranged(relpath, ranges, tail_amount=tail_amount)
115
125
        else:
116
126
            return self._get_full(relpath)
117
 
    
 
127
 
118
128
    def _setup_get_request(self, curl, relpath):
119
129
        # Make sure we do a GET request. versions > 7.14.1 also set the
120
130
        # NO BODY flag, but we'll do it ourselves in case it is an older
156
166
        if code == 404:
157
167
            raise NoSuchFile(abspath)
158
168
        if code != 200:
159
 
            self._raise_curl_http_error(curl, 'expected 200 or 404 for full response.')
 
169
            self._raise_curl_http_error(
 
170
                curl, 'expected 200 or 404 for full response.')
160
171
 
161
172
        return code, data
162
173
 
173
184
        curl = self._range_curl
174
185
        abspath, data, header = self._setup_get_request(curl, relpath)
175
186
 
176
 
        curl.setopt(pycurl.RANGE, self.range_header(ranges, tail_amount))
 
187
        range_header = self.attempted_range_header(ranges, tail_amount)
 
188
        if range_header is None:
 
189
            # Forget ranges, the server can't handle them
 
190
            return self._get_full(relpath)
 
191
 
 
192
        curl.setopt(pycurl.RANGE, range_header)
177
193
        self._curl_perform(curl)
178
194
        data.seek(0)
179
195
 
202
218
    def _raise_curl_http_error(self, curl, info=None):
203
219
        code = curl.getinfo(pycurl.HTTP_CODE)
204
220
        url = curl.getinfo(pycurl.EFFECTIVE_URL)
205
 
        if info is None:
206
 
            msg = ''
 
221
        # Some error codes can be handled the same way for all
 
222
        # requests
 
223
        if code == 403:
 
224
            raise errors.TransportError(
 
225
                'Server refuses to fullfil the request for: %s' % url)
207
226
        else:
208
 
            msg = ': ' + info
209
 
        raise errors.InvalidHttpResponse(url, 'Unable to handle http code %d%s'
210
 
                                              % (code,msg))
 
227
            if info is None:
 
228
                msg = ''
 
229
            else:
 
230
                msg = ': ' + info
 
231
            raise errors.InvalidHttpResponse(
 
232
                url, 'Unable to handle http code %d%s' % (code,msg))
211
233
 
212
234
    def _set_curl_options(self, curl):
213
235
        """Set options for all requests"""
232
254
        try:
233
255
            curl.perform()
234
256
        except pycurl.error, e:
235
 
            # XXX: There seem to be no symbolic constants for these values.
236
257
            url = curl.getinfo(pycurl.EFFECTIVE_URL)
237
258
            mutter('got pycurl error: %s, %s, %s, url: %s ',
238
259
                    e[0], _pycurl_errors.errorcode[e[0]], e, url)
239
260
            if e[0] in (_pycurl_errors.CURLE_COULDNT_RESOLVE_HOST,
240
261
                        _pycurl_errors.CURLE_COULDNT_CONNECT,
 
262
                        _pycurl_errors.CURLE_GOT_NOTHING,
241
263
                        _pycurl_errors.CURLE_COULDNT_RESOLVE_PROXY):
242
264
                raise ConnectionError('curl connection error (%s)\non %s'
243
265
                              % (e[1], url))
246
268
            raise
247
269
 
248
270
 
249
 
class HttpServer_PyCurl(HttpServer):
250
 
    """Subclass of HttpServer that gives http+pycurl urls.
251
 
 
252
 
    This is for use in testing: connections to this server will always go
253
 
    through pycurl where possible.
254
 
    """
255
 
 
256
 
    # urls returned by this server should require the pycurl client impl
257
 
    _url_protocol = 'http+pycurl'
258
 
 
259
 
 
260
271
def get_test_permutations():
261
272
    """Return the permutations to be used in testing."""
 
273
    from bzrlib.tests.HttpServer import HttpServer_PyCurl
262
274
    return [(PyCurlTransport, HttpServer_PyCurl),
263
275
            ]