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