##// END OF EJS Templates
httpclient: import f4c380237fd5 to fix keepalive not working
Augie Fackler -
r14293:9adbb5ef default
parent child Browse files
Show More
@@ -1,650 +1,652
1 # Copyright 2010, Google Inc.
1 # Copyright 2010, Google Inc.
2 # All rights reserved.
2 # All rights reserved.
3 #
3 #
4 # Redistribution and use in source and binary forms, with or without
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
5 # modification, are permitted provided that the following conditions are
6 # met:
6 # met:
7 #
7 #
8 # * Redistributions of source code must retain the above copyright
8 # * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
9 # notice, this list of conditions and the following disclaimer.
10 # * Redistributions in binary form must reproduce the above
10 # * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
12 # in the documentation and/or other materials provided with the
13 # distribution.
13 # distribution.
14 # * Neither the name of Google Inc. nor the names of its
14 # * Neither the name of Google Inc. nor the names of its
15 # contributors may be used to endorse or promote products derived from
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
16 # this software without specific prior written permission.
17
17
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 """Improved HTTP/1.1 client library
29 """Improved HTTP/1.1 client library
30
30
31 This library contains an HTTPConnection which is similar to the one in
31 This library contains an HTTPConnection which is similar to the one in
32 httplib, but has several additional features:
32 httplib, but has several additional features:
33
33
34 * supports keepalives natively
34 * supports keepalives natively
35 * uses select() to block for incoming data
35 * uses select() to block for incoming data
36 * notices when the server responds early to a request
36 * notices when the server responds early to a request
37 * implements ssl inline instead of in a different class
37 * implements ssl inline instead of in a different class
38 """
38 """
39
39
40 import cStringIO
40 import cStringIO
41 import errno
41 import errno
42 import httplib
42 import httplib
43 import logging
43 import logging
44 import rfc822
44 import rfc822
45 import select
45 import select
46 import socket
46 import socket
47
47
48 import socketutil
48 import socketutil
49
49
50 logger = logging.getLogger(__name__)
50 logger = logging.getLogger(__name__)
51
51
52 __all__ = ['HTTPConnection', 'HTTPResponse']
52 __all__ = ['HTTPConnection', 'HTTPResponse']
53
53
54 HTTP_VER_1_0 = 'HTTP/1.0'
54 HTTP_VER_1_0 = 'HTTP/1.0'
55 HTTP_VER_1_1 = 'HTTP/1.1'
55 HTTP_VER_1_1 = 'HTTP/1.1'
56
56
57 _LEN_CLOSE_IS_END = -1
57 _LEN_CLOSE_IS_END = -1
58
58
59 OUTGOING_BUFFER_SIZE = 1 << 15
59 OUTGOING_BUFFER_SIZE = 1 << 15
60 INCOMING_BUFFER_SIZE = 1 << 20
60 INCOMING_BUFFER_SIZE = 1 << 20
61
61
62 HDR_ACCEPT_ENCODING = 'accept-encoding'
62 HDR_ACCEPT_ENCODING = 'accept-encoding'
63 HDR_CONNECTION_CTRL = 'connection'
63 HDR_CONNECTION_CTRL = 'connection'
64 HDR_CONTENT_LENGTH = 'content-length'
64 HDR_CONTENT_LENGTH = 'content-length'
65 HDR_XFER_ENCODING = 'transfer-encoding'
65 HDR_XFER_ENCODING = 'transfer-encoding'
66
66
67 XFER_ENCODING_CHUNKED = 'chunked'
67 XFER_ENCODING_CHUNKED = 'chunked'
68
68
69 CONNECTION_CLOSE = 'close'
69 CONNECTION_CLOSE = 'close'
70
70
71 EOL = '\r\n'
71 EOL = '\r\n'
72 _END_HEADERS = EOL * 2
72 _END_HEADERS = EOL * 2
73
73
74 # Based on some searching around, 1 second seems like a reasonable
74 # Based on some searching around, 1 second seems like a reasonable
75 # default here.
75 # default here.
76 TIMEOUT_ASSUME_CONTINUE = 1
76 TIMEOUT_ASSUME_CONTINUE = 1
77 TIMEOUT_DEFAULT = None
77 TIMEOUT_DEFAULT = None
78
78
79
79
80 class HTTPResponse(object):
80 class HTTPResponse(object):
81 """Response from an HTTP server.
81 """Response from an HTTP server.
82
82
83 The response will continue to load as available. If you need the
83 The response will continue to load as available. If you need the
84 complete response before continuing, check the .complete() method.
84 complete response before continuing, check the .complete() method.
85 """
85 """
86 def __init__(self, sock, timeout):
86 def __init__(self, sock, timeout):
87 self.sock = sock
87 self.sock = sock
88 self.raw_response = ''
88 self.raw_response = ''
89 self._body = None
89 self._body = None
90 self._headers_len = 0
90 self._headers_len = 0
91 self._content_len = 0
91 self._content_len = 0
92 self.headers = None
92 self.headers = None
93 self.will_close = False
93 self.will_close = False
94 self.status_line = ''
94 self.status_line = ''
95 self.status = None
95 self.status = None
96 self.http_version = None
96 self.http_version = None
97 self.reason = None
97 self.reason = None
98 self._chunked = False
98 self._chunked = False
99 self._chunked_done = False
99 self._chunked_done = False
100 self._chunked_until_next = 0
100 self._chunked_until_next = 0
101 self._chunked_skip_bytes = 0
101 self._chunked_skip_bytes = 0
102 self._chunked_preloaded_block = None
102 self._chunked_preloaded_block = None
103
103
104 self._read_location = 0
104 self._read_location = 0
105 self._eol = EOL
105 self._eol = EOL
106
106
107 self._timeout = timeout
107 self._timeout = timeout
108
108
109 @property
109 @property
110 def _end_headers(self):
110 def _end_headers(self):
111 return self._eol * 2
111 return self._eol * 2
112
112
113 def complete(self):
113 def complete(self):
114 """Returns true if this response is completely loaded.
114 """Returns true if this response is completely loaded.
115 """
115 """
116 if self._chunked:
116 if self._chunked:
117 return self._chunked_done
117 return self._chunked_done
118 if self._content_len == _LEN_CLOSE_IS_END:
118 if self._content_len == _LEN_CLOSE_IS_END:
119 return False
119 return False
120 return self._body is not None and len(self._body) >= self._content_len
120 return self._body is not None and len(self._body) >= self._content_len
121
121
122 def readline(self):
122 def readline(self):
123 """Read a single line from the response body.
123 """Read a single line from the response body.
124
124
125 This may block until either a line ending is found or the
125 This may block until either a line ending is found or the
126 response is complete.
126 response is complete.
127 """
127 """
128 eol = self._body.find('\n', self._read_location)
128 eol = self._body.find('\n', self._read_location)
129 while eol == -1 and not self.complete():
129 while eol == -1 and not self.complete():
130 self._select()
130 self._select()
131 eol = self._body.find('\n', self._read_location)
131 eol = self._body.find('\n', self._read_location)
132 if eol != -1:
132 if eol != -1:
133 eol += 1
133 eol += 1
134 else:
134 else:
135 eol = len(self._body)
135 eol = len(self._body)
136 data = self._body[self._read_location:eol]
136 data = self._body[self._read_location:eol]
137 self._read_location = eol
137 self._read_location = eol
138 return data
138 return data
139
139
140 def read(self, length=None):
140 def read(self, length=None):
141 # if length is None, unbounded read
141 # if length is None, unbounded read
142 while (not self.complete() # never select on a finished read
142 while (not self.complete() # never select on a finished read
143 and (not length # unbounded, so we wait for complete()
143 and (not length # unbounded, so we wait for complete()
144 or (self._read_location + length) > len(self._body))):
144 or (self._read_location + length) > len(self._body))):
145 self._select()
145 self._select()
146 if not length:
146 if not length:
147 length = len(self._body) - self._read_location
147 length = len(self._body) - self._read_location
148 elif len(self._body) < (self._read_location + length):
148 elif len(self._body) < (self._read_location + length):
149 length = len(self._body) - self._read_location
149 length = len(self._body) - self._read_location
150 r = self._body[self._read_location:self._read_location + length]
150 r = self._body[self._read_location:self._read_location + length]
151 self._read_location += len(r)
151 self._read_location += len(r)
152 if self.complete() and self.will_close:
152 if self.complete() and self.will_close:
153 self.sock.close()
153 self.sock.close()
154 return r
154 return r
155
155
156 def _select(self):
156 def _select(self):
157 r, _, _ = select.select([self.sock], [], [], self._timeout)
157 r, _, _ = select.select([self.sock], [], [], self._timeout)
158 if not r:
158 if not r:
159 # socket was not readable. If the response is not complete
159 # socket was not readable. If the response is not complete
160 # and we're not a _LEN_CLOSE_IS_END response, raise a timeout.
160 # and we're not a _LEN_CLOSE_IS_END response, raise a timeout.
161 # If we are a _LEN_CLOSE_IS_END response and we have no data,
161 # If we are a _LEN_CLOSE_IS_END response and we have no data,
162 # raise a timeout.
162 # raise a timeout.
163 if not (self.complete() or
163 if not (self.complete() or
164 (self._content_len == _LEN_CLOSE_IS_END and self._body)):
164 (self._content_len == _LEN_CLOSE_IS_END and self._body)):
165 logger.info('timed out with timeout of %s', self._timeout)
165 logger.info('timed out with timeout of %s', self._timeout)
166 raise HTTPTimeoutException('timeout reading data')
166 raise HTTPTimeoutException('timeout reading data')
167 logger.info('cl: %r body: %r', self._content_len, self._body)
167 logger.info('cl: %r body: %r', self._content_len, self._body)
168 data = self.sock.recv(INCOMING_BUFFER_SIZE)
168 data = self.sock.recv(INCOMING_BUFFER_SIZE)
169 logger.debug('response read %d data during _select', len(data))
169 logger.debug('response read %d data during _select', len(data))
170 if not data:
170 if not data:
171 if not self.headers:
171 if not self.headers:
172 self._load_response(self._end_headers)
172 self._load_response(self._end_headers)
173 self._content_len = 0
173 self._content_len = 0
174 elif self._content_len == _LEN_CLOSE_IS_END:
174 elif self._content_len == _LEN_CLOSE_IS_END:
175 self._content_len = len(self._body)
175 self._content_len = len(self._body)
176 return False
176 return False
177 else:
177 else:
178 self._load_response(data)
178 self._load_response(data)
179 return True
179 return True
180
180
181 def _chunked_parsedata(self, data):
181 def _chunked_parsedata(self, data):
182 if self._chunked_preloaded_block:
182 if self._chunked_preloaded_block:
183 data = self._chunked_preloaded_block + data
183 data = self._chunked_preloaded_block + data
184 self._chunked_preloaded_block = None
184 self._chunked_preloaded_block = None
185 while data:
185 while data:
186 logger.debug('looping with %d data remaining', len(data))
186 logger.debug('looping with %d data remaining', len(data))
187 # Slice out anything we should skip
187 # Slice out anything we should skip
188 if self._chunked_skip_bytes:
188 if self._chunked_skip_bytes:
189 if len(data) <= self._chunked_skip_bytes:
189 if len(data) <= self._chunked_skip_bytes:
190 self._chunked_skip_bytes -= len(data)
190 self._chunked_skip_bytes -= len(data)
191 data = ''
191 data = ''
192 break
192 break
193 else:
193 else:
194 data = data[self._chunked_skip_bytes:]
194 data = data[self._chunked_skip_bytes:]
195 self._chunked_skip_bytes = 0
195 self._chunked_skip_bytes = 0
196
196
197 # determine how much is until the next chunk
197 # determine how much is until the next chunk
198 if self._chunked_until_next:
198 if self._chunked_until_next:
199 amt = self._chunked_until_next
199 amt = self._chunked_until_next
200 logger.debug('reading remaining %d of existing chunk', amt)
200 logger.debug('reading remaining %d of existing chunk', amt)
201 self._chunked_until_next = 0
201 self._chunked_until_next = 0
202 body = data
202 body = data
203 else:
203 else:
204 try:
204 try:
205 amt, body = data.split(self._eol, 1)
205 amt, body = data.split(self._eol, 1)
206 except ValueError:
206 except ValueError:
207 self._chunked_preloaded_block = data
207 self._chunked_preloaded_block = data
208 logger.debug('saving %r as a preloaded block for chunked',
208 logger.debug('saving %r as a preloaded block for chunked',
209 self._chunked_preloaded_block)
209 self._chunked_preloaded_block)
210 return
210 return
211 amt = int(amt, base=16)
211 amt = int(amt, base=16)
212 logger.debug('reading chunk of length %d', amt)
212 logger.debug('reading chunk of length %d', amt)
213 if amt == 0:
213 if amt == 0:
214 self._chunked_done = True
214 self._chunked_done = True
215
215
216 # read through end of what we have or the chunk
216 # read through end of what we have or the chunk
217 self._body += body[:amt]
217 self._body += body[:amt]
218 if len(body) >= amt:
218 if len(body) >= amt:
219 data = body[amt:]
219 data = body[amt:]
220 self._chunked_skip_bytes = len(self._eol)
220 self._chunked_skip_bytes = len(self._eol)
221 else:
221 else:
222 self._chunked_until_next = amt - len(body)
222 self._chunked_until_next = amt - len(body)
223 self._chunked_skip_bytes = 0
223 self._chunked_skip_bytes = 0
224 data = ''
224 data = ''
225
225
226 def _load_response(self, data):
226 def _load_response(self, data):
227 if self._chunked:
227 if self._chunked:
228 self._chunked_parsedata(data)
228 self._chunked_parsedata(data)
229 return
229 return
230 elif self._body is not None:
230 elif self._body is not None:
231 self._body += data
231 self._body += data
232 return
232 return
233
233
234 # We haven't seen end of headers yet
234 # We haven't seen end of headers yet
235 self.raw_response += data
235 self.raw_response += data
236 # This is a bogus server with bad line endings
236 # This is a bogus server with bad line endings
237 if self._eol not in self.raw_response:
237 if self._eol not in self.raw_response:
238 for bad_eol in ('\n', '\r'):
238 for bad_eol in ('\n', '\r'):
239 if (bad_eol in self.raw_response
239 if (bad_eol in self.raw_response
240 # verify that bad_eol is not the end of the incoming data
240 # verify that bad_eol is not the end of the incoming data
241 # as this could be a response line that just got
241 # as this could be a response line that just got
242 # split between \r and \n.
242 # split between \r and \n.
243 and (self.raw_response.index(bad_eol) <
243 and (self.raw_response.index(bad_eol) <
244 (len(self.raw_response) - 1))):
244 (len(self.raw_response) - 1))):
245 logger.info('bogus line endings detected, '
245 logger.info('bogus line endings detected, '
246 'using %r for EOL', bad_eol)
246 'using %r for EOL', bad_eol)
247 self._eol = bad_eol
247 self._eol = bad_eol
248 break
248 break
249 # exit early if not at end of headers
249 # exit early if not at end of headers
250 if self._end_headers not in self.raw_response or self.headers:
250 if self._end_headers not in self.raw_response or self.headers:
251 return
251 return
252
252
253 # handle 100-continue response
253 # handle 100-continue response
254 hdrs, body = self.raw_response.split(self._end_headers, 1)
254 hdrs, body = self.raw_response.split(self._end_headers, 1)
255 http_ver, status = hdrs.split(' ', 1)
255 http_ver, status = hdrs.split(' ', 1)
256 if status.startswith('100'):
256 if status.startswith('100'):
257 self.raw_response = body
257 self.raw_response = body
258 logger.debug('continue seen, setting body to %r', body)
258 logger.debug('continue seen, setting body to %r', body)
259 return
259 return
260
260
261 # arriving here means we should parse response headers
261 # arriving here means we should parse response headers
262 # as all headers have arrived completely
262 # as all headers have arrived completely
263 hdrs, body = self.raw_response.split(self._end_headers, 1)
263 hdrs, body = self.raw_response.split(self._end_headers, 1)
264 del self.raw_response
264 del self.raw_response
265 if self._eol in hdrs:
265 if self._eol in hdrs:
266 self.status_line, hdrs = hdrs.split(self._eol, 1)
266 self.status_line, hdrs = hdrs.split(self._eol, 1)
267 else:
267 else:
268 self.status_line = hdrs
268 self.status_line = hdrs
269 hdrs = ''
269 hdrs = ''
270 # TODO HTTP < 1.0 support
270 # TODO HTTP < 1.0 support
271 (self.http_version, self.status,
271 (self.http_version, self.status,
272 self.reason) = self.status_line.split(' ', 2)
272 self.reason) = self.status_line.split(' ', 2)
273 self.status = int(self.status)
273 self.status = int(self.status)
274 if self._eol != EOL:
274 if self._eol != EOL:
275 hdrs = hdrs.replace(self._eol, '\r\n')
275 hdrs = hdrs.replace(self._eol, '\r\n')
276 headers = rfc822.Message(cStringIO.StringIO(hdrs))
276 headers = rfc822.Message(cStringIO.StringIO(hdrs))
277 if HDR_CONTENT_LENGTH in headers:
277 if HDR_CONTENT_LENGTH in headers:
278 self._content_len = int(headers[HDR_CONTENT_LENGTH])
278 self._content_len = int(headers[HDR_CONTENT_LENGTH])
279 if self.http_version == HTTP_VER_1_0:
279 if self.http_version == HTTP_VER_1_0:
280 self.will_close = True
280 self.will_close = True
281 elif HDR_CONNECTION_CTRL in headers:
281 elif HDR_CONNECTION_CTRL in headers:
282 self.will_close = (
282 self.will_close = (
283 headers[HDR_CONNECTION_CTRL].lower() == CONNECTION_CLOSE)
283 headers[HDR_CONNECTION_CTRL].lower() == CONNECTION_CLOSE)
284 if self._content_len == 0:
284 if self._content_len == 0:
285 self._content_len = _LEN_CLOSE_IS_END
285 self._content_len = _LEN_CLOSE_IS_END
286 if (HDR_XFER_ENCODING in headers
286 if (HDR_XFER_ENCODING in headers
287 and headers[HDR_XFER_ENCODING].lower() == XFER_ENCODING_CHUNKED):
287 and headers[HDR_XFER_ENCODING].lower() == XFER_ENCODING_CHUNKED):
288 self._body = ''
288 self._body = ''
289 self._chunked_parsedata(body)
289 self._chunked_parsedata(body)
290 self._chunked = True
290 self._chunked = True
291 if self._body is None:
291 if self._body is None:
292 self._body = body
292 self._body = body
293 self.headers = headers
293 self.headers = headers
294
294
295
295
296 class HTTPConnection(object):
296 class HTTPConnection(object):
297 """Connection to a single http server.
297 """Connection to a single http server.
298
298
299 Supports 100-continue and keepalives natively. Uses select() for
299 Supports 100-continue and keepalives natively. Uses select() for
300 non-blocking socket operations.
300 non-blocking socket operations.
301 """
301 """
302 http_version = HTTP_VER_1_1
302 http_version = HTTP_VER_1_1
303 response_class = HTTPResponse
303 response_class = HTTPResponse
304
304
305 def __init__(self, host, port=None, use_ssl=None, ssl_validator=None,
305 def __init__(self, host, port=None, use_ssl=None, ssl_validator=None,
306 timeout=TIMEOUT_DEFAULT,
306 timeout=TIMEOUT_DEFAULT,
307 continue_timeout=TIMEOUT_ASSUME_CONTINUE,
307 continue_timeout=TIMEOUT_ASSUME_CONTINUE,
308 proxy_hostport=None, **ssl_opts):
308 proxy_hostport=None, **ssl_opts):
309 """Create a new HTTPConnection.
309 """Create a new HTTPConnection.
310
310
311 Args:
311 Args:
312 host: The host to which we'll connect.
312 host: The host to which we'll connect.
313 port: Optional. The port over which we'll connect. Default 80 for
313 port: Optional. The port over which we'll connect. Default 80 for
314 non-ssl, 443 for ssl.
314 non-ssl, 443 for ssl.
315 use_ssl: Optional. Wether to use ssl. Defaults to False if port is
315 use_ssl: Optional. Wether to use ssl. Defaults to False if port is
316 not 443, true if port is 443.
316 not 443, true if port is 443.
317 ssl_validator: a function(socket) to validate the ssl cert
317 ssl_validator: a function(socket) to validate the ssl cert
318 timeout: Optional. Connection timeout, default is TIMEOUT_DEFAULT.
318 timeout: Optional. Connection timeout, default is TIMEOUT_DEFAULT.
319 continue_timeout: Optional. Timeout for waiting on an expected
319 continue_timeout: Optional. Timeout for waiting on an expected
320 "100 Continue" response. Default is TIMEOUT_ASSUME_CONTINUE.
320 "100 Continue" response. Default is TIMEOUT_ASSUME_CONTINUE.
321 proxy_hostport: Optional. Tuple of (host, port) to use as an http
321 proxy_hostport: Optional. Tuple of (host, port) to use as an http
322 proxy for the connection. Default is to not use a proxy.
322 proxy for the connection. Default is to not use a proxy.
323 """
323 """
324 if port is None and host.count(':') == 1 or ']:' in host:
324 if port is None and host.count(':') == 1 or ']:' in host:
325 host, port = host.rsplit(':', 1)
325 host, port = host.rsplit(':', 1)
326 port = int(port)
326 port = int(port)
327 if '[' in host:
327 if '[' in host:
328 host = host[1:-1]
328 host = host[1:-1]
329 if use_ssl is None and port is None:
329 if use_ssl is None and port is None:
330 use_ssl = False
330 use_ssl = False
331 port = 80
331 port = 80
332 elif use_ssl is None:
332 elif use_ssl is None:
333 use_ssl = (port == 443)
333 use_ssl = (port == 443)
334 elif port is None:
334 elif port is None:
335 port = (use_ssl and 443 or 80)
335 port = (use_ssl and 443 or 80)
336 self.port = port
336 self.port = port
337 if use_ssl and not socketutil.have_ssl:
337 if use_ssl and not socketutil.have_ssl:
338 raise Exception('ssl requested but unavailable on this Python')
338 raise Exception('ssl requested but unavailable on this Python')
339 self.ssl = use_ssl
339 self.ssl = use_ssl
340 self.ssl_opts = ssl_opts
340 self.ssl_opts = ssl_opts
341 self._ssl_validator = ssl_validator
341 self._ssl_validator = ssl_validator
342 self.host = host
342 self.host = host
343 self.sock = None
343 self.sock = None
344 self._current_response = None
344 self._current_response = None
345 self._current_response_taken = False
345 self._current_response_taken = False
346 if proxy_hostport is None:
346 if proxy_hostport is None:
347 self._proxy_host = self._proxy_port = None
347 self._proxy_host = self._proxy_port = None
348 else:
348 else:
349 self._proxy_host, self._proxy_port = proxy_hostport
349 self._proxy_host, self._proxy_port = proxy_hostport
350
350
351 self.timeout = timeout
351 self.timeout = timeout
352 self.continue_timeout = continue_timeout
352 self.continue_timeout = continue_timeout
353
353
354 def _connect(self):
354 def _connect(self):
355 """Connect to the host and port specified in __init__."""
355 """Connect to the host and port specified in __init__."""
356 if self.sock:
356 if self.sock:
357 return
357 return
358 if self._proxy_host is not None:
358 if self._proxy_host is not None:
359 logger.info('Connecting to http proxy %s:%s',
359 logger.info('Connecting to http proxy %s:%s',
360 self._proxy_host, self._proxy_port)
360 self._proxy_host, self._proxy_port)
361 sock = socketutil.create_connection((self._proxy_host,
361 sock = socketutil.create_connection((self._proxy_host,
362 self._proxy_port))
362 self._proxy_port))
363 if self.ssl:
363 if self.ssl:
364 # TODO proxy header support
364 # TODO proxy header support
365 data = self.buildheaders('CONNECT', '%s:%d' % (self.host,
365 data = self.buildheaders('CONNECT', '%s:%d' % (self.host,
366 self.port),
366 self.port),
367 {}, HTTP_VER_1_0)
367 {}, HTTP_VER_1_0)
368 sock.send(data)
368 sock.send(data)
369 sock.setblocking(0)
369 sock.setblocking(0)
370 r = self.response_class(sock, self.timeout)
370 r = self.response_class(sock, self.timeout)
371 timeout_exc = HTTPTimeoutException(
371 timeout_exc = HTTPTimeoutException(
372 'Timed out waiting for CONNECT response from proxy')
372 'Timed out waiting for CONNECT response from proxy')
373 while not r.complete():
373 while not r.complete():
374 try:
374 try:
375 if not r._select():
375 if not r._select():
376 raise timeout_exc
376 raise timeout_exc
377 except HTTPTimeoutException:
377 except HTTPTimeoutException:
378 # This raise/except pattern looks goofy, but
378 # This raise/except pattern looks goofy, but
379 # _select can raise the timeout as well as the
379 # _select can raise the timeout as well as the
380 # loop body. I wish it wasn't this convoluted,
380 # loop body. I wish it wasn't this convoluted,
381 # but I don't have a better solution
381 # but I don't have a better solution
382 # immediately handy.
382 # immediately handy.
383 raise timeout_exc
383 raise timeout_exc
384 if r.status != 200:
384 if r.status != 200:
385 raise HTTPProxyConnectFailedException(
385 raise HTTPProxyConnectFailedException(
386 'Proxy connection failed: %d %s' % (r.status,
386 'Proxy connection failed: %d %s' % (r.status,
387 r.read()))
387 r.read()))
388 logger.info('CONNECT (for SSL) to %s:%s via proxy succeeded.',
388 logger.info('CONNECT (for SSL) to %s:%s via proxy succeeded.',
389 self.host, self.port)
389 self.host, self.port)
390 else:
390 else:
391 sock = socketutil.create_connection((self.host, self.port))
391 sock = socketutil.create_connection((self.host, self.port))
392 if self.ssl:
392 if self.ssl:
393 logger.debug('wrapping socket for ssl with options %r',
393 logger.debug('wrapping socket for ssl with options %r',
394 self.ssl_opts)
394 self.ssl_opts)
395 sock = socketutil.wrap_socket(sock, **self.ssl_opts)
395 sock = socketutil.wrap_socket(sock, **self.ssl_opts)
396 if self._ssl_validator:
396 if self._ssl_validator:
397 self._ssl_validator(sock)
397 self._ssl_validator(sock)
398 sock.setblocking(0)
398 sock.setblocking(0)
399 self.sock = sock
399 self.sock = sock
400
400
401 def buildheaders(self, method, path, headers, http_ver):
401 def buildheaders(self, method, path, headers, http_ver):
402 if self.ssl and self.port == 443 or self.port == 80:
402 if self.ssl and self.port == 443 or self.port == 80:
403 # default port for protocol, so leave it out
403 # default port for protocol, so leave it out
404 hdrhost = self.host
404 hdrhost = self.host
405 else:
405 else:
406 # include nonstandard port in header
406 # include nonstandard port in header
407 if ':' in self.host: # must be IPv6
407 if ':' in self.host: # must be IPv6
408 hdrhost = '[%s]:%d' % (self.host, self.port)
408 hdrhost = '[%s]:%d' % (self.host, self.port)
409 else:
409 else:
410 hdrhost = '%s:%d' % (self.host, self.port)
410 hdrhost = '%s:%d' % (self.host, self.port)
411 if self._proxy_host and not self.ssl:
411 if self._proxy_host and not self.ssl:
412 # When talking to a regular http proxy we must send the
412 # When talking to a regular http proxy we must send the
413 # full URI, but in all other cases we must not (although
413 # full URI, but in all other cases we must not (although
414 # technically RFC 2616 says servers must accept our
414 # technically RFC 2616 says servers must accept our
415 # request if we screw up, experimentally few do that
415 # request if we screw up, experimentally few do that
416 # correctly.)
416 # correctly.)
417 assert path[0] == '/', 'path must start with a /'
417 assert path[0] == '/', 'path must start with a /'
418 path = 'http://%s%s' % (hdrhost, path)
418 path = 'http://%s%s' % (hdrhost, path)
419 outgoing = ['%s %s %s%s' % (method, path, http_ver, EOL)]
419 outgoing = ['%s %s %s%s' % (method, path, http_ver, EOL)]
420 headers['host'] = ('Host', hdrhost)
420 headers['host'] = ('Host', hdrhost)
421 headers[HDR_ACCEPT_ENCODING] = (HDR_ACCEPT_ENCODING, 'identity')
421 headers[HDR_ACCEPT_ENCODING] = (HDR_ACCEPT_ENCODING, 'identity')
422 for hdr, val in headers.itervalues():
422 for hdr, val in headers.itervalues():
423 outgoing.append('%s: %s%s' % (hdr, val, EOL))
423 outgoing.append('%s: %s%s' % (hdr, val, EOL))
424 outgoing.append(EOL)
424 outgoing.append(EOL)
425 return ''.join(outgoing)
425 return ''.join(outgoing)
426
426
427 def close(self):
427 def close(self):
428 """Close the connection to the server.
428 """Close the connection to the server.
429
429
430 This is a no-op if the connection is already closed. The
430 This is a no-op if the connection is already closed. The
431 connection may automatically close if requessted by the server
431 connection may automatically close if requessted by the server
432 or required by the nature of a response.
432 or required by the nature of a response.
433 """
433 """
434 if self.sock is None:
434 if self.sock is None:
435 return
435 return
436 self.sock.close()
436 self.sock.close()
437 self.sock = None
437 self.sock = None
438 logger.info('closed connection to %s on %s', self.host, self.port)
438 logger.info('closed connection to %s on %s', self.host, self.port)
439
439
440 def busy(self):
440 def busy(self):
441 """Returns True if this connection object is currently in use.
441 """Returns True if this connection object is currently in use.
442
442
443 If a response is still pending, this will return True, even if
443 If a response is still pending, this will return True, even if
444 the request has finished sending. In the future,
444 the request has finished sending. In the future,
445 HTTPConnection may transparently juggle multiple connections
445 HTTPConnection may transparently juggle multiple connections
446 to the server, in which case this will be useful to detect if
446 to the server, in which case this will be useful to detect if
447 any of those connections is ready for use.
447 any of those connections is ready for use.
448 """
448 """
449 cr = self._current_response
449 cr = self._current_response
450 if cr is not None:
450 if cr is not None:
451 if self._current_response_taken:
451 if self._current_response_taken:
452 if cr.will_close:
452 if cr.will_close:
453 self.sock = None
453 self.sock = None
454 self._current_response = None
454 self._current_response = None
455 return False
455 return False
456 elif cr.complete():
456 elif cr.complete():
457 self._current_response = None
457 self._current_response = None
458 return False
458 return False
459 return True
459 return True
460 return False
460 return False
461
461
462 def request(self, method, path, body=None, headers={},
462 def request(self, method, path, body=None, headers={},
463 expect_continue=False):
463 expect_continue=False):
464 """Send a request to the server.
464 """Send a request to the server.
465
465
466 For increased flexibility, this does not return the response
466 For increased flexibility, this does not return the response
467 object. Future versions of HTTPConnection that juggle multiple
467 object. Future versions of HTTPConnection that juggle multiple
468 sockets will be able to send (for example) 5 requests all at
468 sockets will be able to send (for example) 5 requests all at
469 once, and then let the requests arrive as data is
469 once, and then let the requests arrive as data is
470 available. Use the `getresponse()` method to retrieve the
470 available. Use the `getresponse()` method to retrieve the
471 response.
471 response.
472 """
472 """
473 if self.busy():
473 if self.busy():
474 raise httplib.CannotSendRequest(
474 raise httplib.CannotSendRequest(
475 'Can not send another request before '
475 'Can not send another request before '
476 'current response is read!')
476 'current response is read!')
477 self._current_response_taken = False
477 self._current_response_taken = False
478
478
479 logger.info('sending %s request for %s to %s on port %s',
479 logger.info('sending %s request for %s to %s on port %s',
480 method, path, self.host, self.port)
480 method, path, self.host, self.port)
481 hdrs = dict((k.lower(), (k, v)) for k, v in headers.iteritems())
481 hdrs = dict((k.lower(), (k, v)) for k, v in headers.iteritems())
482 if hdrs.get('expect', ('', ''))[1].lower() == '100-continue':
482 if hdrs.get('expect', ('', ''))[1].lower() == '100-continue':
483 expect_continue = True
483 expect_continue = True
484 elif expect_continue:
484 elif expect_continue:
485 hdrs['expect'] = ('Expect', '100-Continue')
485 hdrs['expect'] = ('Expect', '100-Continue')
486
486
487 chunked = False
487 chunked = False
488 if body and HDR_CONTENT_LENGTH not in hdrs:
488 if body and HDR_CONTENT_LENGTH not in hdrs:
489 if getattr(body, '__len__', False):
489 if getattr(body, '__len__', False):
490 hdrs[HDR_CONTENT_LENGTH] = (HDR_CONTENT_LENGTH, len(body))
490 hdrs[HDR_CONTENT_LENGTH] = (HDR_CONTENT_LENGTH, len(body))
491 elif getattr(body, 'read', False):
491 elif getattr(body, 'read', False):
492 hdrs[HDR_XFER_ENCODING] = (HDR_XFER_ENCODING,
492 hdrs[HDR_XFER_ENCODING] = (HDR_XFER_ENCODING,
493 XFER_ENCODING_CHUNKED)
493 XFER_ENCODING_CHUNKED)
494 chunked = True
494 chunked = True
495 else:
495 else:
496 raise BadRequestData('body has no __len__() nor read()')
496 raise BadRequestData('body has no __len__() nor read()')
497
497
498 self._connect()
498 self._connect()
499 outgoing_headers = self.buildheaders(
499 outgoing_headers = self.buildheaders(
500 method, path, hdrs, self.http_version)
500 method, path, hdrs, self.http_version)
501 response = None
501 response = None
502 first = True
502 first = True
503
503
504 def reconnect(where):
504 def reconnect(where):
505 logger.info('reconnecting during %s', where)
505 logger.info('reconnecting during %s', where)
506 self.close()
506 self.close()
507 self._connect()
507 self._connect()
508
508
509 while ((outgoing_headers or body)
509 while ((outgoing_headers or body)
510 and not (response and response.complete())):
510 and not (response and response.complete())):
511 select_timeout = self.timeout
511 select_timeout = self.timeout
512 out = outgoing_headers or body
512 out = outgoing_headers or body
513 blocking_on_continue = False
513 blocking_on_continue = False
514 if expect_continue and not outgoing_headers and not (
514 if expect_continue and not outgoing_headers and not (
515 response and response.headers):
515 response and response.headers):
516 logger.info(
516 logger.info(
517 'waiting up to %s seconds for'
517 'waiting up to %s seconds for'
518 ' continue response from server',
518 ' continue response from server',
519 self.continue_timeout)
519 self.continue_timeout)
520 select_timeout = self.continue_timeout
520 select_timeout = self.continue_timeout
521 blocking_on_continue = True
521 blocking_on_continue = True
522 out = False
522 out = False
523 if out:
523 if out:
524 w = [self.sock]
524 w = [self.sock]
525 else:
525 else:
526 w = []
526 w = []
527 r, w, x = select.select([self.sock], w, [], select_timeout)
527 r, w, x = select.select([self.sock], w, [], select_timeout)
528 # if we were expecting a 100 continue and it's been long
528 # if we were expecting a 100 continue and it's been long
529 # enough, just go ahead and assume it's ok. This is the
529 # enough, just go ahead and assume it's ok. This is the
530 # recommended behavior from the RFC.
530 # recommended behavior from the RFC.
531 if r == w == x == []:
531 if r == w == x == []:
532 if blocking_on_continue:
532 if blocking_on_continue:
533 expect_continue = False
533 expect_continue = False
534 logger.info('no response to continue expectation from '
534 logger.info('no response to continue expectation from '
535 'server, optimistically sending request body')
535 'server, optimistically sending request body')
536 else:
536 else:
537 raise HTTPTimeoutException('timeout sending data')
537 raise HTTPTimeoutException('timeout sending data')
538 # TODO exceptional conditions with select? (what are those be?)
538 # TODO exceptional conditions with select? (what are those be?)
539 # TODO if the response is loading, must we finish sending at all?
539 # TODO if the response is loading, must we finish sending at all?
540 #
540 #
541 # Certainly not if it's going to close the connection and/or
541 # Certainly not if it's going to close the connection and/or
542 # the response is already done...I think.
542 # the response is already done...I think.
543 was_first = first
543 was_first = first
544
544
545 # incoming data
545 # incoming data
546 if r:
546 if r:
547 try:
547 try:
548 data = r[0].recv(INCOMING_BUFFER_SIZE)
548 data = r[0].recv(INCOMING_BUFFER_SIZE)
549 if not data:
549 if not data:
550 logger.info('socket appears closed in read')
550 logger.info('socket appears closed in read')
551 outgoing_headers = body = None
551 outgoing_headers = body = None
552 break
552 break
553 if response is None:
553 if response is None:
554 response = self.response_class(r[0], self.timeout)
554 response = self.response_class(r[0], self.timeout)
555 response._load_response(data)
555 response._load_response(data)
556 if (response._content_len == _LEN_CLOSE_IS_END
556 if (response._content_len == _LEN_CLOSE_IS_END
557 and len(data) == 0):
557 and len(data) == 0):
558 response._content_len = len(response._body)
558 response._content_len = len(response._body)
559 if response.complete():
559 if response.complete():
560 w = []
560 w = []
561 response.will_close = True
561 response.will_close = True
562 except socket.error, e:
562 except socket.error, e:
563 if e[0] != errno.EPIPE and not was_first:
563 if e[0] != errno.EPIPE and not was_first:
564 raise
564 raise
565 if (response._content_len
565 if (response._content_len
566 and response._content_len != _LEN_CLOSE_IS_END):
566 and response._content_len != _LEN_CLOSE_IS_END):
567 outgoing_headers = sent_data + outgoing_headers
567 outgoing_headers = sent_data + outgoing_headers
568 reconnect('read')
568 reconnect('read')
569
569
570 # outgoing data
570 # outgoing data
571 if w and out:
571 if w and out:
572 try:
572 try:
573 if getattr(out, 'read', False):
573 if getattr(out, 'read', False):
574 data = out.read(OUTGOING_BUFFER_SIZE)
574 data = out.read(OUTGOING_BUFFER_SIZE)
575 if not data:
575 if not data:
576 continue
576 continue
577 if len(data) < OUTGOING_BUFFER_SIZE:
577 if len(data) < OUTGOING_BUFFER_SIZE:
578 if chunked:
578 if chunked:
579 body = '0' + EOL + EOL
579 body = '0' + EOL + EOL
580 else:
580 else:
581 body = None
581 body = None
582 if chunked:
582 if chunked:
583 out = hex(len(data))[2:] + EOL + data + EOL
583 out = hex(len(data))[2:] + EOL + data + EOL
584 else:
584 else:
585 out = data
585 out = data
586 amt = w[0].send(out)
586 amt = w[0].send(out)
587 except socket.error, e:
587 except socket.error, e:
588 if e[0] == socket.SSL_ERROR_WANT_WRITE and self.ssl:
588 if e[0] == socket.SSL_ERROR_WANT_WRITE and self.ssl:
589 # This means that SSL hasn't flushed its buffer into
589 # This means that SSL hasn't flushed its buffer into
590 # the socket yet.
590 # the socket yet.
591 # TODO: find a way to block on ssl flushing its buffer
591 # TODO: find a way to block on ssl flushing its buffer
592 # similar to selecting on a raw socket.
592 # similar to selecting on a raw socket.
593 continue
593 continue
594 elif (e[0] not in (errno.ECONNRESET, errno.EPIPE)
594 elif (e[0] not in (errno.ECONNRESET, errno.EPIPE)
595 and not first):
595 and not first):
596 raise
596 raise
597 reconnect('write')
597 reconnect('write')
598 amt = self.sock.send(out)
598 amt = self.sock.send(out)
599 logger.debug('sent %d', amt)
599 logger.debug('sent %d', amt)
600 first = False
600 first = False
601 # stash data we think we sent in case the socket breaks
601 # stash data we think we sent in case the socket breaks
602 # when we read from it
602 # when we read from it
603 if was_first:
603 if was_first:
604 sent_data = out[:amt]
604 sent_data = out[:amt]
605 if out is body:
605 if out is body:
606 body = out[amt:]
606 body = out[amt:]
607 else:
607 else:
608 outgoing_headers = out[amt:]
608 outgoing_headers = out[amt:]
609
609
610 # close if the server response said to or responded before eating
610 # close if the server response said to or responded before eating
611 # the whole request
611 # the whole request
612 if response is None:
612 if response is None:
613 response = self.response_class(self.sock, self.timeout)
613 response = self.response_class(self.sock, self.timeout)
614 complete = response.complete()
614 complete = response.complete()
615 data_left = bool(outgoing_headers or body)
615 data_left = bool(outgoing_headers or body)
616 if data_left:
616 if data_left:
617 logger.info('stopped sending request early, '
617 logger.info('stopped sending request early, '
618 'will close the socket to be safe.')
618 'will close the socket to be safe.')
619 response.will_close = True
619 response.will_close = True
620 if response.will_close:
620 if response.will_close:
621 # The socket will be closed by the response, so we disown
621 # The socket will be closed by the response, so we disown
622 # the socket
622 # the socket
623 self.sock = None
623 self.sock = None
624 self._current_response = response
624 self._current_response = response
625
625
626 def getresponse(self):
626 def getresponse(self):
627 if self._current_response is None:
627 if self._current_response is None:
628 raise httplib.ResponseNotReady()
628 raise httplib.ResponseNotReady()
629 r = self._current_response
629 r = self._current_response
630 while r.headers is None:
630 while r.headers is None:
631 r._select()
631 r._select()
632 if r.complete() or r.will_close:
632 if r.will_close:
633 self.sock = None
633 self.sock = None
634 self._current_response = None
634 self._current_response = None
635 elif r.complete():
636 self._current_response = None
635 else:
637 else:
636 self._current_response_taken = True
638 self._current_response_taken = True
637 return r
639 return r
638
640
639
641
640 class HTTPTimeoutException(httplib.HTTPException):
642 class HTTPTimeoutException(httplib.HTTPException):
641 """A timeout occurred while waiting on the server."""
643 """A timeout occurred while waiting on the server."""
642
644
643
645
644 class BadRequestData(httplib.HTTPException):
646 class BadRequestData(httplib.HTTPException):
645 """Request body object has neither __len__ nor read."""
647 """Request body object has neither __len__ nor read."""
646
648
647
649
648 class HTTPProxyConnectFailedException(httplib.HTTPException):
650 class HTTPProxyConnectFailedException(httplib.HTTPException):
649 """Connecting to the HTTP proxy failed."""
651 """Connecting to the HTTP proxy failed."""
650 # no-check-code
652 # no-check-code
@@ -1,366 +1,369
1 # Copyright 2010, Google Inc.
1 # Copyright 2010, Google Inc.
2 # All rights reserved.
2 # All rights reserved.
3 #
3 #
4 # Redistribution and use in source and binary forms, with or without
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
5 # modification, are permitted provided that the following conditions are
6 # met:
6 # met:
7 #
7 #
8 # * Redistributions of source code must retain the above copyright
8 # * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
9 # notice, this list of conditions and the following disclaimer.
10 # * Redistributions in binary form must reproduce the above
10 # * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
12 # in the documentation and/or other materials provided with the
13 # distribution.
13 # distribution.
14 # * Neither the name of Google Inc. nor the names of its
14 # * Neither the name of Google Inc. nor the names of its
15 # contributors may be used to endorse or promote products derived from
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
16 # this software without specific prior written permission.
17
17
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 import unittest
29 import unittest
30
30
31 import http
31 import http
32
32
33 # relative import to ease embedding the library
33 # relative import to ease embedding the library
34 import util
34 import util
35
35
36
36
37 class SimpleHttpTest(util.HttpTestBase, unittest.TestCase):
37 class SimpleHttpTest(util.HttpTestBase, unittest.TestCase):
38
38
39 def _run_simple_test(self, host, server_data, expected_req, expected_data):
39 def _run_simple_test(self, host, server_data, expected_req, expected_data):
40 con = http.HTTPConnection(host)
40 con = http.HTTPConnection(host)
41 con._connect()
41 con._connect()
42 con.sock.data = server_data
42 con.sock.data = server_data
43 con.request('GET', '/')
43 con.request('GET', '/')
44
44
45 self.assertStringEqual(expected_req, con.sock.sent)
45 self.assertStringEqual(expected_req, con.sock.sent)
46 self.assertEqual(expected_data, con.getresponse().read())
46 self.assertEqual(expected_data, con.getresponse().read())
47
47
48 def test_broken_data_obj(self):
48 def test_broken_data_obj(self):
49 con = http.HTTPConnection('1.2.3.4:80')
49 con = http.HTTPConnection('1.2.3.4:80')
50 con._connect()
50 con._connect()
51 self.assertRaises(http.BadRequestData,
51 self.assertRaises(http.BadRequestData,
52 con.request, 'POST', '/', body=1)
52 con.request, 'POST', '/', body=1)
53
53
54 def test_no_keepalive_http_1_0(self):
54 def test_no_keepalive_http_1_0(self):
55 expected_request_one = """GET /remote/.hg/requires HTTP/1.1
55 expected_request_one = """GET /remote/.hg/requires HTTP/1.1
56 Host: localhost:9999
56 Host: localhost:9999
57 range: bytes=0-
57 range: bytes=0-
58 accept-encoding: identity
58 accept-encoding: identity
59 accept: application/mercurial-0.1
59 accept: application/mercurial-0.1
60 user-agent: mercurial/proto-1.0
60 user-agent: mercurial/proto-1.0
61
61
62 """.replace('\n', '\r\n')
62 """.replace('\n', '\r\n')
63 expected_response_headers = """HTTP/1.0 200 OK
63 expected_response_headers = """HTTP/1.0 200 OK
64 Server: SimpleHTTP/0.6 Python/2.6.1
64 Server: SimpleHTTP/0.6 Python/2.6.1
65 Date: Sun, 01 May 2011 13:56:57 GMT
65 Date: Sun, 01 May 2011 13:56:57 GMT
66 Content-type: application/octet-stream
66 Content-type: application/octet-stream
67 Content-Length: 33
67 Content-Length: 33
68 Last-Modified: Sun, 01 May 2011 13:56:56 GMT
68 Last-Modified: Sun, 01 May 2011 13:56:56 GMT
69
69
70 """.replace('\n', '\r\n')
70 """.replace('\n', '\r\n')
71 expected_response_body = """revlogv1
71 expected_response_body = """revlogv1
72 store
72 store
73 fncache
73 fncache
74 dotencode
74 dotencode
75 """
75 """
76 con = http.HTTPConnection('localhost:9999')
76 con = http.HTTPConnection('localhost:9999')
77 con._connect()
77 con._connect()
78 con.sock.data = [expected_response_headers, expected_response_body]
78 con.sock.data = [expected_response_headers, expected_response_body]
79 con.request('GET', '/remote/.hg/requires',
79 con.request('GET', '/remote/.hg/requires',
80 headers={'accept-encoding': 'identity',
80 headers={'accept-encoding': 'identity',
81 'range': 'bytes=0-',
81 'range': 'bytes=0-',
82 'accept': 'application/mercurial-0.1',
82 'accept': 'application/mercurial-0.1',
83 'user-agent': 'mercurial/proto-1.0',
83 'user-agent': 'mercurial/proto-1.0',
84 })
84 })
85 self.assertStringEqual(expected_request_one, con.sock.sent)
85 self.assertStringEqual(expected_request_one, con.sock.sent)
86 self.assertEqual(con.sock.closed, False)
86 self.assertEqual(con.sock.closed, False)
87 self.assertNotEqual(con.sock.data, [])
87 self.assertNotEqual(con.sock.data, [])
88 self.assert_(con.busy())
88 self.assert_(con.busy())
89 resp = con.getresponse()
89 resp = con.getresponse()
90 self.assertStringEqual(resp.read(), expected_response_body)
90 self.assertStringEqual(resp.read(), expected_response_body)
91 self.failIf(con.busy())
91 self.failIf(con.busy())
92 self.assertEqual(con.sock, None)
92 self.assertEqual(con.sock, None)
93 self.assertEqual(resp.sock.data, [])
93 self.assertEqual(resp.sock.data, [])
94 self.assert_(resp.sock.closed)
94 self.assert_(resp.sock.closed)
95
95
96 def test_multiline_header(self):
96 def test_multiline_header(self):
97 con = http.HTTPConnection('1.2.3.4:80')
97 con = http.HTTPConnection('1.2.3.4:80')
98 con._connect()
98 con._connect()
99 con.sock.data = ['HTTP/1.1 200 OK\r\n',
99 con.sock.data = ['HTTP/1.1 200 OK\r\n',
100 'Server: BogusServer 1.0\r\n',
100 'Server: BogusServer 1.0\r\n',
101 'Multiline: Value\r\n',
101 'Multiline: Value\r\n',
102 ' Rest of value\r\n',
102 ' Rest of value\r\n',
103 'Content-Length: 10\r\n',
103 'Content-Length: 10\r\n',
104 '\r\n'
104 '\r\n'
105 '1234567890'
105 '1234567890'
106 ]
106 ]
107 con.request('GET', '/')
107 con.request('GET', '/')
108
108
109 expected_req = ('GET / HTTP/1.1\r\n'
109 expected_req = ('GET / HTTP/1.1\r\n'
110 'Host: 1.2.3.4\r\n'
110 'Host: 1.2.3.4\r\n'
111 'accept-encoding: identity\r\n\r\n')
111 'accept-encoding: identity\r\n\r\n')
112
112
113 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
113 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
114 self.assertEqual(expected_req, con.sock.sent)
114 self.assertEqual(expected_req, con.sock.sent)
115 resp = con.getresponse()
115 resp = con.getresponse()
116 self.assertEqual('1234567890', resp.read())
116 self.assertEqual('1234567890', resp.read())
117 self.assertEqual(['Value\n Rest of value'],
117 self.assertEqual(['Value\n Rest of value'],
118 resp.headers.getheaders('multiline'))
118 resp.headers.getheaders('multiline'))
119 # Socket should not be closed
120 self.assertEqual(resp.sock.closed, False)
121 self.assertEqual(con.sock.closed, False)
119
122
120 def testSimpleRequest(self):
123 def testSimpleRequest(self):
121 con = http.HTTPConnection('1.2.3.4:80')
124 con = http.HTTPConnection('1.2.3.4:80')
122 con._connect()
125 con._connect()
123 con.sock.data = ['HTTP/1.1 200 OK\r\n',
126 con.sock.data = ['HTTP/1.1 200 OK\r\n',
124 'Server: BogusServer 1.0\r\n',
127 'Server: BogusServer 1.0\r\n',
125 'MultiHeader: Value\r\n'
128 'MultiHeader: Value\r\n'
126 'MultiHeader: Other Value\r\n'
129 'MultiHeader: Other Value\r\n'
127 'MultiHeader: One More!\r\n'
130 'MultiHeader: One More!\r\n'
128 'Content-Length: 10\r\n',
131 'Content-Length: 10\r\n',
129 '\r\n'
132 '\r\n'
130 '1234567890'
133 '1234567890'
131 ]
134 ]
132 con.request('GET', '/')
135 con.request('GET', '/')
133
136
134 expected_req = ('GET / HTTP/1.1\r\n'
137 expected_req = ('GET / HTTP/1.1\r\n'
135 'Host: 1.2.3.4\r\n'
138 'Host: 1.2.3.4\r\n'
136 'accept-encoding: identity\r\n\r\n')
139 'accept-encoding: identity\r\n\r\n')
137
140
138 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
141 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
139 self.assertEqual(expected_req, con.sock.sent)
142 self.assertEqual(expected_req, con.sock.sent)
140 resp = con.getresponse()
143 resp = con.getresponse()
141 self.assertEqual('1234567890', resp.read())
144 self.assertEqual('1234567890', resp.read())
142 self.assertEqual(['Value', 'Other Value', 'One More!'],
145 self.assertEqual(['Value', 'Other Value', 'One More!'],
143 resp.headers.getheaders('multiheader'))
146 resp.headers.getheaders('multiheader'))
144 self.assertEqual(['BogusServer 1.0'],
147 self.assertEqual(['BogusServer 1.0'],
145 resp.headers.getheaders('server'))
148 resp.headers.getheaders('server'))
146
149
147 def testHeaderlessResponse(self):
150 def testHeaderlessResponse(self):
148 con = http.HTTPConnection('1.2.3.4', use_ssl=False)
151 con = http.HTTPConnection('1.2.3.4', use_ssl=False)
149 con._connect()
152 con._connect()
150 con.sock.data = ['HTTP/1.1 200 OK\r\n',
153 con.sock.data = ['HTTP/1.1 200 OK\r\n',
151 '\r\n'
154 '\r\n'
152 '1234567890'
155 '1234567890'
153 ]
156 ]
154 con.request('GET', '/')
157 con.request('GET', '/')
155
158
156 expected_req = ('GET / HTTP/1.1\r\n'
159 expected_req = ('GET / HTTP/1.1\r\n'
157 'Host: 1.2.3.4\r\n'
160 'Host: 1.2.3.4\r\n'
158 'accept-encoding: identity\r\n\r\n')
161 'accept-encoding: identity\r\n\r\n')
159
162
160 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
163 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
161 self.assertEqual(expected_req, con.sock.sent)
164 self.assertEqual(expected_req, con.sock.sent)
162 resp = con.getresponse()
165 resp = con.getresponse()
163 self.assertEqual('1234567890', resp.read())
166 self.assertEqual('1234567890', resp.read())
164 self.assertEqual({}, dict(resp.headers))
167 self.assertEqual({}, dict(resp.headers))
165 self.assertEqual(resp.status, 200)
168 self.assertEqual(resp.status, 200)
166
169
167 def testReadline(self):
170 def testReadline(self):
168 con = http.HTTPConnection('1.2.3.4')
171 con = http.HTTPConnection('1.2.3.4')
169 con._connect()
172 con._connect()
170 # make sure it trickles in one byte at a time
173 # make sure it trickles in one byte at a time
171 # so that we touch all the cases in readline
174 # so that we touch all the cases in readline
172 con.sock.data = list(''.join(
175 con.sock.data = list(''.join(
173 ['HTTP/1.1 200 OK\r\n',
176 ['HTTP/1.1 200 OK\r\n',
174 'Server: BogusServer 1.0\r\n',
177 'Server: BogusServer 1.0\r\n',
175 'Connection: Close\r\n',
178 'Connection: Close\r\n',
176 '\r\n'
179 '\r\n'
177 '1\n2\nabcdefg\n4\n5']))
180 '1\n2\nabcdefg\n4\n5']))
178
181
179 expected_req = ('GET / HTTP/1.1\r\n'
182 expected_req = ('GET / HTTP/1.1\r\n'
180 'Host: 1.2.3.4\r\n'
183 'Host: 1.2.3.4\r\n'
181 'accept-encoding: identity\r\n\r\n')
184 'accept-encoding: identity\r\n\r\n')
182
185
183 con.request('GET', '/')
186 con.request('GET', '/')
184 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
187 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
185 self.assertEqual(expected_req, con.sock.sent)
188 self.assertEqual(expected_req, con.sock.sent)
186 r = con.getresponse()
189 r = con.getresponse()
187 for expected in ['1\n', '2\n', 'abcdefg\n', '4\n', '5']:
190 for expected in ['1\n', '2\n', 'abcdefg\n', '4\n', '5']:
188 actual = r.readline()
191 actual = r.readline()
189 self.assertEqual(expected, actual,
192 self.assertEqual(expected, actual,
190 'Expected %r, got %r' % (expected, actual))
193 'Expected %r, got %r' % (expected, actual))
191
194
192 def testIPv6(self):
195 def testIPv6(self):
193 self._run_simple_test('[::1]:8221',
196 self._run_simple_test('[::1]:8221',
194 ['HTTP/1.1 200 OK\r\n',
197 ['HTTP/1.1 200 OK\r\n',
195 'Server: BogusServer 1.0\r\n',
198 'Server: BogusServer 1.0\r\n',
196 'Content-Length: 10',
199 'Content-Length: 10',
197 '\r\n\r\n'
200 '\r\n\r\n'
198 '1234567890'],
201 '1234567890'],
199 ('GET / HTTP/1.1\r\n'
202 ('GET / HTTP/1.1\r\n'
200 'Host: [::1]:8221\r\n'
203 'Host: [::1]:8221\r\n'
201 'accept-encoding: identity\r\n\r\n'),
204 'accept-encoding: identity\r\n\r\n'),
202 '1234567890')
205 '1234567890')
203 self._run_simple_test('::2',
206 self._run_simple_test('::2',
204 ['HTTP/1.1 200 OK\r\n',
207 ['HTTP/1.1 200 OK\r\n',
205 'Server: BogusServer 1.0\r\n',
208 'Server: BogusServer 1.0\r\n',
206 'Content-Length: 10',
209 'Content-Length: 10',
207 '\r\n\r\n'
210 '\r\n\r\n'
208 '1234567890'],
211 '1234567890'],
209 ('GET / HTTP/1.1\r\n'
212 ('GET / HTTP/1.1\r\n'
210 'Host: ::2\r\n'
213 'Host: ::2\r\n'
211 'accept-encoding: identity\r\n\r\n'),
214 'accept-encoding: identity\r\n\r\n'),
212 '1234567890')
215 '1234567890')
213 self._run_simple_test('[::3]:443',
216 self._run_simple_test('[::3]:443',
214 ['HTTP/1.1 200 OK\r\n',
217 ['HTTP/1.1 200 OK\r\n',
215 'Server: BogusServer 1.0\r\n',
218 'Server: BogusServer 1.0\r\n',
216 'Content-Length: 10',
219 'Content-Length: 10',
217 '\r\n\r\n'
220 '\r\n\r\n'
218 '1234567890'],
221 '1234567890'],
219 ('GET / HTTP/1.1\r\n'
222 ('GET / HTTP/1.1\r\n'
220 'Host: ::3\r\n'
223 'Host: ::3\r\n'
221 'accept-encoding: identity\r\n\r\n'),
224 'accept-encoding: identity\r\n\r\n'),
222 '1234567890')
225 '1234567890')
223
226
224 def doPost(self, con, expect_body, body_to_send='This is some POST data'):
227 def doPost(self, con, expect_body, body_to_send='This is some POST data'):
225 con.request('POST', '/', body=body_to_send,
228 con.request('POST', '/', body=body_to_send,
226 expect_continue=True)
229 expect_continue=True)
227 expected_req = ('POST / HTTP/1.1\r\n'
230 expected_req = ('POST / HTTP/1.1\r\n'
228 'Host: 1.2.3.4\r\n'
231 'Host: 1.2.3.4\r\n'
229 'content-length: %d\r\n'
232 'content-length: %d\r\n'
230 'Expect: 100-Continue\r\n'
233 'Expect: 100-Continue\r\n'
231 'accept-encoding: identity\r\n\r\n' %
234 'accept-encoding: identity\r\n\r\n' %
232 len(body_to_send))
235 len(body_to_send))
233 if expect_body:
236 if expect_body:
234 expected_req += body_to_send
237 expected_req += body_to_send
235 return expected_req
238 return expected_req
236
239
237 def testEarlyContinueResponse(self):
240 def testEarlyContinueResponse(self):
238 con = http.HTTPConnection('1.2.3.4:80')
241 con = http.HTTPConnection('1.2.3.4:80')
239 con._connect()
242 con._connect()
240 sock = con.sock
243 sock = con.sock
241 sock.data = ['HTTP/1.1 403 Forbidden\r\n',
244 sock.data = ['HTTP/1.1 403 Forbidden\r\n',
242 'Server: BogusServer 1.0\r\n',
245 'Server: BogusServer 1.0\r\n',
243 'Content-Length: 18',
246 'Content-Length: 18',
244 '\r\n\r\n'
247 '\r\n\r\n'
245 "You can't do that."]
248 "You can't do that."]
246 expected_req = self.doPost(con, expect_body=False)
249 expected_req = self.doPost(con, expect_body=False)
247 self.assertEqual(('1.2.3.4', 80), sock.sa)
250 self.assertEqual(('1.2.3.4', 80), sock.sa)
248 self.assertStringEqual(expected_req, sock.sent)
251 self.assertStringEqual(expected_req, sock.sent)
249 self.assertEqual("You can't do that.", con.getresponse().read())
252 self.assertEqual("You can't do that.", con.getresponse().read())
250 self.assertEqual(sock.closed, True)
253 self.assertEqual(sock.closed, True)
251
254
252 def testDeniedAfterContinueTimeoutExpires(self):
255 def testDeniedAfterContinueTimeoutExpires(self):
253 con = http.HTTPConnection('1.2.3.4:80')
256 con = http.HTTPConnection('1.2.3.4:80')
254 con._connect()
257 con._connect()
255 sock = con.sock
258 sock = con.sock
256 sock.data = ['HTTP/1.1 403 Forbidden\r\n',
259 sock.data = ['HTTP/1.1 403 Forbidden\r\n',
257 'Server: BogusServer 1.0\r\n',
260 'Server: BogusServer 1.0\r\n',
258 'Content-Length: 18\r\n',
261 'Content-Length: 18\r\n',
259 'Connection: close',
262 'Connection: close',
260 '\r\n\r\n'
263 '\r\n\r\n'
261 "You can't do that."]
264 "You can't do that."]
262 sock.read_wait_sentinel = 'Dear server, send response!'
265 sock.read_wait_sentinel = 'Dear server, send response!'
263 sock.close_on_empty = True
266 sock.close_on_empty = True
264 # send enough data out that we'll chunk it into multiple
267 # send enough data out that we'll chunk it into multiple
265 # blocks and the socket will close before we can send the
268 # blocks and the socket will close before we can send the
266 # whole request.
269 # whole request.
267 post_body = ('This is some POST data\n' * 1024 * 32 +
270 post_body = ('This is some POST data\n' * 1024 * 32 +
268 'Dear server, send response!\n' +
271 'Dear server, send response!\n' +
269 'This is some POST data\n' * 1024 * 32)
272 'This is some POST data\n' * 1024 * 32)
270 expected_req = self.doPost(con, expect_body=False,
273 expected_req = self.doPost(con, expect_body=False,
271 body_to_send=post_body)
274 body_to_send=post_body)
272 self.assertEqual(('1.2.3.4', 80), sock.sa)
275 self.assertEqual(('1.2.3.4', 80), sock.sa)
273 self.assert_('POST data\n' in sock.sent)
276 self.assert_('POST data\n' in sock.sent)
274 self.assert_('Dear server, send response!\n' in sock.sent)
277 self.assert_('Dear server, send response!\n' in sock.sent)
275 # We expect not all of our data was sent.
278 # We expect not all of our data was sent.
276 self.assertNotEqual(sock.sent, expected_req)
279 self.assertNotEqual(sock.sent, expected_req)
277 self.assertEqual("You can't do that.", con.getresponse().read())
280 self.assertEqual("You can't do that.", con.getresponse().read())
278 self.assertEqual(sock.closed, True)
281 self.assertEqual(sock.closed, True)
279
282
280 def testPostData(self):
283 def testPostData(self):
281 con = http.HTTPConnection('1.2.3.4:80')
284 con = http.HTTPConnection('1.2.3.4:80')
282 con._connect()
285 con._connect()
283 sock = con.sock
286 sock = con.sock
284 sock.read_wait_sentinel = 'POST data'
287 sock.read_wait_sentinel = 'POST data'
285 sock.early_data = ['HTTP/1.1 100 Co', 'ntinue\r\n\r\n']
288 sock.early_data = ['HTTP/1.1 100 Co', 'ntinue\r\n\r\n']
286 sock.data = ['HTTP/1.1 200 OK\r\n',
289 sock.data = ['HTTP/1.1 200 OK\r\n',
287 'Server: BogusServer 1.0\r\n',
290 'Server: BogusServer 1.0\r\n',
288 'Content-Length: 16',
291 'Content-Length: 16',
289 '\r\n\r\n',
292 '\r\n\r\n',
290 "You can do that."]
293 "You can do that."]
291 expected_req = self.doPost(con, expect_body=True)
294 expected_req = self.doPost(con, expect_body=True)
292 self.assertEqual(('1.2.3.4', 80), sock.sa)
295 self.assertEqual(('1.2.3.4', 80), sock.sa)
293 self.assertEqual(expected_req, sock.sent)
296 self.assertEqual(expected_req, sock.sent)
294 self.assertEqual("You can do that.", con.getresponse().read())
297 self.assertEqual("You can do that.", con.getresponse().read())
295 self.assertEqual(sock.closed, False)
298 self.assertEqual(sock.closed, False)
296
299
297 def testServerWithoutContinue(self):
300 def testServerWithoutContinue(self):
298 con = http.HTTPConnection('1.2.3.4:80')
301 con = http.HTTPConnection('1.2.3.4:80')
299 con._connect()
302 con._connect()
300 sock = con.sock
303 sock = con.sock
301 sock.read_wait_sentinel = 'POST data'
304 sock.read_wait_sentinel = 'POST data'
302 sock.data = ['HTTP/1.1 200 OK\r\n',
305 sock.data = ['HTTP/1.1 200 OK\r\n',
303 'Server: BogusServer 1.0\r\n',
306 'Server: BogusServer 1.0\r\n',
304 'Content-Length: 16',
307 'Content-Length: 16',
305 '\r\n\r\n',
308 '\r\n\r\n',
306 "You can do that."]
309 "You can do that."]
307 expected_req = self.doPost(con, expect_body=True)
310 expected_req = self.doPost(con, expect_body=True)
308 self.assertEqual(('1.2.3.4', 80), sock.sa)
311 self.assertEqual(('1.2.3.4', 80), sock.sa)
309 self.assertEqual(expected_req, sock.sent)
312 self.assertEqual(expected_req, sock.sent)
310 self.assertEqual("You can do that.", con.getresponse().read())
313 self.assertEqual("You can do that.", con.getresponse().read())
311 self.assertEqual(sock.closed, False)
314 self.assertEqual(sock.closed, False)
312
315
313 def testServerWithSlowContinue(self):
316 def testServerWithSlowContinue(self):
314 con = http.HTTPConnection('1.2.3.4:80')
317 con = http.HTTPConnection('1.2.3.4:80')
315 con._connect()
318 con._connect()
316 sock = con.sock
319 sock = con.sock
317 sock.read_wait_sentinel = 'POST data'
320 sock.read_wait_sentinel = 'POST data'
318 sock.data = ['HTTP/1.1 100 ', 'Continue\r\n\r\n',
321 sock.data = ['HTTP/1.1 100 ', 'Continue\r\n\r\n',
319 'HTTP/1.1 200 OK\r\n',
322 'HTTP/1.1 200 OK\r\n',
320 'Server: BogusServer 1.0\r\n',
323 'Server: BogusServer 1.0\r\n',
321 'Content-Length: 16',
324 'Content-Length: 16',
322 '\r\n\r\n',
325 '\r\n\r\n',
323 "You can do that."]
326 "You can do that."]
324 expected_req = self.doPost(con, expect_body=True)
327 expected_req = self.doPost(con, expect_body=True)
325 self.assertEqual(('1.2.3.4', 80), sock.sa)
328 self.assertEqual(('1.2.3.4', 80), sock.sa)
326 self.assertEqual(expected_req, sock.sent)
329 self.assertEqual(expected_req, sock.sent)
327 resp = con.getresponse()
330 resp = con.getresponse()
328 self.assertEqual("You can do that.", resp.read())
331 self.assertEqual("You can do that.", resp.read())
329 self.assertEqual(200, resp.status)
332 self.assertEqual(200, resp.status)
330 self.assertEqual(sock.closed, False)
333 self.assertEqual(sock.closed, False)
331
334
332 def testSlowConnection(self):
335 def testSlowConnection(self):
333 con = http.HTTPConnection('1.2.3.4:80')
336 con = http.HTTPConnection('1.2.3.4:80')
334 con._connect()
337 con._connect()
335 # simulate one byte arriving at a time, to check for various
338 # simulate one byte arriving at a time, to check for various
336 # corner cases
339 # corner cases
337 con.sock.data = list('HTTP/1.1 200 OK\r\n'
340 con.sock.data = list('HTTP/1.1 200 OK\r\n'
338 'Server: BogusServer 1.0\r\n'
341 'Server: BogusServer 1.0\r\n'
339 'Content-Length: 10'
342 'Content-Length: 10'
340 '\r\n\r\n'
343 '\r\n\r\n'
341 '1234567890')
344 '1234567890')
342 con.request('GET', '/')
345 con.request('GET', '/')
343
346
344 expected_req = ('GET / HTTP/1.1\r\n'
347 expected_req = ('GET / HTTP/1.1\r\n'
345 'Host: 1.2.3.4\r\n'
348 'Host: 1.2.3.4\r\n'
346 'accept-encoding: identity\r\n\r\n')
349 'accept-encoding: identity\r\n\r\n')
347
350
348 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
351 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
349 self.assertEqual(expected_req, con.sock.sent)
352 self.assertEqual(expected_req, con.sock.sent)
350 self.assertEqual('1234567890', con.getresponse().read())
353 self.assertEqual('1234567890', con.getresponse().read())
351
354
352 def testTimeout(self):
355 def testTimeout(self):
353 con = http.HTTPConnection('1.2.3.4:80')
356 con = http.HTTPConnection('1.2.3.4:80')
354 con._connect()
357 con._connect()
355 con.sock.data = []
358 con.sock.data = []
356 con.request('GET', '/')
359 con.request('GET', '/')
357 self.assertRaises(http.HTTPTimeoutException,
360 self.assertRaises(http.HTTPTimeoutException,
358 con.getresponse)
361 con.getresponse)
359
362
360 expected_req = ('GET / HTTP/1.1\r\n'
363 expected_req = ('GET / HTTP/1.1\r\n'
361 'Host: 1.2.3.4\r\n'
364 'Host: 1.2.3.4\r\n'
362 'accept-encoding: identity\r\n\r\n')
365 'accept-encoding: identity\r\n\r\n')
363
366
364 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
367 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
365 self.assertEqual(expected_req, con.sock.sent)
368 self.assertEqual(expected_req, con.sock.sent)
366 # no-check-code
369 # no-check-code
General Comments 0
You need to be logged in to leave comments. Login now