##// END OF EJS Templates
httpclient: import revision fc731618702a of py-nonblocking-http
Augie Fackler -
r14376:a75e0f4b default
parent child Browse files
Show More
@@ -1,665 +1,698
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
116 Note that if this is a connection where complete means the
117 socket is closed, this will nearly always return False, even
118 in cases where all the data has actually been loaded.
115 """
119 """
116 if self._chunked:
120 if self._chunked:
117 return self._chunked_done
121 return self._chunked_done
118 if self._content_len == _LEN_CLOSE_IS_END:
122 if self._content_len == _LEN_CLOSE_IS_END:
119 return False
123 return False
120 return self._body is not None and len(self._body) >= self._content_len
124 return self._body is not None and len(self._body) >= self._content_len
121
125
122 def readline(self):
126 def readline(self):
123 """Read a single line from the response body.
127 """Read a single line from the response body.
124
128
125 This may block until either a line ending is found or the
129 This may block until either a line ending is found or the
126 response is complete.
130 response is complete.
127 """
131 """
128 eol = self._body.find('\n', self._read_location)
132 eol = self._body.find('\n', self._read_location)
129 while eol == -1 and not self.complete():
133 while eol == -1 and not self.complete():
130 self._select()
134 self._select()
131 eol = self._body.find('\n', self._read_location)
135 eol = self._body.find('\n', self._read_location)
132 if eol != -1:
136 if eol != -1:
133 eol += 1
137 eol += 1
134 else:
138 else:
135 eol = len(self._body)
139 eol = len(self._body)
136 data = self._body[self._read_location:eol]
140 data = self._body[self._read_location:eol]
137 self._read_location = eol
141 self._read_location = eol
138 return data
142 return data
139
143
140 def read(self, length=None):
144 def read(self, length=None):
141 # if length is None, unbounded read
145 # if length is None, unbounded read
142 while (not self.complete() # never select on a finished read
146 while (not self.complete() # never select on a finished read
143 and (not length # unbounded, so we wait for complete()
147 and (not length # unbounded, so we wait for complete()
144 or (self._read_location + length) > len(self._body))):
148 or (self._read_location + length) > len(self._body))):
145 self._select()
149 self._select()
146 if not length:
150 if not length:
147 length = len(self._body) - self._read_location
151 length = len(self._body) - self._read_location
148 elif len(self._body) < (self._read_location + length):
152 elif len(self._body) < (self._read_location + length):
149 length = len(self._body) - self._read_location
153 length = len(self._body) - self._read_location
150 r = self._body[self._read_location:self._read_location + length]
154 r = self._body[self._read_location:self._read_location + length]
151 self._read_location += len(r)
155 self._read_location += len(r)
152 if self.complete() and self.will_close:
156 if self.complete() and self.will_close:
153 self.sock.close()
157 self.sock.close()
154 return r
158 return r
155
159
156 def _select(self):
160 def _select(self):
157 r, _, _ = select.select([self.sock], [], [], self._timeout)
161 r, _, _ = select.select([self.sock], [], [], self._timeout)
158 if not r:
162 if not r:
159 # socket was not readable. If the response is not complete
163 # socket was not readable. If the response is not complete
160 # and we're not a _LEN_CLOSE_IS_END response, raise a timeout.
164 # 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,
165 # If we are a _LEN_CLOSE_IS_END response and we have no data,
162 # raise a timeout.
166 # raise a timeout.
163 if not (self.complete() or
167 if not (self.complete() or
164 (self._content_len == _LEN_CLOSE_IS_END and self._body)):
168 (self._content_len == _LEN_CLOSE_IS_END and self._body)):
165 logger.info('timed out with timeout of %s', self._timeout)
169 logger.info('timed out with timeout of %s', self._timeout)
166 raise HTTPTimeoutException('timeout reading data')
170 raise HTTPTimeoutException('timeout reading data')
167 logger.info('cl: %r body: %r', self._content_len, self._body)
171 logger.info('cl: %r body: %r', self._content_len, self._body)
168 try:
172 try:
169 data = self.sock.recv(INCOMING_BUFFER_SIZE)
173 data = self.sock.recv(INCOMING_BUFFER_SIZE)
170 except socket.sslerror, e:
174 except socket.sslerror, e:
171 if e.args[0] != socket.SSL_ERROR_WANT_READ:
175 if e.args[0] != socket.SSL_ERROR_WANT_READ:
172 raise
176 raise
173 logger.debug('SSL_WANT_READ in _select, should retry later')
177 logger.debug('SSL_WANT_READ in _select, should retry later')
174 return True
178 return True
175 logger.debug('response read %d data during _select', len(data))
179 logger.debug('response read %d data during _select', len(data))
176 if not data:
180 if not data:
177 if not self.headers:
181 if self.headers and self._content_len == _LEN_CLOSE_IS_END:
178 self._load_response(self._end_headers)
179 self._content_len = 0
180 elif self._content_len == _LEN_CLOSE_IS_END:
181 self._content_len = len(self._body)
182 self._content_len = len(self._body)
182 return False
183 return False
183 else:
184 else:
184 self._load_response(data)
185 self._load_response(data)
185 return True
186 return True
186
187
187 def _chunked_parsedata(self, data):
188 def _chunked_parsedata(self, data):
188 if self._chunked_preloaded_block:
189 if self._chunked_preloaded_block:
189 data = self._chunked_preloaded_block + data
190 data = self._chunked_preloaded_block + data
190 self._chunked_preloaded_block = None
191 self._chunked_preloaded_block = None
191 while data:
192 while data:
192 logger.debug('looping with %d data remaining', len(data))
193 logger.debug('looping with %d data remaining', len(data))
193 # Slice out anything we should skip
194 # Slice out anything we should skip
194 if self._chunked_skip_bytes:
195 if self._chunked_skip_bytes:
195 if len(data) <= self._chunked_skip_bytes:
196 if len(data) <= self._chunked_skip_bytes:
196 self._chunked_skip_bytes -= len(data)
197 self._chunked_skip_bytes -= len(data)
197 data = ''
198 data = ''
198 break
199 break
199 else:
200 else:
200 data = data[self._chunked_skip_bytes:]
201 data = data[self._chunked_skip_bytes:]
201 self._chunked_skip_bytes = 0
202 self._chunked_skip_bytes = 0
202
203
203 # determine how much is until the next chunk
204 # determine how much is until the next chunk
204 if self._chunked_until_next:
205 if self._chunked_until_next:
205 amt = self._chunked_until_next
206 amt = self._chunked_until_next
206 logger.debug('reading remaining %d of existing chunk', amt)
207 logger.debug('reading remaining %d of existing chunk', amt)
207 self._chunked_until_next = 0
208 self._chunked_until_next = 0
208 body = data
209 body = data
209 else:
210 else:
210 try:
211 try:
211 amt, body = data.split(self._eol, 1)
212 amt, body = data.split(self._eol, 1)
212 except ValueError:
213 except ValueError:
213 self._chunked_preloaded_block = data
214 self._chunked_preloaded_block = data
214 logger.debug('saving %r as a preloaded block for chunked',
215 logger.debug('saving %r as a preloaded block for chunked',
215 self._chunked_preloaded_block)
216 self._chunked_preloaded_block)
216 return
217 return
217 amt = int(amt, base=16)
218 amt = int(amt, base=16)
218 logger.debug('reading chunk of length %d', amt)
219 logger.debug('reading chunk of length %d', amt)
219 if amt == 0:
220 if amt == 0:
220 self._chunked_done = True
221 self._chunked_done = True
221
222
222 # read through end of what we have or the chunk
223 # read through end of what we have or the chunk
223 self._body += body[:amt]
224 self._body += body[:amt]
224 if len(body) >= amt:
225 if len(body) >= amt:
225 data = body[amt:]
226 data = body[amt:]
226 self._chunked_skip_bytes = len(self._eol)
227 self._chunked_skip_bytes = len(self._eol)
227 else:
228 else:
228 self._chunked_until_next = amt - len(body)
229 self._chunked_until_next = amt - len(body)
229 self._chunked_skip_bytes = 0
230 self._chunked_skip_bytes = 0
230 data = ''
231 data = ''
231
232
232 def _load_response(self, data):
233 def _load_response(self, data):
233 if self._chunked:
234 if self._chunked:
234 self._chunked_parsedata(data)
235 self._chunked_parsedata(data)
235 return
236 return
236 elif self._body is not None:
237 elif self._body is not None:
237 self._body += data
238 self._body += data
238 return
239 return
239
240
240 # We haven't seen end of headers yet
241 # We haven't seen end of headers yet
241 self.raw_response += data
242 self.raw_response += data
242 # This is a bogus server with bad line endings
243 # This is a bogus server with bad line endings
243 if self._eol not in self.raw_response:
244 if self._eol not in self.raw_response:
244 for bad_eol in ('\n', '\r'):
245 for bad_eol in ('\n', '\r'):
245 if (bad_eol in self.raw_response
246 if (bad_eol in self.raw_response
246 # verify that bad_eol is not the end of the incoming data
247 # verify that bad_eol is not the end of the incoming data
247 # as this could be a response line that just got
248 # as this could be a response line that just got
248 # split between \r and \n.
249 # split between \r and \n.
249 and (self.raw_response.index(bad_eol) <
250 and (self.raw_response.index(bad_eol) <
250 (len(self.raw_response) - 1))):
251 (len(self.raw_response) - 1))):
251 logger.info('bogus line endings detected, '
252 logger.info('bogus line endings detected, '
252 'using %r for EOL', bad_eol)
253 'using %r for EOL', bad_eol)
253 self._eol = bad_eol
254 self._eol = bad_eol
254 break
255 break
255 # exit early if not at end of headers
256 # exit early if not at end of headers
256 if self._end_headers not in self.raw_response or self.headers:
257 if self._end_headers not in self.raw_response or self.headers:
257 return
258 return
258
259
259 # handle 100-continue response
260 # handle 100-continue response
260 hdrs, body = self.raw_response.split(self._end_headers, 1)
261 hdrs, body = self.raw_response.split(self._end_headers, 1)
261 http_ver, status = hdrs.split(' ', 1)
262 http_ver, status = hdrs.split(' ', 1)
262 if status.startswith('100'):
263 if status.startswith('100'):
263 self.raw_response = body
264 self.raw_response = body
264 logger.debug('continue seen, setting body to %r', body)
265 logger.debug('continue seen, setting body to %r', body)
265 return
266 return
266
267
267 # arriving here means we should parse response headers
268 # arriving here means we should parse response headers
268 # as all headers have arrived completely
269 # as all headers have arrived completely
269 hdrs, body = self.raw_response.split(self._end_headers, 1)
270 hdrs, body = self.raw_response.split(self._end_headers, 1)
270 del self.raw_response
271 del self.raw_response
271 if self._eol in hdrs:
272 if self._eol in hdrs:
272 self.status_line, hdrs = hdrs.split(self._eol, 1)
273 self.status_line, hdrs = hdrs.split(self._eol, 1)
273 else:
274 else:
274 self.status_line = hdrs
275 self.status_line = hdrs
275 hdrs = ''
276 hdrs = ''
276 # TODO HTTP < 1.0 support
277 # TODO HTTP < 1.0 support
277 (self.http_version, self.status,
278 (self.http_version, self.status,
278 self.reason) = self.status_line.split(' ', 2)
279 self.reason) = self.status_line.split(' ', 2)
279 self.status = int(self.status)
280 self.status = int(self.status)
280 if self._eol != EOL:
281 if self._eol != EOL:
281 hdrs = hdrs.replace(self._eol, '\r\n')
282 hdrs = hdrs.replace(self._eol, '\r\n')
282 headers = rfc822.Message(cStringIO.StringIO(hdrs))
283 headers = rfc822.Message(cStringIO.StringIO(hdrs))
283 if HDR_CONTENT_LENGTH in headers:
284 if HDR_CONTENT_LENGTH in headers:
284 self._content_len = int(headers[HDR_CONTENT_LENGTH])
285 self._content_len = int(headers[HDR_CONTENT_LENGTH])
285 if self.http_version == HTTP_VER_1_0:
286 if self.http_version == HTTP_VER_1_0:
286 self.will_close = True
287 self.will_close = True
287 elif HDR_CONNECTION_CTRL in headers:
288 elif HDR_CONNECTION_CTRL in headers:
288 self.will_close = (
289 self.will_close = (
289 headers[HDR_CONNECTION_CTRL].lower() == CONNECTION_CLOSE)
290 headers[HDR_CONNECTION_CTRL].lower() == CONNECTION_CLOSE)
290 if self._content_len == 0:
291 if self._content_len == 0:
291 self._content_len = _LEN_CLOSE_IS_END
292 self._content_len = _LEN_CLOSE_IS_END
292 if (HDR_XFER_ENCODING in headers
293 if (HDR_XFER_ENCODING in headers
293 and headers[HDR_XFER_ENCODING].lower() == XFER_ENCODING_CHUNKED):
294 and headers[HDR_XFER_ENCODING].lower() == XFER_ENCODING_CHUNKED):
294 self._body = ''
295 self._body = ''
295 self._chunked_parsedata(body)
296 self._chunked_parsedata(body)
296 self._chunked = True
297 self._chunked = True
297 if self._body is None:
298 if self._body is None:
298 self._body = body
299 self._body = body
299 self.headers = headers
300 self.headers = headers
300
301
301
302
302 class HTTPConnection(object):
303 class HTTPConnection(object):
303 """Connection to a single http server.
304 """Connection to a single http server.
304
305
305 Supports 100-continue and keepalives natively. Uses select() for
306 Supports 100-continue and keepalives natively. Uses select() for
306 non-blocking socket operations.
307 non-blocking socket operations.
307 """
308 """
308 http_version = HTTP_VER_1_1
309 http_version = HTTP_VER_1_1
309 response_class = HTTPResponse
310 response_class = HTTPResponse
310
311
311 def __init__(self, host, port=None, use_ssl=None, ssl_validator=None,
312 def __init__(self, host, port=None, use_ssl=None, ssl_validator=None,
312 timeout=TIMEOUT_DEFAULT,
313 timeout=TIMEOUT_DEFAULT,
313 continue_timeout=TIMEOUT_ASSUME_CONTINUE,
314 continue_timeout=TIMEOUT_ASSUME_CONTINUE,
314 proxy_hostport=None, **ssl_opts):
315 proxy_hostport=None, **ssl_opts):
315 """Create a new HTTPConnection.
316 """Create a new HTTPConnection.
316
317
317 Args:
318 Args:
318 host: The host to which we'll connect.
319 host: The host to which we'll connect.
319 port: Optional. The port over which we'll connect. Default 80 for
320 port: Optional. The port over which we'll connect. Default 80 for
320 non-ssl, 443 for ssl.
321 non-ssl, 443 for ssl.
321 use_ssl: Optional. Wether to use ssl. Defaults to False if port is
322 use_ssl: Optional. Wether to use ssl. Defaults to False if port is
322 not 443, true if port is 443.
323 not 443, true if port is 443.
323 ssl_validator: a function(socket) to validate the ssl cert
324 ssl_validator: a function(socket) to validate the ssl cert
324 timeout: Optional. Connection timeout, default is TIMEOUT_DEFAULT.
325 timeout: Optional. Connection timeout, default is TIMEOUT_DEFAULT.
325 continue_timeout: Optional. Timeout for waiting on an expected
326 continue_timeout: Optional. Timeout for waiting on an expected
326 "100 Continue" response. Default is TIMEOUT_ASSUME_CONTINUE.
327 "100 Continue" response. Default is TIMEOUT_ASSUME_CONTINUE.
327 proxy_hostport: Optional. Tuple of (host, port) to use as an http
328 proxy_hostport: Optional. Tuple of (host, port) to use as an http
328 proxy for the connection. Default is to not use a proxy.
329 proxy for the connection. Default is to not use a proxy.
329 """
330 """
330 if port is None and host.count(':') == 1 or ']:' in host:
331 if port is None and host.count(':') == 1 or ']:' in host:
331 host, port = host.rsplit(':', 1)
332 host, port = host.rsplit(':', 1)
332 port = int(port)
333 port = int(port)
333 if '[' in host:
334 if '[' in host:
334 host = host[1:-1]
335 host = host[1:-1]
335 if use_ssl is None and port is None:
336 if use_ssl is None and port is None:
336 use_ssl = False
337 use_ssl = False
337 port = 80
338 port = 80
338 elif use_ssl is None:
339 elif use_ssl is None:
339 use_ssl = (port == 443)
340 use_ssl = (port == 443)
340 elif port is None:
341 elif port is None:
341 port = (use_ssl and 443 or 80)
342 port = (use_ssl and 443 or 80)
342 self.port = port
343 self.port = port
343 if use_ssl and not socketutil.have_ssl:
344 if use_ssl and not socketutil.have_ssl:
344 raise Exception('ssl requested but unavailable on this Python')
345 raise Exception('ssl requested but unavailable on this Python')
345 self.ssl = use_ssl
346 self.ssl = use_ssl
346 self.ssl_opts = ssl_opts
347 self.ssl_opts = ssl_opts
347 self._ssl_validator = ssl_validator
348 self._ssl_validator = ssl_validator
348 self.host = host
349 self.host = host
349 self.sock = None
350 self.sock = None
350 self._current_response = None
351 self._current_response = None
351 self._current_response_taken = False
352 self._current_response_taken = False
352 if proxy_hostport is None:
353 if proxy_hostport is None:
353 self._proxy_host = self._proxy_port = None
354 self._proxy_host = self._proxy_port = None
354 else:
355 else:
355 self._proxy_host, self._proxy_port = proxy_hostport
356 self._proxy_host, self._proxy_port = proxy_hostport
356
357
357 self.timeout = timeout
358 self.timeout = timeout
358 self.continue_timeout = continue_timeout
359 self.continue_timeout = continue_timeout
359
360
360 def _connect(self):
361 def _connect(self):
361 """Connect to the host and port specified in __init__."""
362 """Connect to the host and port specified in __init__."""
362 if self.sock:
363 if self.sock:
363 return
364 return
364 if self._proxy_host is not None:
365 if self._proxy_host is not None:
365 logger.info('Connecting to http proxy %s:%s',
366 logger.info('Connecting to http proxy %s:%s',
366 self._proxy_host, self._proxy_port)
367 self._proxy_host, self._proxy_port)
367 sock = socketutil.create_connection((self._proxy_host,
368 sock = socketutil.create_connection((self._proxy_host,
368 self._proxy_port))
369 self._proxy_port))
369 if self.ssl:
370 if self.ssl:
370 # TODO proxy header support
371 # TODO proxy header support
371 data = self.buildheaders('CONNECT', '%s:%d' % (self.host,
372 data = self.buildheaders('CONNECT', '%s:%d' % (self.host,
372 self.port),
373 self.port),
373 {}, HTTP_VER_1_0)
374 {}, HTTP_VER_1_0)
374 sock.send(data)
375 sock.send(data)
375 sock.setblocking(0)
376 sock.setblocking(0)
376 r = self.response_class(sock, self.timeout)
377 r = self.response_class(sock, self.timeout)
377 timeout_exc = HTTPTimeoutException(
378 timeout_exc = HTTPTimeoutException(
378 'Timed out waiting for CONNECT response from proxy')
379 'Timed out waiting for CONNECT response from proxy')
379 while not r.complete():
380 while not r.complete():
380 try:
381 try:
381 if not r._select():
382 if not r._select():
382 raise timeout_exc
383 raise timeout_exc
383 except HTTPTimeoutException:
384 except HTTPTimeoutException:
384 # This raise/except pattern looks goofy, but
385 # This raise/except pattern looks goofy, but
385 # _select can raise the timeout as well as the
386 # _select can raise the timeout as well as the
386 # loop body. I wish it wasn't this convoluted,
387 # loop body. I wish it wasn't this convoluted,
387 # but I don't have a better solution
388 # but I don't have a better solution
388 # immediately handy.
389 # immediately handy.
389 raise timeout_exc
390 raise timeout_exc
390 if r.status != 200:
391 if r.status != 200:
391 raise HTTPProxyConnectFailedException(
392 raise HTTPProxyConnectFailedException(
392 'Proxy connection failed: %d %s' % (r.status,
393 'Proxy connection failed: %d %s' % (r.status,
393 r.read()))
394 r.read()))
394 logger.info('CONNECT (for SSL) to %s:%s via proxy succeeded.',
395 logger.info('CONNECT (for SSL) to %s:%s via proxy succeeded.',
395 self.host, self.port)
396 self.host, self.port)
396 else:
397 else:
397 sock = socketutil.create_connection((self.host, self.port))
398 sock = socketutil.create_connection((self.host, self.port))
398 if self.ssl:
399 if self.ssl:
399 logger.debug('wrapping socket for ssl with options %r',
400 logger.debug('wrapping socket for ssl with options %r',
400 self.ssl_opts)
401 self.ssl_opts)
401 sock = socketutil.wrap_socket(sock, **self.ssl_opts)
402 sock = socketutil.wrap_socket(sock, **self.ssl_opts)
402 if self._ssl_validator:
403 if self._ssl_validator:
403 self._ssl_validator(sock)
404 self._ssl_validator(sock)
404 sock.setblocking(0)
405 sock.setblocking(0)
405 self.sock = sock
406 self.sock = sock
406
407
407 def buildheaders(self, method, path, headers, http_ver):
408 def buildheaders(self, method, path, headers, http_ver):
408 if self.ssl and self.port == 443 or self.port == 80:
409 if self.ssl and self.port == 443 or self.port == 80:
409 # default port for protocol, so leave it out
410 # default port for protocol, so leave it out
410 hdrhost = self.host
411 hdrhost = self.host
411 else:
412 else:
412 # include nonstandard port in header
413 # include nonstandard port in header
413 if ':' in self.host: # must be IPv6
414 if ':' in self.host: # must be IPv6
414 hdrhost = '[%s]:%d' % (self.host, self.port)
415 hdrhost = '[%s]:%d' % (self.host, self.port)
415 else:
416 else:
416 hdrhost = '%s:%d' % (self.host, self.port)
417 hdrhost = '%s:%d' % (self.host, self.port)
417 if self._proxy_host and not self.ssl:
418 if self._proxy_host and not self.ssl:
418 # When talking to a regular http proxy we must send the
419 # When talking to a regular http proxy we must send the
419 # full URI, but in all other cases we must not (although
420 # full URI, but in all other cases we must not (although
420 # technically RFC 2616 says servers must accept our
421 # technically RFC 2616 says servers must accept our
421 # request if we screw up, experimentally few do that
422 # request if we screw up, experimentally few do that
422 # correctly.)
423 # correctly.)
423 assert path[0] == '/', 'path must start with a /'
424 assert path[0] == '/', 'path must start with a /'
424 path = 'http://%s%s' % (hdrhost, path)
425 path = 'http://%s%s' % (hdrhost, path)
425 outgoing = ['%s %s %s%s' % (method, path, http_ver, EOL)]
426 outgoing = ['%s %s %s%s' % (method, path, http_ver, EOL)]
426 headers['host'] = ('Host', hdrhost)
427 headers['host'] = ('Host', hdrhost)
427 headers[HDR_ACCEPT_ENCODING] = (HDR_ACCEPT_ENCODING, 'identity')
428 headers[HDR_ACCEPT_ENCODING] = (HDR_ACCEPT_ENCODING, 'identity')
428 for hdr, val in headers.itervalues():
429 for hdr, val in headers.itervalues():
429 outgoing.append('%s: %s%s' % (hdr, val, EOL))
430 outgoing.append('%s: %s%s' % (hdr, val, EOL))
430 outgoing.append(EOL)
431 outgoing.append(EOL)
431 return ''.join(outgoing)
432 return ''.join(outgoing)
432
433
433 def close(self):
434 def close(self):
434 """Close the connection to the server.
435 """Close the connection to the server.
435
436
436 This is a no-op if the connection is already closed. The
437 This is a no-op if the connection is already closed. The
437 connection may automatically close if requessted by the server
438 connection may automatically close if requessted by the server
438 or required by the nature of a response.
439 or required by the nature of a response.
439 """
440 """
440 if self.sock is None:
441 if self.sock is None:
441 return
442 return
442 self.sock.close()
443 self.sock.close()
443 self.sock = None
444 self.sock = None
444 logger.info('closed connection to %s on %s', self.host, self.port)
445 logger.info('closed connection to %s on %s', self.host, self.port)
445
446
446 def busy(self):
447 def busy(self):
447 """Returns True if this connection object is currently in use.
448 """Returns True if this connection object is currently in use.
448
449
449 If a response is still pending, this will return True, even if
450 If a response is still pending, this will return True, even if
450 the request has finished sending. In the future,
451 the request has finished sending. In the future,
451 HTTPConnection may transparently juggle multiple connections
452 HTTPConnection may transparently juggle multiple connections
452 to the server, in which case this will be useful to detect if
453 to the server, in which case this will be useful to detect if
453 any of those connections is ready for use.
454 any of those connections is ready for use.
454 """
455 """
455 cr = self._current_response
456 cr = self._current_response
456 if cr is not None:
457 if cr is not None:
457 if self._current_response_taken:
458 if self._current_response_taken:
458 if cr.will_close:
459 if cr.will_close:
459 self.sock = None
460 self.sock = None
460 self._current_response = None
461 self._current_response = None
461 return False
462 return False
462 elif cr.complete():
463 elif cr.complete():
463 self._current_response = None
464 self._current_response = None
464 return False
465 return False
465 return True
466 return True
466 return False
467 return False
467
468
468 def request(self, method, path, body=None, headers={},
469 def request(self, method, path, body=None, headers={},
469 expect_continue=False):
470 expect_continue=False):
470 """Send a request to the server.
471 """Send a request to the server.
471
472
472 For increased flexibility, this does not return the response
473 For increased flexibility, this does not return the response
473 object. Future versions of HTTPConnection that juggle multiple
474 object. Future versions of HTTPConnection that juggle multiple
474 sockets will be able to send (for example) 5 requests all at
475 sockets will be able to send (for example) 5 requests all at
475 once, and then let the requests arrive as data is
476 once, and then let the requests arrive as data is
476 available. Use the `getresponse()` method to retrieve the
477 available. Use the `getresponse()` method to retrieve the
477 response.
478 response.
478 """
479 """
479 if self.busy():
480 if self.busy():
480 raise httplib.CannotSendRequest(
481 raise httplib.CannotSendRequest(
481 'Can not send another request before '
482 'Can not send another request before '
482 'current response is read!')
483 'current response is read!')
483 self._current_response_taken = False
484 self._current_response_taken = False
484
485
485 logger.info('sending %s request for %s to %s on port %s',
486 logger.info('sending %s request for %s to %s on port %s',
486 method, path, self.host, self.port)
487 method, path, self.host, self.port)
487 hdrs = dict((k.lower(), (k, v)) for k, v in headers.iteritems())
488 hdrs = dict((k.lower(), (k, v)) for k, v in headers.iteritems())
488 if hdrs.get('expect', ('', ''))[1].lower() == '100-continue':
489 if hdrs.get('expect', ('', ''))[1].lower() == '100-continue':
489 expect_continue = True
490 expect_continue = True
490 elif expect_continue:
491 elif expect_continue:
491 hdrs['expect'] = ('Expect', '100-Continue')
492 hdrs['expect'] = ('Expect', '100-Continue')
492
493
493 chunked = False
494 chunked = False
494 if body and HDR_CONTENT_LENGTH not in hdrs:
495 if body and HDR_CONTENT_LENGTH not in hdrs:
495 if getattr(body, '__len__', False):
496 if getattr(body, '__len__', False):
496 hdrs[HDR_CONTENT_LENGTH] = (HDR_CONTENT_LENGTH, len(body))
497 hdrs[HDR_CONTENT_LENGTH] = (HDR_CONTENT_LENGTH, len(body))
497 elif getattr(body, 'read', False):
498 elif getattr(body, 'read', False):
498 hdrs[HDR_XFER_ENCODING] = (HDR_XFER_ENCODING,
499 hdrs[HDR_XFER_ENCODING] = (HDR_XFER_ENCODING,
499 XFER_ENCODING_CHUNKED)
500 XFER_ENCODING_CHUNKED)
500 chunked = True
501 chunked = True
501 else:
502 else:
502 raise BadRequestData('body has no __len__() nor read()')
503 raise BadRequestData('body has no __len__() nor read()')
503
504
504 self._connect()
505 self._connect()
505 outgoing_headers = self.buildheaders(
506 outgoing_headers = self.buildheaders(
506 method, path, hdrs, self.http_version)
507 method, path, hdrs, self.http_version)
507 response = None
508 response = None
508 first = True
509 first = True
509
510
510 def reconnect(where):
511 def reconnect(where):
511 logger.info('reconnecting during %s', where)
512 logger.info('reconnecting during %s', where)
512 self.close()
513 self.close()
513 self._connect()
514 self._connect()
514
515
515 while ((outgoing_headers or body)
516 while ((outgoing_headers or body)
516 and not (response and response.complete())):
517 and not (response and response.complete())):
517 select_timeout = self.timeout
518 select_timeout = self.timeout
518 out = outgoing_headers or body
519 out = outgoing_headers or body
519 blocking_on_continue = False
520 blocking_on_continue = False
520 if expect_continue and not outgoing_headers and not (
521 if expect_continue and not outgoing_headers and not (
521 response and response.headers):
522 response and response.headers):
522 logger.info(
523 logger.info(
523 'waiting up to %s seconds for'
524 'waiting up to %s seconds for'
524 ' continue response from server',
525 ' continue response from server',
525 self.continue_timeout)
526 self.continue_timeout)
526 select_timeout = self.continue_timeout
527 select_timeout = self.continue_timeout
527 blocking_on_continue = True
528 blocking_on_continue = True
528 out = False
529 out = False
529 if out:
530 if out:
530 w = [self.sock]
531 w = [self.sock]
531 else:
532 else:
532 w = []
533 w = []
533 r, w, x = select.select([self.sock], w, [], select_timeout)
534 r, w, x = select.select([self.sock], w, [], select_timeout)
534 # if we were expecting a 100 continue and it's been long
535 # if we were expecting a 100 continue and it's been long
535 # enough, just go ahead and assume it's ok. This is the
536 # enough, just go ahead and assume it's ok. This is the
536 # recommended behavior from the RFC.
537 # recommended behavior from the RFC.
537 if r == w == x == []:
538 if r == w == x == []:
538 if blocking_on_continue:
539 if blocking_on_continue:
539 expect_continue = False
540 expect_continue = False
540 logger.info('no response to continue expectation from '
541 logger.info('no response to continue expectation from '
541 'server, optimistically sending request body')
542 'server, optimistically sending request body')
542 else:
543 else:
543 raise HTTPTimeoutException('timeout sending data')
544 raise HTTPTimeoutException('timeout sending data')
544 # TODO exceptional conditions with select? (what are those be?)
545 # TODO exceptional conditions with select? (what are those be?)
545 # TODO if the response is loading, must we finish sending at all?
546 # TODO if the response is loading, must we finish sending at all?
546 #
547 #
547 # Certainly not if it's going to close the connection and/or
548 # Certainly not if it's going to close the connection and/or
548 # the response is already done...I think.
549 # the response is already done...I think.
549 was_first = first
550 was_first = first
550
551
551 # incoming data
552 # incoming data
552 if r:
553 if r:
553 try:
554 try:
554 try:
555 try:
555 data = r[0].recv(INCOMING_BUFFER_SIZE)
556 data = r[0].recv(INCOMING_BUFFER_SIZE)
556 except socket.sslerror, e:
557 except socket.sslerror, e:
557 if e.args[0] != socket.SSL_ERROR_WANT_READ:
558 if e.args[0] != socket.SSL_ERROR_WANT_READ:
558 raise
559 raise
559 logger.debug(
560 logger.debug(
560 'SSL_WANT_READ while sending data, retrying...')
561 'SSL_WANT_READ while sending data, retrying...')
561 continue
562 continue
562 if not data:
563 if not data:
563 logger.info('socket appears closed in read')
564 logger.info('socket appears closed in read')
564 outgoing_headers = body = None
565 self.sock = None
565 break
566 self._current_response = None
567 # This if/elif ladder is a bit subtle,
568 # comments in each branch should help.
569 if response is not None and (
570 response.complete() or
571 response._content_len == _LEN_CLOSE_IS_END):
572 # Server responded completely and then
573 # closed the socket. We should just shut
574 # things down and let the caller get their
575 # response.
576 logger.info('Got an early response, '
577 'aborting remaining request.')
578 break
579 elif was_first and response is None:
580 # Most likely a keepalive that got killed
581 # on the server's end. Commonly happens
582 # after getting a really large response
583 # from the server.
584 logger.info(
585 'Connection appeared closed in read on first'
586 ' request loop iteration, will retry.')
587 reconnect('read')
588 continue
589 else:
590 # We didn't just send the first data hunk,
591 # and either have a partial response or no
592 # response at all. There's really nothing
593 # meaningful we can do here.
594 raise HTTPStateError(
595 'Connection appears closed after '
596 'some request data was written, but the '
597 'response was missing or incomplete!')
598 logger.debug('read %d bytes in request()', len(data))
566 if response is None:
599 if response is None:
567 response = self.response_class(r[0], self.timeout)
600 response = self.response_class(r[0], self.timeout)
568 response._load_response(data)
601 response._load_response(data)
569 if (response._content_len == _LEN_CLOSE_IS_END
602 # Jump to the next select() call so we load more
570 and len(data) == 0):
603 # data if the server is still sending us content.
571 response._content_len = len(response._body)
604 continue
572 if response.complete():
573 w = []
574 response.will_close = True
575 except socket.error, e:
605 except socket.error, e:
576 if e[0] != errno.EPIPE and not was_first:
606 if e[0] != errno.EPIPE and not was_first:
577 raise
607 raise
578 if (response._content_len
608 if (response._content_len
579 and response._content_len != _LEN_CLOSE_IS_END):
609 and response._content_len != _LEN_CLOSE_IS_END):
580 outgoing_headers = sent_data + outgoing_headers
610 outgoing_headers = sent_data + outgoing_headers
581 reconnect('read')
611 reconnect('read')
582
612
583 # outgoing data
613 # outgoing data
584 if w and out:
614 if w and out:
585 try:
615 try:
586 if getattr(out, 'read', False):
616 if getattr(out, 'read', False):
587 data = out.read(OUTGOING_BUFFER_SIZE)
617 data = out.read(OUTGOING_BUFFER_SIZE)
588 if not data:
618 if not data:
589 continue
619 continue
590 if len(data) < OUTGOING_BUFFER_SIZE:
620 if len(data) < OUTGOING_BUFFER_SIZE:
591 if chunked:
621 if chunked:
592 body = '0' + EOL + EOL
622 body = '0' + EOL + EOL
593 else:
623 else:
594 body = None
624 body = None
595 if chunked:
625 if chunked:
596 out = hex(len(data))[2:] + EOL + data + EOL
626 out = hex(len(data))[2:] + EOL + data + EOL
597 else:
627 else:
598 out = data
628 out = data
599 amt = w[0].send(out)
629 amt = w[0].send(out)
600 except socket.error, e:
630 except socket.error, e:
601 if e[0] == socket.SSL_ERROR_WANT_WRITE and self.ssl:
631 if e[0] == socket.SSL_ERROR_WANT_WRITE and self.ssl:
602 # This means that SSL hasn't flushed its buffer into
632 # This means that SSL hasn't flushed its buffer into
603 # the socket yet.
633 # the socket yet.
604 # TODO: find a way to block on ssl flushing its buffer
634 # TODO: find a way to block on ssl flushing its buffer
605 # similar to selecting on a raw socket.
635 # similar to selecting on a raw socket.
606 continue
636 continue
607 elif (e[0] not in (errno.ECONNRESET, errno.EPIPE)
637 elif (e[0] not in (errno.ECONNRESET, errno.EPIPE)
608 and not first):
638 and not first):
609 raise
639 raise
610 reconnect('write')
640 reconnect('write')
611 amt = self.sock.send(out)
641 amt = self.sock.send(out)
612 logger.debug('sent %d', amt)
642 logger.debug('sent %d', amt)
613 first = False
643 first = False
614 # stash data we think we sent in case the socket breaks
644 # stash data we think we sent in case the socket breaks
615 # when we read from it
645 # when we read from it
616 if was_first:
646 if was_first:
617 sent_data = out[:amt]
647 sent_data = out[:amt]
618 if out is body:
648 if out is body:
619 body = out[amt:]
649 body = out[amt:]
620 else:
650 else:
621 outgoing_headers = out[amt:]
651 outgoing_headers = out[amt:]
622
652
623 # close if the server response said to or responded before eating
653 # close if the server response said to or responded before eating
624 # the whole request
654 # the whole request
625 if response is None:
655 if response is None:
626 response = self.response_class(self.sock, self.timeout)
656 response = self.response_class(self.sock, self.timeout)
627 complete = response.complete()
657 complete = response.complete()
628 data_left = bool(outgoing_headers or body)
658 data_left = bool(outgoing_headers or body)
629 if data_left:
659 if data_left:
630 logger.info('stopped sending request early, '
660 logger.info('stopped sending request early, '
631 'will close the socket to be safe.')
661 'will close the socket to be safe.')
632 response.will_close = True
662 response.will_close = True
633 if response.will_close:
663 if response.will_close:
634 # The socket will be closed by the response, so we disown
664 # The socket will be closed by the response, so we disown
635 # the socket
665 # the socket
636 self.sock = None
666 self.sock = None
637 self._current_response = response
667 self._current_response = response
638
668
639 def getresponse(self):
669 def getresponse(self):
640 if self._current_response is None:
670 if self._current_response is None:
641 raise httplib.ResponseNotReady()
671 raise httplib.ResponseNotReady()
642 r = self._current_response
672 r = self._current_response
643 while r.headers is None:
673 while r.headers is None:
644 r._select()
674 r._select()
645 if r.will_close:
675 if r.will_close:
646 self.sock = None
676 self.sock = None
647 self._current_response = None
677 self._current_response = None
648 elif r.complete():
678 elif r.complete():
649 self._current_response = None
679 self._current_response = None
650 else:
680 else:
651 self._current_response_taken = True
681 self._current_response_taken = True
652 return r
682 return r
653
683
654
684
655 class HTTPTimeoutException(httplib.HTTPException):
685 class HTTPTimeoutException(httplib.HTTPException):
656 """A timeout occurred while waiting on the server."""
686 """A timeout occurred while waiting on the server."""
657
687
658
688
659 class BadRequestData(httplib.HTTPException):
689 class BadRequestData(httplib.HTTPException):
660 """Request body object has neither __len__ nor read."""
690 """Request body object has neither __len__ nor read."""
661
691
662
692
663 class HTTPProxyConnectFailedException(httplib.HTTPException):
693 class HTTPProxyConnectFailedException(httplib.HTTPException):
664 """Connecting to the HTTP proxy failed."""
694 """Connecting to the HTTP proxy failed."""
695
696 class HTTPStateError(httplib.HTTPException):
697 """Invalid internal state encountered."""
665 # no-check-code
698 # no-check-code
@@ -1,356 +1,386
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 socket
29 import unittest
30 import unittest
30
31
31 import http
32 import http
32
33
33 # relative import to ease embedding the library
34 # relative import to ease embedding the library
34 import util
35 import util
35
36
36
37
37 class SimpleHttpTest(util.HttpTestBase, unittest.TestCase):
38 class SimpleHttpTest(util.HttpTestBase, unittest.TestCase):
38
39
39 def _run_simple_test(self, host, server_data, expected_req, expected_data):
40 def _run_simple_test(self, host, server_data, expected_req, expected_data):
40 con = http.HTTPConnection(host)
41 con = http.HTTPConnection(host)
41 con._connect()
42 con._connect()
42 con.sock.data.extend(server_data)
43 con.sock.data = server_data
43 con.request('GET', '/')
44 con.request('GET', '/')
44
45
45 self.assertStringEqual(expected_req, con.sock.sent)
46 self.assertStringEqual(expected_req, con.sock.sent)
46 self.assertEqual(expected_data, con.getresponse().read())
47 self.assertEqual(expected_data, con.getresponse().read())
47
48
48 def test_broken_data_obj(self):
49 def test_broken_data_obj(self):
49 con = http.HTTPConnection('1.2.3.4:80')
50 con = http.HTTPConnection('1.2.3.4:80')
50 con._connect()
51 con._connect()
51 self.assertRaises(http.BadRequestData,
52 self.assertRaises(http.BadRequestData,
52 con.request, 'POST', '/', body=1)
53 con.request, 'POST', '/', body=1)
53
54
54 def test_no_keepalive_http_1_0(self):
55 def test_no_keepalive_http_1_0(self):
55 expected_request_one = """GET /remote/.hg/requires HTTP/1.1
56 expected_request_one = """GET /remote/.hg/requires HTTP/1.1
56 Host: localhost:9999
57 Host: localhost:9999
57 range: bytes=0-
58 range: bytes=0-
58 accept-encoding: identity
59 accept-encoding: identity
59 accept: application/mercurial-0.1
60 accept: application/mercurial-0.1
60 user-agent: mercurial/proto-1.0
61 user-agent: mercurial/proto-1.0
61
62
62 """.replace('\n', '\r\n')
63 """.replace('\n', '\r\n')
63 expected_response_headers = """HTTP/1.0 200 OK
64 expected_response_headers = """HTTP/1.0 200 OK
64 Server: SimpleHTTP/0.6 Python/2.6.1
65 Server: SimpleHTTP/0.6 Python/2.6.1
65 Date: Sun, 01 May 2011 13:56:57 GMT
66 Date: Sun, 01 May 2011 13:56:57 GMT
66 Content-type: application/octet-stream
67 Content-type: application/octet-stream
67 Content-Length: 33
68 Content-Length: 33
68 Last-Modified: Sun, 01 May 2011 13:56:56 GMT
69 Last-Modified: Sun, 01 May 2011 13:56:56 GMT
69
70
70 """.replace('\n', '\r\n')
71 """.replace('\n', '\r\n')
71 expected_response_body = """revlogv1
72 expected_response_body = """revlogv1
72 store
73 store
73 fncache
74 fncache
74 dotencode
75 dotencode
75 """
76 """
76 con = http.HTTPConnection('localhost:9999')
77 con = http.HTTPConnection('localhost:9999')
77 con._connect()
78 con._connect()
78 con.sock.data = [expected_response_headers, expected_response_body]
79 con.sock.data = [expected_response_headers, expected_response_body]
79 con.request('GET', '/remote/.hg/requires',
80 con.request('GET', '/remote/.hg/requires',
80 headers={'accept-encoding': 'identity',
81 headers={'accept-encoding': 'identity',
81 'range': 'bytes=0-',
82 'range': 'bytes=0-',
82 'accept': 'application/mercurial-0.1',
83 'accept': 'application/mercurial-0.1',
83 'user-agent': 'mercurial/proto-1.0',
84 'user-agent': 'mercurial/proto-1.0',
84 })
85 })
85 self.assertStringEqual(expected_request_one, con.sock.sent)
86 self.assertStringEqual(expected_request_one, con.sock.sent)
86 self.assertEqual(con.sock.closed, False)
87 self.assertEqual(con.sock.closed, False)
87 self.assertNotEqual(con.sock.data, [])
88 self.assertNotEqual(con.sock.data, [])
88 self.assert_(con.busy())
89 self.assert_(con.busy())
89 resp = con.getresponse()
90 resp = con.getresponse()
90 self.assertStringEqual(resp.read(), expected_response_body)
91 self.assertStringEqual(resp.read(), expected_response_body)
91 self.failIf(con.busy())
92 self.failIf(con.busy())
92 self.assertEqual(con.sock, None)
93 self.assertEqual(con.sock, None)
93 self.assertEqual(resp.sock.data, [])
94 self.assertEqual(resp.sock.data, [])
94 self.assert_(resp.sock.closed)
95 self.assert_(resp.sock.closed)
95
96
96 def test_multiline_header(self):
97 def test_multiline_header(self):
97 con = http.HTTPConnection('1.2.3.4:80')
98 con = http.HTTPConnection('1.2.3.4:80')
98 con._connect()
99 con._connect()
99 con.sock.data = ['HTTP/1.1 200 OK\r\n',
100 con.sock.data = ['HTTP/1.1 200 OK\r\n',
100 'Server: BogusServer 1.0\r\n',
101 'Server: BogusServer 1.0\r\n',
101 'Multiline: Value\r\n',
102 'Multiline: Value\r\n',
102 ' Rest of value\r\n',
103 ' Rest of value\r\n',
103 'Content-Length: 10\r\n',
104 'Content-Length: 10\r\n',
104 '\r\n'
105 '\r\n'
105 '1234567890'
106 '1234567890'
106 ]
107 ]
107 con.request('GET', '/')
108 con.request('GET', '/')
108
109
109 expected_req = ('GET / HTTP/1.1\r\n'
110 expected_req = ('GET / HTTP/1.1\r\n'
110 'Host: 1.2.3.4\r\n'
111 'Host: 1.2.3.4\r\n'
111 'accept-encoding: identity\r\n\r\n')
112 'accept-encoding: identity\r\n\r\n')
112
113
113 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
114 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
114 self.assertEqual(expected_req, con.sock.sent)
115 self.assertEqual(expected_req, con.sock.sent)
115 resp = con.getresponse()
116 resp = con.getresponse()
116 self.assertEqual('1234567890', resp.read())
117 self.assertEqual('1234567890', resp.read())
117 self.assertEqual(['Value\n Rest of value'],
118 self.assertEqual(['Value\n Rest of value'],
118 resp.headers.getheaders('multiline'))
119 resp.headers.getheaders('multiline'))
119 # Socket should not be closed
120 # Socket should not be closed
120 self.assertEqual(resp.sock.closed, False)
121 self.assertEqual(resp.sock.closed, False)
121 self.assertEqual(con.sock.closed, False)
122 self.assertEqual(con.sock.closed, False)
122
123
123 def testSimpleRequest(self):
124 def testSimpleRequest(self):
124 con = http.HTTPConnection('1.2.3.4:80')
125 con = http.HTTPConnection('1.2.3.4:80')
125 con._connect()
126 con._connect()
126 con.sock.data = ['HTTP/1.1 200 OK\r\n',
127 con.sock.data = ['HTTP/1.1 200 OK\r\n',
127 'Server: BogusServer 1.0\r\n',
128 'Server: BogusServer 1.0\r\n',
128 'MultiHeader: Value\r\n'
129 'MultiHeader: Value\r\n'
129 'MultiHeader: Other Value\r\n'
130 'MultiHeader: Other Value\r\n'
130 'MultiHeader: One More!\r\n'
131 'MultiHeader: One More!\r\n'
131 'Content-Length: 10\r\n',
132 'Content-Length: 10\r\n',
132 '\r\n'
133 '\r\n'
133 '1234567890'
134 '1234567890'
134 ]
135 ]
135 con.request('GET', '/')
136 con.request('GET', '/')
136
137
137 expected_req = ('GET / HTTP/1.1\r\n'
138 expected_req = ('GET / HTTP/1.1\r\n'
138 'Host: 1.2.3.4\r\n'
139 'Host: 1.2.3.4\r\n'
139 'accept-encoding: identity\r\n\r\n')
140 'accept-encoding: identity\r\n\r\n')
140
141
141 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
142 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
142 self.assertEqual(expected_req, con.sock.sent)
143 self.assertEqual(expected_req, con.sock.sent)
143 resp = con.getresponse()
144 resp = con.getresponse()
144 self.assertEqual('1234567890', resp.read())
145 self.assertEqual('1234567890', resp.read())
145 self.assertEqual(['Value', 'Other Value', 'One More!'],
146 self.assertEqual(['Value', 'Other Value', 'One More!'],
146 resp.headers.getheaders('multiheader'))
147 resp.headers.getheaders('multiheader'))
147 self.assertEqual(['BogusServer 1.0'],
148 self.assertEqual(['BogusServer 1.0'],
148 resp.headers.getheaders('server'))
149 resp.headers.getheaders('server'))
149
150
150 def testHeaderlessResponse(self):
151 def testHeaderlessResponse(self):
151 con = http.HTTPConnection('1.2.3.4', use_ssl=False)
152 con = http.HTTPConnection('1.2.3.4', use_ssl=False)
152 con._connect()
153 con._connect()
153 con.sock.data = ['HTTP/1.1 200 OK\r\n',
154 con.sock.data = ['HTTP/1.1 200 OK\r\n',
154 '\r\n'
155 '\r\n'
155 '1234567890'
156 '1234567890'
156 ]
157 ]
157 con.request('GET', '/')
158 con.request('GET', '/')
158
159
159 expected_req = ('GET / HTTP/1.1\r\n'
160 expected_req = ('GET / HTTP/1.1\r\n'
160 'Host: 1.2.3.4\r\n'
161 'Host: 1.2.3.4\r\n'
161 'accept-encoding: identity\r\n\r\n')
162 'accept-encoding: identity\r\n\r\n')
162
163
163 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
164 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
164 self.assertEqual(expected_req, con.sock.sent)
165 self.assertEqual(expected_req, con.sock.sent)
165 resp = con.getresponse()
166 resp = con.getresponse()
166 self.assertEqual('1234567890', resp.read())
167 self.assertEqual('1234567890', resp.read())
167 self.assertEqual({}, dict(resp.headers))
168 self.assertEqual({}, dict(resp.headers))
168 self.assertEqual(resp.status, 200)
169 self.assertEqual(resp.status, 200)
169
170
170 def testReadline(self):
171 def testReadline(self):
171 con = http.HTTPConnection('1.2.3.4')
172 con = http.HTTPConnection('1.2.3.4')
172 con._connect()
173 con._connect()
173 # make sure it trickles in one byte at a time
174 # make sure it trickles in one byte at a time
174 # so that we touch all the cases in readline
175 # so that we touch all the cases in readline
175 con.sock.data = list(''.join(
176 con.sock.data = list(''.join(
176 ['HTTP/1.1 200 OK\r\n',
177 ['HTTP/1.1 200 OK\r\n',
177 'Server: BogusServer 1.0\r\n',
178 'Server: BogusServer 1.0\r\n',
178 'Connection: Close\r\n',
179 'Connection: Close\r\n',
179 '\r\n'
180 '\r\n'
180 '1\n2\nabcdefg\n4\n5']))
181 '1\n2\nabcdefg\n4\n5']))
181
182
182 expected_req = ('GET / HTTP/1.1\r\n'
183 expected_req = ('GET / HTTP/1.1\r\n'
183 'Host: 1.2.3.4\r\n'
184 'Host: 1.2.3.4\r\n'
184 'accept-encoding: identity\r\n\r\n')
185 'accept-encoding: identity\r\n\r\n')
185
186
186 con.request('GET', '/')
187 con.request('GET', '/')
187 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
188 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
188 self.assertEqual(expected_req, con.sock.sent)
189 self.assertEqual(expected_req, con.sock.sent)
189 r = con.getresponse()
190 r = con.getresponse()
190 for expected in ['1\n', '2\n', 'abcdefg\n', '4\n', '5']:
191 for expected in ['1\n', '2\n', 'abcdefg\n', '4\n', '5']:
191 actual = r.readline()
192 actual = r.readline()
192 self.assertEqual(expected, actual,
193 self.assertEqual(expected, actual,
193 'Expected %r, got %r' % (expected, actual))
194 'Expected %r, got %r' % (expected, actual))
194
195
195 def testIPv6(self):
196 def testIPv6(self):
196 self._run_simple_test('[::1]:8221',
197 self._run_simple_test('[::1]:8221',
197 ['HTTP/1.1 200 OK\r\n',
198 ['HTTP/1.1 200 OK\r\n',
198 'Server: BogusServer 1.0\r\n',
199 'Server: BogusServer 1.0\r\n',
199 'Content-Length: 10',
200 'Content-Length: 10',
200 '\r\n\r\n'
201 '\r\n\r\n'
201 '1234567890'],
202 '1234567890'],
202 ('GET / HTTP/1.1\r\n'
203 ('GET / HTTP/1.1\r\n'
203 'Host: [::1]:8221\r\n'
204 'Host: [::1]:8221\r\n'
204 'accept-encoding: identity\r\n\r\n'),
205 'accept-encoding: identity\r\n\r\n'),
205 '1234567890')
206 '1234567890')
206 self._run_simple_test('::2',
207 self._run_simple_test('::2',
207 ['HTTP/1.1 200 OK\r\n',
208 ['HTTP/1.1 200 OK\r\n',
208 'Server: BogusServer 1.0\r\n',
209 'Server: BogusServer 1.0\r\n',
209 'Content-Length: 10',
210 'Content-Length: 10',
210 '\r\n\r\n'
211 '\r\n\r\n'
211 '1234567890'],
212 '1234567890'],
212 ('GET / HTTP/1.1\r\n'
213 ('GET / HTTP/1.1\r\n'
213 'Host: ::2\r\n'
214 'Host: ::2\r\n'
214 'accept-encoding: identity\r\n\r\n'),
215 'accept-encoding: identity\r\n\r\n'),
215 '1234567890')
216 '1234567890')
216 self._run_simple_test('[::3]:443',
217 self._run_simple_test('[::3]:443',
217 ['HTTP/1.1 200 OK\r\n',
218 ['HTTP/1.1 200 OK\r\n',
218 'Server: BogusServer 1.0\r\n',
219 'Server: BogusServer 1.0\r\n',
219 'Content-Length: 10',
220 'Content-Length: 10',
220 '\r\n\r\n'
221 '\r\n\r\n'
221 '1234567890'],
222 '1234567890'],
222 ('GET / HTTP/1.1\r\n'
223 ('GET / HTTP/1.1\r\n'
223 'Host: ::3\r\n'
224 'Host: ::3\r\n'
224 'accept-encoding: identity\r\n\r\n'),
225 'accept-encoding: identity\r\n\r\n'),
225 '1234567890')
226 '1234567890')
226
227
227 def testEarlyContinueResponse(self):
228 def testEarlyContinueResponse(self):
228 con = http.HTTPConnection('1.2.3.4:80')
229 con = http.HTTPConnection('1.2.3.4:80')
229 con._connect()
230 con._connect()
230 sock = con.sock
231 sock = con.sock
231 sock.data = ['HTTP/1.1 403 Forbidden\r\n',
232 sock.data = ['HTTP/1.1 403 Forbidden\r\n',
232 'Server: BogusServer 1.0\r\n',
233 'Server: BogusServer 1.0\r\n',
233 'Content-Length: 18',
234 'Content-Length: 18',
234 '\r\n\r\n'
235 '\r\n\r\n'
235 "You can't do that."]
236 "You can't do that."]
236 expected_req = self.doPost(con, expect_body=False)
237 expected_req = self.doPost(con, expect_body=False)
237 self.assertEqual(('1.2.3.4', 80), sock.sa)
238 self.assertEqual(('1.2.3.4', 80), sock.sa)
238 self.assertStringEqual(expected_req, sock.sent)
239 self.assertStringEqual(expected_req, sock.sent)
239 self.assertEqual("You can't do that.", con.getresponse().read())
240 self.assertEqual("You can't do that.", con.getresponse().read())
240 self.assertEqual(sock.closed, True)
241 self.assertEqual(sock.closed, True)
241
242
242 def testDeniedAfterContinueTimeoutExpires(self):
243 def testDeniedAfterContinueTimeoutExpires(self):
243 con = http.HTTPConnection('1.2.3.4:80')
244 con = http.HTTPConnection('1.2.3.4:80')
244 con._connect()
245 con._connect()
245 sock = con.sock
246 sock = con.sock
246 sock.data = ['HTTP/1.1 403 Forbidden\r\n',
247 sock.data = ['HTTP/1.1 403 Forbidden\r\n',
247 'Server: BogusServer 1.0\r\n',
248 'Server: BogusServer 1.0\r\n',
248 'Content-Length: 18\r\n',
249 'Content-Length: 18\r\n',
249 'Connection: close',
250 'Connection: close',
250 '\r\n\r\n'
251 '\r\n\r\n'
251 "You can't do that."]
252 "You can't do that."]
252 sock.read_wait_sentinel = 'Dear server, send response!'
253 sock.read_wait_sentinel = 'Dear server, send response!'
253 sock.close_on_empty = True
254 sock.close_on_empty = True
254 # send enough data out that we'll chunk it into multiple
255 # send enough data out that we'll chunk it into multiple
255 # blocks and the socket will close before we can send the
256 # blocks and the socket will close before we can send the
256 # whole request.
257 # whole request.
257 post_body = ('This is some POST data\n' * 1024 * 32 +
258 post_body = ('This is some POST data\n' * 1024 * 32 +
258 'Dear server, send response!\n' +
259 'Dear server, send response!\n' +
259 'This is some POST data\n' * 1024 * 32)
260 'This is some POST data\n' * 1024 * 32)
260 expected_req = self.doPost(con, expect_body=False,
261 expected_req = self.doPost(con, expect_body=False,
261 body_to_send=post_body)
262 body_to_send=post_body)
262 self.assertEqual(('1.2.3.4', 80), sock.sa)
263 self.assertEqual(('1.2.3.4', 80), sock.sa)
263 self.assert_('POST data\n' in sock.sent)
264 self.assert_('POST data\n' in sock.sent)
264 self.assert_('Dear server, send response!\n' in sock.sent)
265 self.assert_('Dear server, send response!\n' in sock.sent)
265 # We expect not all of our data was sent.
266 # We expect not all of our data was sent.
266 self.assertNotEqual(sock.sent, expected_req)
267 self.assertNotEqual(sock.sent, expected_req)
267 self.assertEqual("You can't do that.", con.getresponse().read())
268 self.assertEqual("You can't do that.", con.getresponse().read())
268 self.assertEqual(sock.closed, True)
269 self.assertEqual(sock.closed, True)
269
270
270 def testPostData(self):
271 def testPostData(self):
271 con = http.HTTPConnection('1.2.3.4:80')
272 con = http.HTTPConnection('1.2.3.4:80')
272 con._connect()
273 con._connect()
273 sock = con.sock
274 sock = con.sock
274 sock.read_wait_sentinel = 'POST data'
275 sock.read_wait_sentinel = 'POST data'
275 sock.early_data = ['HTTP/1.1 100 Co', 'ntinue\r\n\r\n']
276 sock.early_data = ['HTTP/1.1 100 Co', 'ntinue\r\n\r\n']
276 sock.data = ['HTTP/1.1 200 OK\r\n',
277 sock.data = ['HTTP/1.1 200 OK\r\n',
277 'Server: BogusServer 1.0\r\n',
278 'Server: BogusServer 1.0\r\n',
278 'Content-Length: 16',
279 'Content-Length: 16',
279 '\r\n\r\n',
280 '\r\n\r\n',
280 "You can do that."]
281 "You can do that."]
281 expected_req = self.doPost(con, expect_body=True)
282 expected_req = self.doPost(con, expect_body=True)
282 self.assertEqual(('1.2.3.4', 80), sock.sa)
283 self.assertEqual(('1.2.3.4', 80), sock.sa)
283 self.assertEqual(expected_req, sock.sent)
284 self.assertEqual(expected_req, sock.sent)
284 self.assertEqual("You can do that.", con.getresponse().read())
285 self.assertEqual("You can do that.", con.getresponse().read())
285 self.assertEqual(sock.closed, False)
286 self.assertEqual(sock.closed, False)
286
287
287 def testServerWithoutContinue(self):
288 def testServerWithoutContinue(self):
288 con = http.HTTPConnection('1.2.3.4:80')
289 con = http.HTTPConnection('1.2.3.4:80')
289 con._connect()
290 con._connect()
290 sock = con.sock
291 sock = con.sock
291 sock.read_wait_sentinel = 'POST data'
292 sock.read_wait_sentinel = 'POST data'
292 sock.data = ['HTTP/1.1 200 OK\r\n',
293 sock.data = ['HTTP/1.1 200 OK\r\n',
293 'Server: BogusServer 1.0\r\n',
294 'Server: BogusServer 1.0\r\n',
294 'Content-Length: 16',
295 'Content-Length: 16',
295 '\r\n\r\n',
296 '\r\n\r\n',
296 "You can do that."]
297 "You can do that."]
297 expected_req = self.doPost(con, expect_body=True)
298 expected_req = self.doPost(con, expect_body=True)
298 self.assertEqual(('1.2.3.4', 80), sock.sa)
299 self.assertEqual(('1.2.3.4', 80), sock.sa)
299 self.assertEqual(expected_req, sock.sent)
300 self.assertEqual(expected_req, sock.sent)
300 self.assertEqual("You can do that.", con.getresponse().read())
301 self.assertEqual("You can do that.", con.getresponse().read())
301 self.assertEqual(sock.closed, False)
302 self.assertEqual(sock.closed, False)
302
303
303 def testServerWithSlowContinue(self):
304 def testServerWithSlowContinue(self):
304 con = http.HTTPConnection('1.2.3.4:80')
305 con = http.HTTPConnection('1.2.3.4:80')
305 con._connect()
306 con._connect()
306 sock = con.sock
307 sock = con.sock
307 sock.read_wait_sentinel = 'POST data'
308 sock.read_wait_sentinel = 'POST data'
308 sock.data = ['HTTP/1.1 100 ', 'Continue\r\n\r\n',
309 sock.data = ['HTTP/1.1 100 ', 'Continue\r\n\r\n',
309 'HTTP/1.1 200 OK\r\n',
310 'HTTP/1.1 200 OK\r\n',
310 'Server: BogusServer 1.0\r\n',
311 'Server: BogusServer 1.0\r\n',
311 'Content-Length: 16',
312 'Content-Length: 16',
312 '\r\n\r\n',
313 '\r\n\r\n',
313 "You can do that."]
314 "You can do that."]
314 expected_req = self.doPost(con, expect_body=True)
315 expected_req = self.doPost(con, expect_body=True)
315 self.assertEqual(('1.2.3.4', 80), sock.sa)
316 self.assertEqual(('1.2.3.4', 80), sock.sa)
316 self.assertEqual(expected_req, sock.sent)
317 self.assertEqual(expected_req, sock.sent)
317 resp = con.getresponse()
318 resp = con.getresponse()
318 self.assertEqual("You can do that.", resp.read())
319 self.assertEqual("You can do that.", resp.read())
319 self.assertEqual(200, resp.status)
320 self.assertEqual(200, resp.status)
320 self.assertEqual(sock.closed, False)
321 self.assertEqual(sock.closed, False)
321
322
322 def testSlowConnection(self):
323 def testSlowConnection(self):
323 con = http.HTTPConnection('1.2.3.4:80')
324 con = http.HTTPConnection('1.2.3.4:80')
324 con._connect()
325 con._connect()
325 # simulate one byte arriving at a time, to check for various
326 # simulate one byte arriving at a time, to check for various
326 # corner cases
327 # corner cases
327 con.sock.data = list('HTTP/1.1 200 OK\r\n'
328 con.sock.data = list('HTTP/1.1 200 OK\r\n'
328 'Server: BogusServer 1.0\r\n'
329 'Server: BogusServer 1.0\r\n'
329 'Content-Length: 10'
330 'Content-Length: 10'
330 '\r\n\r\n'
331 '\r\n\r\n'
331 '1234567890')
332 '1234567890')
332 con.request('GET', '/')
333 con.request('GET', '/')
333
334
334 expected_req = ('GET / HTTP/1.1\r\n'
335 expected_req = ('GET / HTTP/1.1\r\n'
335 'Host: 1.2.3.4\r\n'
336 'Host: 1.2.3.4\r\n'
336 'accept-encoding: identity\r\n\r\n')
337 'accept-encoding: identity\r\n\r\n')
337
338
338 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
339 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
339 self.assertEqual(expected_req, con.sock.sent)
340 self.assertEqual(expected_req, con.sock.sent)
340 self.assertEqual('1234567890', con.getresponse().read())
341 self.assertEqual('1234567890', con.getresponse().read())
341
342
342 def testTimeout(self):
343 def testTimeout(self):
343 con = http.HTTPConnection('1.2.3.4:80')
344 con = http.HTTPConnection('1.2.3.4:80')
344 con._connect()
345 con._connect()
345 con.sock.data = []
346 con.sock.data = []
346 con.request('GET', '/')
347 con.request('GET', '/')
347 self.assertRaises(http.HTTPTimeoutException,
348 self.assertRaises(http.HTTPTimeoutException,
348 con.getresponse)
349 con.getresponse)
349
350
350 expected_req = ('GET / HTTP/1.1\r\n'
351 expected_req = ('GET / HTTP/1.1\r\n'
351 'Host: 1.2.3.4\r\n'
352 'Host: 1.2.3.4\r\n'
352 'accept-encoding: identity\r\n\r\n')
353 'accept-encoding: identity\r\n\r\n')
353
354
354 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
355 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
355 self.assertEqual(expected_req, con.sock.sent)
356 self.assertEqual(expected_req, con.sock.sent)
357
358 def test_conn_keep_alive_but_server_close_anyway(self):
359 sockets = []
360 def closingsocket(*args, **kwargs):
361 s = util.MockSocket(*args, **kwargs)
362 sockets.append(s)
363 s.data = ['HTTP/1.1 200 OK\r\n',
364 'Server: BogusServer 1.0\r\n',
365 'Connection: Keep-Alive\r\n',
366 'Content-Length: 16',
367 '\r\n\r\n',
368 'You can do that.']
369 s.close_on_empty = True
370 return s
371
372 socket.socket = closingsocket
373 con = http.HTTPConnection('1.2.3.4:80')
374 con._connect()
375 con.request('GET', '/')
376 r1 = con.getresponse()
377 r1.read()
378 self.assertFalse(con.sock.closed)
379 self.assert_(con.sock.remote_closed)
380 con.request('GET', '/')
381 self.assertEqual(2, len(sockets))
382
383 def test_no_response_raises_response_not_ready(self):
384 con = http.HTTPConnection('foo')
385 self.assertRaises(http.httplib.ResponseNotReady, con.getresponse)
356 # no-check-code
386 # no-check-code
@@ -1,137 +1,137
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 cStringIO
29 import cStringIO
30 import unittest
30 import unittest
31
31
32 import http
32 import http
33
33
34 # relative import to ease embedding the library
34 # relative import to ease embedding the library
35 import util
35 import util
36
36
37
37
38 def chunkedblock(x, eol='\r\n'):
38 def chunkedblock(x, eol='\r\n'):
39 r"""Make a chunked transfer-encoding block.
39 r"""Make a chunked transfer-encoding block.
40
40
41 >>> chunkedblock('hi')
41 >>> chunkedblock('hi')
42 '2\r\nhi\r\n'
42 '2\r\nhi\r\n'
43 >>> chunkedblock('hi' * 10)
43 >>> chunkedblock('hi' * 10)
44 '14\r\nhihihihihihihihihihi\r\n'
44 '14\r\nhihihihihihihihihihi\r\n'
45 >>> chunkedblock('hi', eol='\n')
45 >>> chunkedblock('hi', eol='\n')
46 '2\nhi\n'
46 '2\nhi\n'
47 """
47 """
48 return ''.join((hex(len(x))[2:], eol, x, eol))
48 return ''.join((hex(len(x))[2:], eol, x, eol))
49
49
50
50
51 class ChunkedTransferTest(util.HttpTestBase, unittest.TestCase):
51 class ChunkedTransferTest(util.HttpTestBase, unittest.TestCase):
52 def testChunkedUpload(self):
52 def testChunkedUpload(self):
53 con = http.HTTPConnection('1.2.3.4:80')
53 con = http.HTTPConnection('1.2.3.4:80')
54 con._connect()
54 con._connect()
55 sock = con.sock
55 sock = con.sock
56 sock.read_wait_sentinel = 'end-of-body'
56 sock.read_wait_sentinel = '0\r\n\r\n'
57 sock.data = ['HTTP/1.1 200 OK\r\n',
57 sock.data = ['HTTP/1.1 200 OK\r\n',
58 'Server: BogusServer 1.0\r\n',
58 'Server: BogusServer 1.0\r\n',
59 'Content-Length: 6',
59 'Content-Length: 6',
60 '\r\n\r\n',
60 '\r\n\r\n',
61 "Thanks"]
61 "Thanks"]
62
62
63 zz = 'zz\n'
63 zz = 'zz\n'
64 con.request('POST', '/', body=cStringIO.StringIO(
64 con.request('POST', '/', body=cStringIO.StringIO(
65 (zz * (0x8010 / 3)) + 'end-of-body'))
65 (zz * (0x8010 / 3)) + 'end-of-body'))
66 expected_req = ('POST / HTTP/1.1\r\n'
66 expected_req = ('POST / HTTP/1.1\r\n'
67 'transfer-encoding: chunked\r\n'
67 'transfer-encoding: chunked\r\n'
68 'Host: 1.2.3.4\r\n'
68 'Host: 1.2.3.4\r\n'
69 'accept-encoding: identity\r\n\r\n')
69 'accept-encoding: identity\r\n\r\n')
70 expected_req += chunkedblock('zz\n' * (0x8000 / 3) + 'zz')
70 expected_req += chunkedblock('zz\n' * (0x8000 / 3) + 'zz')
71 expected_req += chunkedblock(
71 expected_req += chunkedblock(
72 '\n' + 'zz\n' * ((0x1b - len('end-of-body')) / 3) + 'end-of-body')
72 '\n' + 'zz\n' * ((0x1b - len('end-of-body')) / 3) + 'end-of-body')
73 expected_req += '0\r\n\r\n'
73 expected_req += '0\r\n\r\n'
74 self.assertEqual(('1.2.3.4', 80), sock.sa)
74 self.assertEqual(('1.2.3.4', 80), sock.sa)
75 self.assertStringEqual(expected_req, sock.sent)
75 self.assertStringEqual(expected_req, sock.sent)
76 self.assertEqual("Thanks", con.getresponse().read())
76 self.assertEqual("Thanks", con.getresponse().read())
77 self.assertEqual(sock.closed, False)
77 self.assertEqual(sock.closed, False)
78
78
79 def testChunkedDownload(self):
79 def testChunkedDownload(self):
80 con = http.HTTPConnection('1.2.3.4:80')
80 con = http.HTTPConnection('1.2.3.4:80')
81 con._connect()
81 con._connect()
82 sock = con.sock
82 sock = con.sock
83 sock.data = ['HTTP/1.1 200 OK\r\n',
83 sock.data = ['HTTP/1.1 200 OK\r\n',
84 'Server: BogusServer 1.0\r\n',
84 'Server: BogusServer 1.0\r\n',
85 'transfer-encoding: chunked',
85 'transfer-encoding: chunked',
86 '\r\n\r\n',
86 '\r\n\r\n',
87 chunkedblock('hi '),
87 chunkedblock('hi '),
88 chunkedblock('there'),
88 chunkedblock('there'),
89 chunkedblock(''),
89 chunkedblock(''),
90 ]
90 ]
91 con.request('GET', '/')
91 con.request('GET', '/')
92 self.assertStringEqual('hi there', con.getresponse().read())
92 self.assertStringEqual('hi there', con.getresponse().read())
93
93
94 def testChunkedDownloadBadEOL(self):
94 def testChunkedDownloadBadEOL(self):
95 con = http.HTTPConnection('1.2.3.4:80')
95 con = http.HTTPConnection('1.2.3.4:80')
96 con._connect()
96 con._connect()
97 sock = con.sock
97 sock = con.sock
98 sock.data = ['HTTP/1.1 200 OK\n',
98 sock.data = ['HTTP/1.1 200 OK\n',
99 'Server: BogusServer 1.0\n',
99 'Server: BogusServer 1.0\n',
100 'transfer-encoding: chunked',
100 'transfer-encoding: chunked',
101 '\n\n',
101 '\n\n',
102 chunkedblock('hi ', eol='\n'),
102 chunkedblock('hi ', eol='\n'),
103 chunkedblock('there', eol='\n'),
103 chunkedblock('there', eol='\n'),
104 chunkedblock('', eol='\n'),
104 chunkedblock('', eol='\n'),
105 ]
105 ]
106 con.request('GET', '/')
106 con.request('GET', '/')
107 self.assertStringEqual('hi there', con.getresponse().read())
107 self.assertStringEqual('hi there', con.getresponse().read())
108
108
109 def testChunkedDownloadPartialChunkBadEOL(self):
109 def testChunkedDownloadPartialChunkBadEOL(self):
110 con = http.HTTPConnection('1.2.3.4:80')
110 con = http.HTTPConnection('1.2.3.4:80')
111 con._connect()
111 con._connect()
112 sock = con.sock
112 sock = con.sock
113 sock.data = ['HTTP/1.1 200 OK\n',
113 sock.data = ['HTTP/1.1 200 OK\n',
114 'Server: BogusServer 1.0\n',
114 'Server: BogusServer 1.0\n',
115 'transfer-encoding: chunked',
115 'transfer-encoding: chunked',
116 '\n\n',
116 '\n\n',
117 chunkedblock('hi ', eol='\n'),
117 chunkedblock('hi ', eol='\n'),
118 ] + list(chunkedblock('there\n' * 5, eol='\n')) + [
118 ] + list(chunkedblock('there\n' * 5, eol='\n')) + [
119 chunkedblock('', eol='\n')]
119 chunkedblock('', eol='\n')]
120 con.request('GET', '/')
120 con.request('GET', '/')
121 self.assertStringEqual('hi there\nthere\nthere\nthere\nthere\n',
121 self.assertStringEqual('hi there\nthere\nthere\nthere\nthere\n',
122 con.getresponse().read())
122 con.getresponse().read())
123
123
124 def testChunkedDownloadPartialChunk(self):
124 def testChunkedDownloadPartialChunk(self):
125 con = http.HTTPConnection('1.2.3.4:80')
125 con = http.HTTPConnection('1.2.3.4:80')
126 con._connect()
126 con._connect()
127 sock = con.sock
127 sock = con.sock
128 sock.data = ['HTTP/1.1 200 OK\r\n',
128 sock.data = ['HTTP/1.1 200 OK\r\n',
129 'Server: BogusServer 1.0\r\n',
129 'Server: BogusServer 1.0\r\n',
130 'transfer-encoding: chunked',
130 'transfer-encoding: chunked',
131 '\r\n\r\n',
131 '\r\n\r\n',
132 chunkedblock('hi '),
132 chunkedblock('hi '),
133 ] + list(chunkedblock('there\n' * 5)) + [chunkedblock('')]
133 ] + list(chunkedblock('there\n' * 5)) + [chunkedblock('')]
134 con.request('GET', '/')
134 con.request('GET', '/')
135 self.assertStringEqual('hi there\nthere\nthere\nthere\nthere\n',
135 self.assertStringEqual('hi there\nthere\nthere\nthere\nthere\n',
136 con.getresponse().read())
136 con.getresponse().read())
137 # no-check-code
137 # no-check-code
@@ -1,132 +1,135
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 import socket
30 import socket
31
31
32 import http
32 import http
33
33
34 # relative import to ease embedding the library
34 # relative import to ease embedding the library
35 import util
35 import util
36
36
37
37
38 def make_preloaded_socket(data):
38 def make_preloaded_socket(data):
39 """Make a socket pre-loaded with data so it can be read during connect.
39 """Make a socket pre-loaded with data so it can be read during connect.
40
40
41 Useful for https proxy tests because we have to read from the
41 Useful for https proxy tests because we have to read from the
42 socket during _connect rather than later on.
42 socket during _connect rather than later on.
43 """
43 """
44 def s(*args, **kwargs):
44 def s(*args, **kwargs):
45 sock = util.MockSocket(*args, **kwargs)
45 sock = util.MockSocket(*args, **kwargs)
46 sock.data = data[:]
46 sock.early_data = data[:]
47 return sock
47 return sock
48 return s
48 return s
49
49
50
50
51 class ProxyHttpTest(util.HttpTestBase, unittest.TestCase):
51 class ProxyHttpTest(util.HttpTestBase, unittest.TestCase):
52
52
53 def _run_simple_test(self, host, server_data, expected_req, expected_data):
53 def _run_simple_test(self, host, server_data, expected_req, expected_data):
54 con = http.HTTPConnection(host)
54 con = http.HTTPConnection(host)
55 con._connect()
55 con._connect()
56 con.sock.data = server_data
56 con.sock.data = server_data
57 con.request('GET', '/')
57 con.request('GET', '/')
58
58
59 self.assertEqual(expected_req, con.sock.sent)
59 self.assertEqual(expected_req, con.sock.sent)
60 self.assertEqual(expected_data, con.getresponse().read())
60 self.assertEqual(expected_data, con.getresponse().read())
61
61
62 def testSimpleRequest(self):
62 def testSimpleRequest(self):
63 con = http.HTTPConnection('1.2.3.4:80',
63 con = http.HTTPConnection('1.2.3.4:80',
64 proxy_hostport=('magicproxy', 4242))
64 proxy_hostport=('magicproxy', 4242))
65 con._connect()
65 con._connect()
66 con.sock.data = ['HTTP/1.1 200 OK\r\n',
66 con.sock.data = ['HTTP/1.1 200 OK\r\n',
67 'Server: BogusServer 1.0\r\n',
67 'Server: BogusServer 1.0\r\n',
68 'MultiHeader: Value\r\n'
68 'MultiHeader: Value\r\n'
69 'MultiHeader: Other Value\r\n'
69 'MultiHeader: Other Value\r\n'
70 'MultiHeader: One More!\r\n'
70 'MultiHeader: One More!\r\n'
71 'Content-Length: 10\r\n',
71 'Content-Length: 10\r\n',
72 '\r\n'
72 '\r\n'
73 '1234567890'
73 '1234567890'
74 ]
74 ]
75 con.request('GET', '/')
75 con.request('GET', '/')
76
76
77 expected_req = ('GET http://1.2.3.4/ HTTP/1.1\r\n'
77 expected_req = ('GET http://1.2.3.4/ HTTP/1.1\r\n'
78 'Host: 1.2.3.4\r\n'
78 'Host: 1.2.3.4\r\n'
79 'accept-encoding: identity\r\n\r\n')
79 'accept-encoding: identity\r\n\r\n')
80
80
81 self.assertEqual(('127.0.0.42', 4242), con.sock.sa)
81 self.assertEqual(('127.0.0.42', 4242), con.sock.sa)
82 self.assertStringEqual(expected_req, con.sock.sent)
82 self.assertStringEqual(expected_req, con.sock.sent)
83 resp = con.getresponse()
83 resp = con.getresponse()
84 self.assertEqual('1234567890', resp.read())
84 self.assertEqual('1234567890', resp.read())
85 self.assertEqual(['Value', 'Other Value', 'One More!'],
85 self.assertEqual(['Value', 'Other Value', 'One More!'],
86 resp.headers.getheaders('multiheader'))
86 resp.headers.getheaders('multiheader'))
87 self.assertEqual(['BogusServer 1.0'],
87 self.assertEqual(['BogusServer 1.0'],
88 resp.headers.getheaders('server'))
88 resp.headers.getheaders('server'))
89
89
90 def testSSLRequest(self):
90 def testSSLRequest(self):
91 con = http.HTTPConnection('1.2.3.4:443',
91 con = http.HTTPConnection('1.2.3.4:443',
92 proxy_hostport=('magicproxy', 4242))
92 proxy_hostport=('magicproxy', 4242))
93 socket.socket = make_preloaded_socket(
93 socket.socket = make_preloaded_socket(
94 ['HTTP/1.1 200 OK\r\n',
94 ['HTTP/1.1 200 OK\r\n',
95 'Server: BogusServer 1.0\r\n',
95 'Server: BogusServer 1.0\r\n',
96 'Content-Length: 10\r\n',
96 'Content-Length: 10\r\n',
97 '\r\n'
97 '\r\n'
98 '1234567890'])
98 '1234567890'])
99 con._connect()
99 con._connect()
100 con.sock.data.extend(['HTTP/1.1 200 OK\r\n',
100 con.sock.data = ['HTTP/1.1 200 OK\r\n',
101 'Server: BogusServer 1.0\r\n',
101 'Server: BogusServer 1.0\r\n',
102 'Content-Length: 10\r\n',
102 'Content-Length: 10\r\n',
103 '\r\n'
103 '\r\n'
104 '1234567890'
104 '1234567890'
105 ])
105 ]
106 connect_sent = con.sock.sent
107 con.sock.sent = ''
106 con.request('GET', '/')
108 con.request('GET', '/')
107
109
108 expected_req = ('CONNECT 1.2.3.4:443 HTTP/1.0\r\n'
110 expected_connect = ('CONNECT 1.2.3.4:443 HTTP/1.0\r\n'
109 'Host: 1.2.3.4\r\n'
111 'Host: 1.2.3.4\r\n'
110 'accept-encoding: identity\r\n'
112 'accept-encoding: identity\r\n'
111 '\r\n'
113 '\r\n')
112 'GET / HTTP/1.1\r\n'
114 expected_request = ('GET / HTTP/1.1\r\n'
113 'Host: 1.2.3.4\r\n'
115 'Host: 1.2.3.4\r\n'
114 'accept-encoding: identity\r\n\r\n')
116 'accept-encoding: identity\r\n\r\n')
115
117
116 self.assertEqual(('127.0.0.42', 4242), con.sock.sa)
118 self.assertEqual(('127.0.0.42', 4242), con.sock.sa)
117 self.assertStringEqual(expected_req, con.sock.sent)
119 self.assertStringEqual(expected_connect, connect_sent)
120 self.assertStringEqual(expected_request, con.sock.sent)
118 resp = con.getresponse()
121 resp = con.getresponse()
119 self.assertEqual(resp.status, 200)
122 self.assertEqual(resp.status, 200)
120 self.assertEqual('1234567890', resp.read())
123 self.assertEqual('1234567890', resp.read())
121 self.assertEqual(['BogusServer 1.0'],
124 self.assertEqual(['BogusServer 1.0'],
122 resp.headers.getheaders('server'))
125 resp.headers.getheaders('server'))
123
126
124 def testSSLProxyFailure(self):
127 def testSSLProxyFailure(self):
125 con = http.HTTPConnection('1.2.3.4:443',
128 con = http.HTTPConnection('1.2.3.4:443',
126 proxy_hostport=('magicproxy', 4242))
129 proxy_hostport=('magicproxy', 4242))
127 socket.socket = make_preloaded_socket(
130 socket.socket = make_preloaded_socket(
128 ['HTTP/1.1 407 Proxy Authentication Required\r\n\r\n'])
131 ['HTTP/1.1 407 Proxy Authentication Required\r\n\r\n'])
129 self.assertRaises(http.HTTPProxyConnectFailedException, con._connect)
132 self.assertRaises(http.HTTPProxyConnectFailedException, con._connect)
130 self.assertRaises(http.HTTPProxyConnectFailedException,
133 self.assertRaises(http.HTTPProxyConnectFailedException,
131 con.request, 'GET', '/')
134 con.request, 'GET', '/')
132 # no-check-code
135 # no-check-code
@@ -1,94 +1,93
1 # Copyright 2011, Google Inc.
1 # Copyright 2011, 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
37
38 class HttpSslTest(util.HttpTestBase, unittest.TestCase):
38 class HttpSslTest(util.HttpTestBase, unittest.TestCase):
39 def testSslRereadRequired(self):
39 def testSslRereadRequired(self):
40 con = http.HTTPConnection('1.2.3.4:443')
40 con = http.HTTPConnection('1.2.3.4:443')
41 con._connect()
41 con._connect()
42 # extend the list instead of assign because of how
42 # extend the list instead of assign because of how
43 # MockSSLSocket works.
43 # MockSSLSocket works.
44 con.sock.data.extend(['HTTP/1.1 200 OK\r\n',
44 con.sock.data = ['HTTP/1.1 200 OK\r\n',
45 'Server: BogusServer 1.0\r\n',
45 'Server: BogusServer 1.0\r\n',
46 'MultiHeader: Value\r\n'
46 'MultiHeader: Value\r\n'
47 'MultiHeader: Other Value\r\n'
47 'MultiHeader: Other Value\r\n'
48 'MultiHeader: One More!\r\n'
48 'MultiHeader: One More!\r\n'
49 'Content-Length: 10\r\n',
49 'Content-Length: 10\r\n',
50 '\r\n'
50 '\r\n'
51 '1234567890'
51 '1234567890'
52 ])
52 ]
53 con.request('GET', '/')
53 con.request('GET', '/')
54
54
55 expected_req = ('GET / HTTP/1.1\r\n'
55 expected_req = ('GET / HTTP/1.1\r\n'
56 'Host: 1.2.3.4\r\n'
56 'Host: 1.2.3.4\r\n'
57 'accept-encoding: identity\r\n\r\n')
57 'accept-encoding: identity\r\n\r\n')
58
58
59 self.assertEqual(('1.2.3.4', 443), con.sock.sa)
59 self.assertEqual(('1.2.3.4', 443), con.sock.sa)
60 self.assertEqual(expected_req, con.sock.sent)
60 self.assertEqual(expected_req, con.sock.sent)
61 resp = con.getresponse()
61 resp = con.getresponse()
62 self.assertEqual('1234567890', resp.read())
62 self.assertEqual('1234567890', resp.read())
63 self.assertEqual(['Value', 'Other Value', 'One More!'],
63 self.assertEqual(['Value', 'Other Value', 'One More!'],
64 resp.headers.getheaders('multiheader'))
64 resp.headers.getheaders('multiheader'))
65 self.assertEqual(['BogusServer 1.0'],
65 self.assertEqual(['BogusServer 1.0'],
66 resp.headers.getheaders('server'))
66 resp.headers.getheaders('server'))
67
67
68 def testSslRereadInEarlyResponse(self):
68 def testSslRereadInEarlyResponse(self):
69 con = http.HTTPConnection('1.2.3.4:443')
69 con = http.HTTPConnection('1.2.3.4:443')
70 con._connect()
70 con._connect()
71 # extend the list instead of assign because of how
71 con.sock.early_data = ['HTTP/1.1 200 OK\r\n',
72 # MockSSLSocket works.
72 'Server: BogusServer 1.0\r\n',
73 con.sock.early_data.extend(['HTTP/1.1 200 OK\r\n',
73 'MultiHeader: Value\r\n'
74 'Server: BogusServer 1.0\r\n',
74 'MultiHeader: Other Value\r\n'
75 'MultiHeader: Value\r\n'
75 'MultiHeader: One More!\r\n'
76 'MultiHeader: Other Value\r\n'
76 'Content-Length: 10\r\n',
77 'MultiHeader: One More!\r\n'
77 '\r\n'
78 'Content-Length: 10\r\n',
78 '1234567890'
79 '\r\n'
79 ]
80 '1234567890'
81 ])
82
80
83 expected_req = self.doPost(con, False)
81 expected_req = self.doPost(con, False)
84 self.assertEqual(None, con.sock,
82 self.assertEqual(None, con.sock,
85 'Connection should have disowned socket')
83 'Connection should have disowned socket')
86
84
87 resp = con.getresponse()
85 resp = con.getresponse()
88 self.assertEqual(('1.2.3.4', 443), resp.sock.sa)
86 self.assertEqual(('1.2.3.4', 443), resp.sock.sa)
89 self.assertEqual(expected_req, resp.sock.sent)
87 self.assertEqual(expected_req, resp.sock.sent)
90 self.assertEqual('1234567890', resp.read())
88 self.assertEqual('1234567890', resp.read())
91 self.assertEqual(['Value', 'Other Value', 'One More!'],
89 self.assertEqual(['Value', 'Other Value', 'One More!'],
92 resp.headers.getheaders('multiheader'))
90 resp.headers.getheaders('multiheader'))
93 self.assertEqual(['BogusServer 1.0'],
91 self.assertEqual(['BogusServer 1.0'],
94 resp.headers.getheaders('server'))
92 resp.headers.getheaders('server'))
93 # no-check-code
@@ -1,190 +1,195
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 difflib
29 import difflib
30 import socket
30 import socket
31
31
32 import http
32 import http
33
33
34
34
35 class MockSocket(object):
35 class MockSocket(object):
36 """Mock non-blocking socket object.
36 """Mock non-blocking socket object.
37
37
38 This is ONLY capable of mocking a nonblocking socket.
38 This is ONLY capable of mocking a nonblocking socket.
39
39
40 Attributes:
40 Attributes:
41 early_data: data to always send as soon as end of headers is seen
41 early_data: data to always send as soon as end of headers is seen
42 data: a list of strings to return on recv(), with the
42 data: a list of strings to return on recv(), with the
43 assumption that the socket would block between each
43 assumption that the socket would block between each
44 string in the list.
44 string in the list.
45 read_wait_sentinel: data that must be written to the socket before
45 read_wait_sentinel: data that must be written to the socket before
46 beginning the response.
46 beginning the response.
47 close_on_empty: If true, close the socket when it runs out of data
47 close_on_empty: If true, close the socket when it runs out of data
48 for the client.
48 for the client.
49 """
49 """
50 def __init__(self, af, socktype, proto):
50 def __init__(self, af, socktype, proto):
51 self.af = af
51 self.af = af
52 self.socktype = socktype
52 self.socktype = socktype
53 self.proto = proto
53 self.proto = proto
54
54
55 self.early_data = []
55 self.early_data = []
56 self.data = []
56 self.data = []
57 self.remote_closed = self.closed = False
57 self.remote_closed = self.closed = False
58 self.close_on_empty = False
58 self.close_on_empty = False
59 self.sent = ''
59 self.sent = ''
60 self.read_wait_sentinel = http._END_HEADERS
60 self.read_wait_sentinel = http._END_HEADERS
61
61
62 def close(self):
62 def close(self):
63 self.closed = True
63 self.closed = True
64
64
65 def connect(self, sa):
65 def connect(self, sa):
66 self.sa = sa
66 self.sa = sa
67
67
68 def setblocking(self, timeout):
68 def setblocking(self, timeout):
69 assert timeout == 0
69 assert timeout == 0
70
70
71 def recv(self, amt=-1):
71 def recv(self, amt=-1):
72 if self.early_data:
72 if self.early_data:
73 datalist = self.early_data
73 datalist = self.early_data
74 elif not self.data:
74 elif not self.data:
75 return ''
75 return ''
76 else:
76 else:
77 datalist = self.data
77 datalist = self.data
78 if amt == -1:
78 if amt == -1:
79 return datalist.pop(0)
79 return datalist.pop(0)
80 data = datalist.pop(0)
80 data = datalist.pop(0)
81 if len(data) > amt:
81 if len(data) > amt:
82 datalist.insert(0, data[amt:])
82 datalist.insert(0, data[amt:])
83 if not self.data and not self.early_data and self.close_on_empty:
83 if not self.data and not self.early_data and self.close_on_empty:
84 self.remote_closed = True
84 self.remote_closed = True
85 return data[:amt]
85 return data[:amt]
86
86
87 @property
87 @property
88 def ready_for_read(self):
88 def ready_for_read(self):
89 return ((self.early_data and http._END_HEADERS in self.sent)
89 return ((self.early_data and http._END_HEADERS in self.sent)
90 or (self.read_wait_sentinel in self.sent and self.data)
90 or (self.read_wait_sentinel in self.sent and self.data)
91 or self.closed)
91 or self.closed or self.remote_closed)
92
92
93 def send(self, data):
93 def send(self, data):
94 # this is a horrible mock, but nothing needs us to raise the
94 # this is a horrible mock, but nothing needs us to raise the
95 # correct exception yet
95 # correct exception yet
96 assert not self.closed, 'attempted to write to a closed socket'
96 assert not self.closed, 'attempted to write to a closed socket'
97 assert not self.remote_closed, ('attempted to write to a'
97 assert not self.remote_closed, ('attempted to write to a'
98 ' socket closed by the server')
98 ' socket closed by the server')
99 if len(data) > 8192:
99 if len(data) > 8192:
100 data = data[:8192]
100 data = data[:8192]
101 self.sent += data
101 self.sent += data
102 return len(data)
102 return len(data)
103
103
104
104
105 def mockselect(r, w, x, timeout=0):
105 def mockselect(r, w, x, timeout=0):
106 """Simple mock for select()
106 """Simple mock for select()
107 """
107 """
108 readable = filter(lambda s: s.ready_for_read, r)
108 readable = filter(lambda s: s.ready_for_read, r)
109 return readable, w[:], []
109 return readable, w[:], []
110
110
111
111
112 class MockSSLSocket(object):
112 class MockSSLSocket(object):
113 def __init__(self, sock):
113 def __init__(self, sock):
114 self._sock = sock
114 self._sock = sock
115 self._fail_recv = True
115 self._fail_recv = True
116
116
117 def __getattr__(self, key):
117 def __getattr__(self, key):
118 return getattr(self._sock, key)
118 return getattr(self._sock, key)
119
119
120 def __setattr__(self, key, value):
121 if key not in ('_sock', '_fail_recv'):
122 return setattr(self._sock, key, value)
123 return object.__setattr__(self, key, value)
124
120 def recv(self, amt=-1):
125 def recv(self, amt=-1):
121 try:
126 try:
122 if self._fail_recv:
127 if self._fail_recv:
123 raise socket.sslerror(socket.SSL_ERROR_WANT_READ)
128 raise socket.sslerror(socket.SSL_ERROR_WANT_READ)
124 return self._sock.recv(amt=amt)
129 return self._sock.recv(amt=amt)
125 finally:
130 finally:
126 self._fail_recv = not self._fail_recv
131 self._fail_recv = not self._fail_recv
127
132
128
133
129 def mocksslwrap(sock, keyfile=None, certfile=None,
134 def mocksslwrap(sock, keyfile=None, certfile=None,
130 server_side=False, cert_reqs=http.socketutil.CERT_NONE,
135 server_side=False, cert_reqs=http.socketutil.CERT_NONE,
131 ssl_version=http.socketutil.PROTOCOL_SSLv23, ca_certs=None,
136 ssl_version=http.socketutil.PROTOCOL_SSLv23, ca_certs=None,
132 do_handshake_on_connect=True,
137 do_handshake_on_connect=True,
133 suppress_ragged_eofs=True):
138 suppress_ragged_eofs=True):
134 return MockSSLSocket(sock)
139 return MockSSLSocket(sock)
135
140
136
141
137 def mockgetaddrinfo(host, port, unused, streamtype):
142 def mockgetaddrinfo(host, port, unused, streamtype):
138 assert unused == 0
143 assert unused == 0
139 assert streamtype == socket.SOCK_STREAM
144 assert streamtype == socket.SOCK_STREAM
140 if host.count('.') != 3:
145 if host.count('.') != 3:
141 host = '127.0.0.42'
146 host = '127.0.0.42'
142 return [(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, '',
147 return [(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, '',
143 (host, port))]
148 (host, port))]
144
149
145
150
146 class HttpTestBase(object):
151 class HttpTestBase(object):
147 def setUp(self):
152 def setUp(self):
148 self.orig_socket = socket.socket
153 self.orig_socket = socket.socket
149 socket.socket = MockSocket
154 socket.socket = MockSocket
150
155
151 self.orig_getaddrinfo = socket.getaddrinfo
156 self.orig_getaddrinfo = socket.getaddrinfo
152 socket.getaddrinfo = mockgetaddrinfo
157 socket.getaddrinfo = mockgetaddrinfo
153
158
154 self.orig_select = http.select.select
159 self.orig_select = http.select.select
155 http.select.select = mockselect
160 http.select.select = mockselect
156
161
157 self.orig_sslwrap = http.socketutil.wrap_socket
162 self.orig_sslwrap = http.socketutil.wrap_socket
158 http.socketutil.wrap_socket = mocksslwrap
163 http.socketutil.wrap_socket = mocksslwrap
159
164
160 def tearDown(self):
165 def tearDown(self):
161 socket.socket = self.orig_socket
166 socket.socket = self.orig_socket
162 http.select.select = self.orig_select
167 http.select.select = self.orig_select
163 http.socketutil.wrap_socket = self.orig_sslwrap
168 http.socketutil.wrap_socket = self.orig_sslwrap
164 socket.getaddrinfo = self.orig_getaddrinfo
169 socket.getaddrinfo = self.orig_getaddrinfo
165
170
166 def assertStringEqual(self, l, r):
171 def assertStringEqual(self, l, r):
167 try:
172 try:
168 self.assertEqual(l, r, ('failed string equality check, '
173 self.assertEqual(l, r, ('failed string equality check, '
169 'see stdout for details'))
174 'see stdout for details'))
170 except:
175 except:
171 add_nl = lambda li: map(lambda x: x + '\n', li)
176 add_nl = lambda li: map(lambda x: x + '\n', li)
172 print 'failed expectation:'
177 print 'failed expectation:'
173 print ''.join(difflib.unified_diff(
178 print ''.join(difflib.unified_diff(
174 add_nl(l.splitlines()), add_nl(r.splitlines()),
179 add_nl(l.splitlines()), add_nl(r.splitlines()),
175 fromfile='expected', tofile='got'))
180 fromfile='expected', tofile='got'))
176 raise
181 raise
177
182
178 def doPost(self, con, expect_body, body_to_send='This is some POST data'):
183 def doPost(self, con, expect_body, body_to_send='This is some POST data'):
179 con.request('POST', '/', body=body_to_send,
184 con.request('POST', '/', body=body_to_send,
180 expect_continue=True)
185 expect_continue=True)
181 expected_req = ('POST / HTTP/1.1\r\n'
186 expected_req = ('POST / HTTP/1.1\r\n'
182 'Host: 1.2.3.4\r\n'
187 'Host: 1.2.3.4\r\n'
183 'content-length: %d\r\n'
188 'content-length: %d\r\n'
184 'Expect: 100-Continue\r\n'
189 'Expect: 100-Continue\r\n'
185 'accept-encoding: identity\r\n\r\n' %
190 'accept-encoding: identity\r\n\r\n' %
186 len(body_to_send))
191 len(body_to_send))
187 if expect_body:
192 if expect_body:
188 expected_req += body_to_send
193 expected_req += body_to_send
189 return expected_req
194 return expected_req
190 # no-check-code
195 # no-check-code
General Comments 0
You need to be logged in to leave comments. Login now