60
60
register_urlparse_netloc_protocol('http+pycurl')
63
class CurlResponse(object):
64
"""This just exists to make the curl response look like a urllib response."""
66
def __init__(self, curl_handle):
67
self._data = StringIO()
68
self._header = StringIO()
69
self._curl_handle = curl_handle
70
self._curl_handle.setopt(pycurl.WRITEFUNCTION, self._data.write)
71
self._curl_handle.setopt(pycurl.HEADERFUNCTION, self._header.write)
73
# Will be filled out by calling update()
77
# Overload some functions used elsewhere
78
self.read = self._data.read
79
self.seek = self._data.seek
82
"""Update self after the action has been performed."""
83
self.code = self._curl_handle.getinfo(pycurl.HTTP_CODE)
87
def _build_headers(self):
88
"""Parse the headers into RFC822 format"""
90
# TODO: jam 20060617 We probably don't need all of these
91
# headers, but this is what the 'curl' wrapper around
93
self._header.seek(0, 0)
94
url = self._curl_handle.getinfo(pycurl.EFFECTIVE_URL)
95
if url[:5] in ('http:', 'https:'):
96
self._header.readline()
97
m = mimetools.Message(self._header)
99
m = mimetools.Message(StringIO())
100
m['effective-url'] = url
101
m['http-code'] = str(self._curl_handle.getinfo(pycurl.HTTP_CODE))
102
# jam 20060617 These aren't used, so don't bother parsing them
103
# m['total-time'] = str(self._curl_handle.getinfo(pycurl.TOTAL_TIME))
104
# m['namelookup-time'] = str(self._curl_handle.getinfo(pycurl.NAMELOOKUP_TIME))
105
# m['connect-time'] = str(self._curl_handle.getinfo(pycurl.CONNECT_TIME))
106
# m['pretransfer-time'] = str(self._curl_handle.getinfo(pycurl.PRETRANSFER_TIME))
107
# m['redirect-time'] = str(self._curl_handle.getinfo(pycurl.REDIRECT_TIME))
108
# m['redirect-count'] = str(self._curl_handle.getinfo(pycurl.REDIRECT_COUNT))
109
# m['size-upload'] = str(self._curl_handle.getinfo(pycurl.SIZE_UPLOAD))
110
# m['size-download'] = str(self._curl_handle.getinfo(pycurl.SIZE_DOWNLOAD))
111
# m['speed-upload'] = str(self._curl_handle.getinfo(pycurl.SPEED_UPLOAD))
112
# m['header-size'] = str(self._curl_handle.getinfo(pycurl.HEADER_SIZE))
113
# m['request-size'] = str(self._curl_handle.getinfo(pycurl.REQUEST_SIZE))
114
# m['content-length-download'] = str(self._curl_handle.getinfo(pycurl.CONTENT_LENGTH_DOWNLOAD))
115
# m['content-length-upload'] = str(self._curl_handle.getinfo(pycurl.CONTENT_LENGTH_UPLOAD))
117
# Shouldn't this be 'Content-Type', I'm hoping that was already
118
# parsed by the mimetools section.
119
# m['content-type'] = (self._curl_handle.getinfo(pycurl.CONTENT_TYPE) or '').strip(';')
63
123
class PyCurlTransport(HttpTransportBase):
64
124
"""http client transport using pycurl
112
172
curl = self._range_curl
114
174
abspath = self._real_abspath(relpath)
116
header_sio = StringIO()
117
175
curl.setopt(pycurl.URL, abspath)
118
176
self._set_curl_options(curl)
119
curl.setopt(pycurl.WRITEFUNCTION, sio.write)
120
177
curl.setopt(pycurl.NOBODY, 0)
121
curl.setopt(pycurl.HEADERFUNCTION, header_sio.write)
179
response = CurlResponse(curl)
123
181
if ranges is not None:
124
assert len(ranges) == 1
125
# multiple ranges not supported yet because we can't decode the
127
curl.setopt(pycurl.RANGE, '%d-%d' % ranges[0])
182
curl.setopt(pycurl.RANGE, self._range_header(ranges))
128
183
self._curl_perform(curl)
129
info = self._get_header_info(header_sio, curl)
130
code = info['http-code']
132
raise NoSuchFile(abspath)
136
elif code == '206' and (ranges is not None):
185
if response.code == 0:
140
186
self._raise_curl_connection_error(curl)
142
self._raise_curl_http_error(curl)
144
def _get_header_info(self, header, curl_handle):
145
"""Parse the headers into RFC822 format"""
147
# TODO: jam 20060617 We probably don't need all of these
148
# headers, but this is what the 'curl' wrapper around
151
url = curl_handle.getinfo(pycurl.EFFECTIVE_URL)
152
if url[:5] in ('http:', 'https:'):
154
m = mimetools.Message(header)
156
m = mimetools.Message(StringIO())
157
m['effective-url'] = url
158
m['http-code'] = str(curl_handle.getinfo(pycurl.HTTP_CODE))
159
m['total-time'] = str(curl_handle.getinfo(pycurl.TOTAL_TIME))
160
m['namelookup-time'] = str(curl_handle.getinfo(pycurl.NAMELOOKUP_TIME))
161
m['connect-time'] = str(curl_handle.getinfo(pycurl.CONNECT_TIME))
162
m['pretransfer-time'] = str(curl_handle.getinfo(pycurl.PRETRANSFER_TIME))
163
m['redirect-time'] = str(curl_handle.getinfo(pycurl.REDIRECT_TIME))
164
m['redirect-count'] = str(curl_handle.getinfo(pycurl.REDIRECT_COUNT))
165
m['size-upload'] = str(curl_handle.getinfo(pycurl.SIZE_UPLOAD))
166
m['size-download'] = str(curl_handle.getinfo(pycurl.SIZE_DOWNLOAD))
167
m['speed-upload'] = str(curl_handle.getinfo(pycurl.SPEED_UPLOAD))
168
m['header-size'] = str(curl_handle.getinfo(pycurl.HEADER_SIZE))
169
m['request-size'] = str(curl_handle.getinfo(pycurl.REQUEST_SIZE))
170
m['content-length-download'] = str(curl_handle.getinfo(pycurl.CONTENT_LENGTH_DOWNLOAD))
171
m['content-length-upload'] = str(curl_handle.getinfo(pycurl.CONTENT_LENGTH_UPLOAD))
172
m['content-type'] = (curl_handle.getinfo(pycurl.CONTENT_TYPE) or '').strip(';')
187
return response.code, self._handle_response(relpath, response)
176
189
def _raise_curl_connection_error(self, curl):
177
190
curl_errno = curl.getinfo(pycurl.OS_ERRNO)