##// END OF EJS Templates
httpclient: update to revision 892730fe7f46 of httpplus
Augie Fackler -
r16643:24dbef11 default
parent child Browse files
Show More
@@ -0,0 +1,195 b''
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 """Reader objects to abstract out different body response types.
30
31 This module is package-private. It is not expected that these will
32 have any clients outside of httpplus.
33 """
34
35 import httplib
36 import itertools
37 import logging
38
39 logger = logging.getLogger(__name__)
40
41
42 class ReadNotReady(Exception):
43 """Raised when read() is attempted but not enough data is loaded."""
44
45
46 class HTTPRemoteClosedError(httplib.HTTPException):
47 """The server closed the remote socket in the middle of a response."""
48
49
50 class AbstractReader(object):
51 """Abstract base class for response readers.
52
53 Subclasses must implement _load, and should implement _close if
54 it's not an error for the server to close their socket without
55 some termination condition being detected during _load.
56 """
57 def __init__(self):
58 self._finished = False
59 self._done_chunks = []
60
61 @property
62 def available_data(self):
63 return sum(map(len, self._done_chunks))
64
65 def done(self):
66 return self._finished
67
68 def read(self, amt):
69 if self.available_data < amt and not self._finished:
70 raise ReadNotReady()
71 need = [amt]
72 def pred(s):
73 needed = need[0] > 0
74 need[0] -= len(s)
75 return needed
76 blocks = list(itertools.takewhile(pred, self._done_chunks))
77 self._done_chunks = self._done_chunks[len(blocks):]
78 over_read = sum(map(len, blocks)) - amt
79 if over_read > 0 and blocks:
80 logger.debug('need to reinsert %d data into done chunks', over_read)
81 last = blocks[-1]
82 blocks[-1], reinsert = last[:-over_read], last[-over_read:]
83 self._done_chunks.insert(0, reinsert)
84 result = ''.join(blocks)
85 assert len(result) == amt or (self._finished and len(result) < amt)
86 return result
87
88 def _load(self, data): # pragma: no cover
89 """Subclasses must implement this.
90
91 As data is available to be read out of this object, it should
92 be placed into the _done_chunks list. Subclasses should not
93 rely on data remaining in _done_chunks forever, as it may be
94 reaped if the client is parsing data as it comes in.
95 """
96 raise NotImplementedError
97
98 def _close(self):
99 """Default implementation of close.
100
101 The default implementation assumes that the reader will mark
102 the response as finished on the _finished attribute once the
103 entire response body has been read. In the event that this is
104 not true, the subclass should override the implementation of
105 close (for example, close-is-end responses have to set
106 self._finished in the close handler.)
107 """
108 if not self._finished:
109 raise HTTPRemoteClosedError(
110 'server appears to have closed the socket mid-response')
111
112
113 class AbstractSimpleReader(AbstractReader):
114 """Abstract base class for simple readers that require no response decoding.
115
116 Examples of such responses are Connection: Close (close-is-end)
117 and responses that specify a content length.
118 """
119 def _load(self, data):
120 if data:
121 assert not self._finished, (
122 'tried to add data (%r) to a closed reader!' % data)
123 logger.debug('%s read an addtional %d data', self.name, len(data))
124 self._done_chunks.append(data)
125
126
127 class CloseIsEndReader(AbstractSimpleReader):
128 """Reader for responses that specify Connection: Close for length."""
129 name = 'close-is-end'
130
131 def _close(self):
132 logger.info('Marking close-is-end reader as closed.')
133 self._finished = True
134
135
136 class ContentLengthReader(AbstractSimpleReader):
137 """Reader for responses that specify an exact content length."""
138 name = 'content-length'
139
140 def __init__(self, amount):
141 AbstractReader.__init__(self)
142 self._amount = amount
143 if amount == 0:
144 self._finished = True
145 self._amount_seen = 0
146
147 def _load(self, data):
148 AbstractSimpleReader._load(self, data)
149 self._amount_seen += len(data)
150 if self._amount_seen >= self._amount:
151 self._finished = True
152 logger.debug('content-length read complete')
153
154
155 class ChunkedReader(AbstractReader):
156 """Reader for chunked transfer encoding responses."""
157 def __init__(self, eol):
158 AbstractReader.__init__(self)
159 self._eol = eol
160 self._leftover_skip_amt = 0
161 self._leftover_data = ''
162
163 def _load(self, data):
164 assert not self._finished, 'tried to add data to a closed reader!'
165 logger.debug('chunked read an addtional %d data', len(data))
166 position = 0
167 if self._leftover_data:
168 logger.debug('chunked reader trying to finish block from leftover data')
169 # TODO: avoid this string concatenation if possible
170 data = self._leftover_data + data
171 position = self._leftover_skip_amt
172 self._leftover_data = ''
173 self._leftover_skip_amt = 0
174 datalen = len(data)
175 while position < datalen:
176 split = data.find(self._eol, position)
177 if split == -1:
178 self._leftover_data = data
179 self._leftover_skip_amt = position
180 return
181 amt = int(data[position:split], base=16)
182 block_start = split + len(self._eol)
183 # If the whole data chunk plus the eol trailer hasn't
184 # loaded, we'll wait for the next load.
185 if block_start + amt + len(self._eol) > len(data):
186 self._leftover_data = data
187 self._leftover_skip_amt = position
188 return
189 if amt == 0:
190 self._finished = True
191 logger.debug('closing chunked redaer due to chunk of length 0')
192 return
193 self._done_chunks.append(data[block_start:block_start + amt])
194 position = block_start + amt + len(self._eol)
195 # no-check-code
@@ -0,0 +1,70 b''
1 # Copyright 2010, 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
30 import unittest
31
32 from httpplus import _readers
33
34 def chunkedblock(x, eol='\r\n'):
35 r"""Make a chunked transfer-encoding block.
36
37 >>> chunkedblock('hi')
38 '2\r\nhi\r\n'
39 >>> chunkedblock('hi' * 10)
40 '14\r\nhihihihihihihihihihi\r\n'
41 >>> chunkedblock('hi', eol='\n')
42 '2\nhi\n'
43 """
44 return ''.join((hex(len(x))[2:], eol, x, eol))
45
46 corpus = 'foo\r\nbar\r\nbaz\r\n'
47
48
49 class ChunkedReaderTest(unittest.TestCase):
50 def test_many_block_boundaries(self):
51 for step in xrange(1, len(corpus)):
52 data = ''.join(chunkedblock(corpus[start:start+step]) for
53 start in xrange(0, len(corpus), step))
54 for istep in xrange(1, len(data)):
55 rdr = _readers.ChunkedReader('\r\n')
56 print 'step', step, 'load', istep
57 for start in xrange(0, len(data), istep):
58 rdr._load(data[start:start+istep])
59 rdr._load(chunkedblock(''))
60 self.assertEqual(corpus, rdr.read(len(corpus) + 1))
61
62 def test_small_chunk_blocks_large_wire_blocks(self):
63 data = ''.join(map(chunkedblock, corpus)) + chunkedblock('')
64 rdr = _readers.ChunkedReader('\r\n')
65 for start in xrange(0, len(data), 4):
66 d = data[start:start + 4]
67 if d:
68 rdr._load(d)
69 self.assertEqual(corpus, rdr.read(len(corpus)+100))
70 # no-check-code
@@ -45,6 +45,7 b' import rfc822'
45 import select
45 import select
46 import socket
46 import socket
47
47
48 import _readers
48 import socketutil
49 import socketutil
49
50
50 logger = logging.getLogger(__name__)
51 logger = logging.getLogger(__name__)
@@ -54,8 +55,6 b' logger = logging.getLogger(__name__)'
54 HTTP_VER_1_0 = 'HTTP/1.0'
55 HTTP_VER_1_0 = 'HTTP/1.0'
55 HTTP_VER_1_1 = 'HTTP/1.1'
56 HTTP_VER_1_1 = 'HTTP/1.1'
56
57
57 _LEN_CLOSE_IS_END = -1
58
59 OUTGOING_BUFFER_SIZE = 1 << 15
58 OUTGOING_BUFFER_SIZE = 1 << 15
60 INCOMING_BUFFER_SIZE = 1 << 20
59 INCOMING_BUFFER_SIZE = 1 << 20
61
60
@@ -83,23 +82,19 b' class HTTPResponse(object):'
83 The response will continue to load as available. If you need the
82 The response will continue to load as available. If you need the
84 complete response before continuing, check the .complete() method.
83 complete response before continuing, check the .complete() method.
85 """
84 """
86 def __init__(self, sock, timeout):
85 def __init__(self, sock, timeout, method):
87 self.sock = sock
86 self.sock = sock
87 self.method = method
88 self.raw_response = ''
88 self.raw_response = ''
89 self._body = None
90 self._headers_len = 0
89 self._headers_len = 0
91 self._content_len = 0
92 self.headers = None
90 self.headers = None
93 self.will_close = False
91 self.will_close = False
94 self.status_line = ''
92 self.status_line = ''
95 self.status = None
93 self.status = None
94 self.continued = False
96 self.http_version = None
95 self.http_version = None
97 self.reason = None
96 self.reason = None
98 self._chunked = False
97 self._reader = None
99 self._chunked_done = False
100 self._chunked_until_next = 0
101 self._chunked_skip_bytes = 0
102 self._chunked_preloaded_block = None
103
98
104 self._read_location = 0
99 self._read_location = 0
105 self._eol = EOL
100 self._eol = EOL
@@ -117,11 +112,12 b' class HTTPResponse(object):'
117 socket is closed, this will nearly always return False, even
112 socket is closed, this will nearly always return False, even
118 in cases where all the data has actually been loaded.
113 in cases where all the data has actually been loaded.
119 """
114 """
120 if self._chunked:
115 if self._reader:
121 return self._chunked_done
116 return self._reader.done()
122 if self._content_len == _LEN_CLOSE_IS_END:
117
123 return False
118 def _close(self):
124 return self._body is not None and len(self._body) >= self._content_len
119 if self._reader is not None:
120 self._reader._close()
125
121
126 def readline(self):
122 def readline(self):
127 """Read a single line from the response body.
123 """Read a single line from the response body.
@@ -129,30 +125,34 b' class HTTPResponse(object):'
129 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
130 response is complete.
126 response is complete.
131 """
127 """
132 eol = self._body.find('\n', self._read_location)
128 # TODO: move this into the reader interface where it can be
133 while eol == -1 and not self.complete():
129 # smarter (and probably avoid copies)
130 bytes = []
131 while not bytes:
132 try:
133 bytes = [self._reader.read(1)]
134 except _readers.ReadNotReady:
135 self._select()
136 while bytes[-1] != '\n' and not self.complete():
134 self._select()
137 self._select()
135 eol = self._body.find('\n', self._read_location)
138 bytes.append(self._reader.read(1))
136 if eol != -1:
139 if bytes[-1] != '\n':
137 eol += 1
140 next = self._reader.read(1)
138 else:
141 while next and next != '\n':
139 eol = len(self._body)
142 bytes.append(next)
140 data = self._body[self._read_location:eol]
143 next = self._reader.read(1)
141 self._read_location = eol
144 bytes.append(next)
142 return data
145 return ''.join(bytes)
143
146
144 def read(self, length=None):
147 def read(self, length=None):
145 # if length is None, unbounded read
148 # if length is None, unbounded read
146 while (not self.complete() # never select on a finished read
149 while (not self.complete() # never select on a finished read
147 and (not length # unbounded, so we wait for complete()
150 and (not length # unbounded, so we wait for complete()
148 or (self._read_location + length) > len(self._body))):
151 or length > self._reader.available_data)):
149 self._select()
152 self._select()
150 if not length:
153 if not length:
151 length = len(self._body) - self._read_location
154 length = self._reader.available_data
152 elif len(self._body) < (self._read_location + length):
155 r = self._reader.read(length)
153 length = len(self._body) - self._read_location
154 r = self._body[self._read_location:self._read_location + length]
155 self._read_location += len(r)
156 if self.complete() and self.will_close:
156 if self.complete() and self.will_close:
157 self.sock.close()
157 self.sock.close()
158 return r
158 return r
@@ -160,93 +160,35 b' class HTTPResponse(object):'
160 def _select(self):
160 def _select(self):
161 r, _, _ = select.select([self.sock], [], [], self._timeout)
161 r, _, _ = select.select([self.sock], [], [], self._timeout)
162 if not r:
162 if not r:
163 # socket was not readable. If the response is not complete
163 # socket was not readable. If the response is not
164 # and we're not a _LEN_CLOSE_IS_END response, raise a timeout.
164 # complete, raise a timeout.
165 # If we are a _LEN_CLOSE_IS_END response and we have no data,
165 if not self.complete():
166 # raise a timeout.
167 if not (self.complete() or
168 (self._content_len == _LEN_CLOSE_IS_END and self._body)):
169 logger.info('timed out with timeout of %s', self._timeout)
166 logger.info('timed out with timeout of %s', self._timeout)
170 raise HTTPTimeoutException('timeout reading data')
167 raise HTTPTimeoutException('timeout reading data')
171 logger.info('cl: %r body: %r', self._content_len, self._body)
172 try:
168 try:
173 data = self.sock.recv(INCOMING_BUFFER_SIZE)
169 data = self.sock.recv(INCOMING_BUFFER_SIZE)
174 # If the socket was readable and no data was read, that
175 # means the socket was closed. If this isn't a
176 # _CLOSE_IS_END socket, then something is wrong if we're
177 # here (we shouldn't enter _select() if the response is
178 # complete), so abort.
179 if not data and self._content_len != _LEN_CLOSE_IS_END:
180 raise HTTPRemoteClosedError(
181 'server appears to have closed the socket mid-response')
182 except socket.sslerror, e:
170 except socket.sslerror, e:
183 if e.args[0] != socket.SSL_ERROR_WANT_READ:
171 if e.args[0] != socket.SSL_ERROR_WANT_READ:
184 raise
172 raise
185 logger.debug('SSL_WANT_READ in _select, should retry later')
173 logger.debug('SSL_WANT_READ in _select, should retry later')
186 return True
174 return True
187 logger.debug('response read %d data during _select', len(data))
175 logger.debug('response read %d data during _select', len(data))
176 # If the socket was readable and no data was read, that means
177 # the socket was closed. Inform the reader (if any) so it can
178 # raise an exception if this is an invalid situation.
188 if not data:
179 if not data:
189 if self.headers and self._content_len == _LEN_CLOSE_IS_END:
180 if self._reader:
190 self._content_len = len(self._body)
181 self._reader._close()
191 return False
182 return False
192 else:
183 else:
193 self._load_response(data)
184 self._load_response(data)
194 return True
185 return True
195
186
196 def _chunked_parsedata(self, data):
197 if self._chunked_preloaded_block:
198 data = self._chunked_preloaded_block + data
199 self._chunked_preloaded_block = None
200 while data:
201 logger.debug('looping with %d data remaining', len(data))
202 # Slice out anything we should skip
203 if self._chunked_skip_bytes:
204 if len(data) <= self._chunked_skip_bytes:
205 self._chunked_skip_bytes -= len(data)
206 data = ''
207 break
208 else:
209 data = data[self._chunked_skip_bytes:]
210 self._chunked_skip_bytes = 0
211
212 # determine how much is until the next chunk
213 if self._chunked_until_next:
214 amt = self._chunked_until_next
215 logger.debug('reading remaining %d of existing chunk', amt)
216 self._chunked_until_next = 0
217 body = data
218 else:
219 try:
220 amt, body = data.split(self._eol, 1)
221 except ValueError:
222 self._chunked_preloaded_block = data
223 logger.debug('saving %r as a preloaded block for chunked',
224 self._chunked_preloaded_block)
225 return
226 amt = int(amt, base=16)
227 logger.debug('reading chunk of length %d', amt)
228 if amt == 0:
229 self._chunked_done = True
230
231 # read through end of what we have or the chunk
232 self._body += body[:amt]
233 if len(body) >= amt:
234 data = body[amt:]
235 self._chunked_skip_bytes = len(self._eol)
236 else:
237 self._chunked_until_next = amt - len(body)
238 self._chunked_skip_bytes = 0
239 data = ''
240
241 def _load_response(self, data):
187 def _load_response(self, data):
242 if self._chunked:
188 # Being here implies we're not at the end of the headers yet,
243 self._chunked_parsedata(data)
189 # since at the end of this method if headers were completely
244 return
190 # loaded we replace this method with the load() method of the
245 elif self._body is not None:
191 # reader we created.
246 self._body += data
247 return
248
249 # We haven't seen end of headers yet
250 self.raw_response += data
192 self.raw_response += data
251 # This is a bogus server with bad line endings
193 # This is a bogus server with bad line endings
252 if self._eol not in self.raw_response:
194 if self._eol not in self.raw_response:
@@ -270,6 +212,7 b' class HTTPResponse(object):'
270 http_ver, status = hdrs.split(' ', 1)
212 http_ver, status = hdrs.split(' ', 1)
271 if status.startswith('100'):
213 if status.startswith('100'):
272 self.raw_response = body
214 self.raw_response = body
215 self.continued = True
273 logger.debug('continue seen, setting body to %r', body)
216 logger.debug('continue seen, setting body to %r', body)
274 return
217 return
275
218
@@ -289,23 +232,46 b' class HTTPResponse(object):'
289 if self._eol != EOL:
232 if self._eol != EOL:
290 hdrs = hdrs.replace(self._eol, '\r\n')
233 hdrs = hdrs.replace(self._eol, '\r\n')
291 headers = rfc822.Message(cStringIO.StringIO(hdrs))
234 headers = rfc822.Message(cStringIO.StringIO(hdrs))
235 content_len = None
292 if HDR_CONTENT_LENGTH in headers:
236 if HDR_CONTENT_LENGTH in headers:
293 self._content_len = int(headers[HDR_CONTENT_LENGTH])
237 content_len = int(headers[HDR_CONTENT_LENGTH])
294 if self.http_version == HTTP_VER_1_0:
238 if self.http_version == HTTP_VER_1_0:
295 self.will_close = True
239 self.will_close = True
296 elif HDR_CONNECTION_CTRL in headers:
240 elif HDR_CONNECTION_CTRL in headers:
297 self.will_close = (
241 self.will_close = (
298 headers[HDR_CONNECTION_CTRL].lower() == CONNECTION_CLOSE)
242 headers[HDR_CONNECTION_CTRL].lower() == CONNECTION_CLOSE)
299 if self._content_len == 0:
300 self._content_len = _LEN_CLOSE_IS_END
301 if (HDR_XFER_ENCODING in headers
243 if (HDR_XFER_ENCODING in headers
302 and headers[HDR_XFER_ENCODING].lower() == XFER_ENCODING_CHUNKED):
244 and headers[HDR_XFER_ENCODING].lower() == XFER_ENCODING_CHUNKED):
303 self._body = ''
245 self._reader = _readers.ChunkedReader(self._eol)
304 self._chunked_parsedata(body)
246 logger.debug('using a chunked reader')
305 self._chunked = True
247 else:
306 if self._body is None:
248 # HEAD responses are forbidden from returning a body, and
307 self._body = body
249 # it's implausible for a CONNECT response to use
250 # close-is-end logic for an OK response.
251 if (self.method == 'HEAD' or
252 (self.method == 'CONNECT' and content_len is None)):
253 content_len = 0
254 if content_len is not None:
255 logger.debug('using a content-length reader with length %d',
256 content_len)
257 self._reader = _readers.ContentLengthReader(content_len)
258 else:
259 # Response body had no length specified and is not
260 # chunked, so the end of the body will only be
261 # identifiable by the termination of the socket by the
262 # server. My interpretation of the spec means that we
263 # are correct in hitting this case if
264 # transfer-encoding, content-length, and
265 # connection-control were left unspecified.
266 self._reader = _readers.CloseIsEndReader()
267 logger.debug('using a close-is-end reader')
268 self.will_close = True
269
270 if body:
271 self._reader._load(body)
272 logger.debug('headers complete')
308 self.headers = headers
273 self.headers = headers
274 self._load_response = self._reader._load
309
275
310
276
311 class HTTPConnection(object):
277 class HTTPConnection(object):
@@ -382,13 +348,14 b' class HTTPConnection(object):'
382 {}, HTTP_VER_1_0)
348 {}, HTTP_VER_1_0)
383 sock.send(data)
349 sock.send(data)
384 sock.setblocking(0)
350 sock.setblocking(0)
385 r = self.response_class(sock, self.timeout)
351 r = self.response_class(sock, self.timeout, 'CONNECT')
386 timeout_exc = HTTPTimeoutException(
352 timeout_exc = HTTPTimeoutException(
387 'Timed out waiting for CONNECT response from proxy')
353 'Timed out waiting for CONNECT response from proxy')
388 while not r.complete():
354 while not r.complete():
389 try:
355 try:
390 if not r._select():
356 if not r._select():
391 raise timeout_exc
357 if not r.complete():
358 raise timeout_exc
392 except HTTPTimeoutException:
359 except HTTPTimeoutException:
393 # This raise/except pattern looks goofy, but
360 # This raise/except pattern looks goofy, but
394 # _select can raise the timeout as well as the
361 # _select can raise the timeout as well as the
@@ -527,7 +494,7 b' class HTTPConnection(object):'
527 out = outgoing_headers or body
494 out = outgoing_headers or body
528 blocking_on_continue = False
495 blocking_on_continue = False
529 if expect_continue and not outgoing_headers and not (
496 if expect_continue and not outgoing_headers and not (
530 response and response.headers):
497 response and (response.headers or response.continued)):
531 logger.info(
498 logger.info(
532 'waiting up to %s seconds for'
499 'waiting up to %s seconds for'
533 ' continue response from server',
500 ' continue response from server',
@@ -550,11 +517,6 b' class HTTPConnection(object):'
550 'server, optimistically sending request body')
517 'server, optimistically sending request body')
551 else:
518 else:
552 raise HTTPTimeoutException('timeout sending data')
519 raise HTTPTimeoutException('timeout sending data')
553 # TODO exceptional conditions with select? (what are those be?)
554 # TODO if the response is loading, must we finish sending at all?
555 #
556 # Certainly not if it's going to close the connection and/or
557 # the response is already done...I think.
558 was_first = first
520 was_first = first
559
521
560 # incoming data
522 # incoming data
@@ -572,11 +534,11 b' class HTTPConnection(object):'
572 logger.info('socket appears closed in read')
534 logger.info('socket appears closed in read')
573 self.sock = None
535 self.sock = None
574 self._current_response = None
536 self._current_response = None
537 if response is not None:
538 response._close()
575 # This if/elif ladder is a bit subtle,
539 # This if/elif ladder is a bit subtle,
576 # comments in each branch should help.
540 # comments in each branch should help.
577 if response is not None and (
541 if response is not None and response.complete():
578 response.complete() or
579 response._content_len == _LEN_CLOSE_IS_END):
580 # Server responded completely and then
542 # Server responded completely and then
581 # closed the socket. We should just shut
543 # closed the socket. We should just shut
582 # things down and let the caller get their
544 # things down and let the caller get their
@@ -605,7 +567,7 b' class HTTPConnection(object):'
605 'response was missing or incomplete!')
567 'response was missing or incomplete!')
606 logger.debug('read %d bytes in request()', len(data))
568 logger.debug('read %d bytes in request()', len(data))
607 if response is None:
569 if response is None:
608 response = self.response_class(r[0], self.timeout)
570 response = self.response_class(r[0], self.timeout, method)
609 response._load_response(data)
571 response._load_response(data)
610 # Jump to the next select() call so we load more
572 # Jump to the next select() call so we load more
611 # data if the server is still sending us content.
573 # data if the server is still sending us content.
@@ -613,10 +575,6 b' class HTTPConnection(object):'
613 except socket.error, e:
575 except socket.error, e:
614 if e[0] != errno.EPIPE and not was_first:
576 if e[0] != errno.EPIPE and not was_first:
615 raise
577 raise
616 if (response._content_len
617 and response._content_len != _LEN_CLOSE_IS_END):
618 outgoing_headers = sent_data + outgoing_headers
619 reconnect('read')
620
578
621 # outgoing data
579 # outgoing data
622 if w and out:
580 if w and out:
@@ -661,7 +619,7 b' class HTTPConnection(object):'
661 # close if the server response said to or responded before eating
619 # close if the server response said to or responded before eating
662 # the whole request
620 # the whole request
663 if response is None:
621 if response is None:
664 response = self.response_class(self.sock, self.timeout)
622 response = self.response_class(self.sock, self.timeout, method)
665 complete = response.complete()
623 complete = response.complete()
666 data_left = bool(outgoing_headers or body)
624 data_left = bool(outgoing_headers or body)
667 if data_left:
625 if data_left:
@@ -679,7 +637,8 b' class HTTPConnection(object):'
679 raise httplib.ResponseNotReady()
637 raise httplib.ResponseNotReady()
680 r = self._current_response
638 r = self._current_response
681 while r.headers is None:
639 while r.headers is None:
682 r._select()
640 if not r._select() and not r.complete():
641 raise _readers.HTTPRemoteClosedError()
683 if r.will_close:
642 if r.will_close:
684 self.sock = None
643 self.sock = None
685 self._current_response = None
644 self._current_response = None
@@ -705,7 +664,7 b' class HTTPProxyConnectFailedException(ht'
705 class HTTPStateError(httplib.HTTPException):
664 class HTTPStateError(httplib.HTTPException):
706 """Invalid internal state encountered."""
665 """Invalid internal state encountered."""
707
666
708
667 # Forward this exception type from _readers since it needs to be part
709 class HTTPRemoteClosedError(httplib.HTTPException):
668 # of the public API.
710 """The server closed the remote socket in the middle of a response."""
669 HTTPRemoteClosedError = _readers.HTTPRemoteClosedError
711 # no-check-code
670 # no-check-code
@@ -29,7 +29,7 b''
29 import socket
29 import socket
30 import unittest
30 import unittest
31
31
32 import http
32 import httpplus
33
33
34 # relative import to ease embedding the library
34 # relative import to ease embedding the library
35 import util
35 import util
@@ -38,7 +38,7 b' import util'
38 class SimpleHttpTest(util.HttpTestBase, unittest.TestCase):
38 class SimpleHttpTest(util.HttpTestBase, unittest.TestCase):
39
39
40 def _run_simple_test(self, host, server_data, expected_req, expected_data):
40 def _run_simple_test(self, host, server_data, expected_req, expected_data):
41 con = http.HTTPConnection(host)
41 con = httpplus.HTTPConnection(host)
42 con._connect()
42 con._connect()
43 con.sock.data = server_data
43 con.sock.data = server_data
44 con.request('GET', '/')
44 con.request('GET', '/')
@@ -47,9 +47,9 b' class SimpleHttpTest(util.HttpTestBase, '
47 self.assertEqual(expected_data, con.getresponse().read())
47 self.assertEqual(expected_data, con.getresponse().read())
48
48
49 def test_broken_data_obj(self):
49 def test_broken_data_obj(self):
50 con = http.HTTPConnection('1.2.3.4:80')
50 con = httpplus.HTTPConnection('1.2.3.4:80')
51 con._connect()
51 con._connect()
52 self.assertRaises(http.BadRequestData,
52 self.assertRaises(httpplus.BadRequestData,
53 con.request, 'POST', '/', body=1)
53 con.request, 'POST', '/', body=1)
54
54
55 def test_no_keepalive_http_1_0(self):
55 def test_no_keepalive_http_1_0(self):
@@ -74,7 +74,7 b' store'
74 fncache
74 fncache
75 dotencode
75 dotencode
76 """
76 """
77 con = http.HTTPConnection('localhost:9999')
77 con = httpplus.HTTPConnection('localhost:9999')
78 con._connect()
78 con._connect()
79 con.sock.data = [expected_response_headers, expected_response_body]
79 con.sock.data = [expected_response_headers, expected_response_body]
80 con.request('GET', '/remote/.hg/requires',
80 con.request('GET', '/remote/.hg/requires',
@@ -95,7 +95,7 b' dotencode'
95 self.assert_(resp.sock.closed)
95 self.assert_(resp.sock.closed)
96
96
97 def test_multiline_header(self):
97 def test_multiline_header(self):
98 con = http.HTTPConnection('1.2.3.4:80')
98 con = httpplus.HTTPConnection('1.2.3.4:80')
99 con._connect()
99 con._connect()
100 con.sock.data = ['HTTP/1.1 200 OK\r\n',
100 con.sock.data = ['HTTP/1.1 200 OK\r\n',
101 'Server: BogusServer 1.0\r\n',
101 'Server: BogusServer 1.0\r\n',
@@ -122,7 +122,7 b' dotencode'
122 self.assertEqual(con.sock.closed, False)
122 self.assertEqual(con.sock.closed, False)
123
123
124 def testSimpleRequest(self):
124 def testSimpleRequest(self):
125 con = http.HTTPConnection('1.2.3.4:80')
125 con = httpplus.HTTPConnection('1.2.3.4:80')
126 con._connect()
126 con._connect()
127 con.sock.data = ['HTTP/1.1 200 OK\r\n',
127 con.sock.data = ['HTTP/1.1 200 OK\r\n',
128 'Server: BogusServer 1.0\r\n',
128 'Server: BogusServer 1.0\r\n',
@@ -149,12 +149,13 b' dotencode'
149 resp.headers.getheaders('server'))
149 resp.headers.getheaders('server'))
150
150
151 def testHeaderlessResponse(self):
151 def testHeaderlessResponse(self):
152 con = http.HTTPConnection('1.2.3.4', use_ssl=False)
152 con = httpplus.HTTPConnection('1.2.3.4', use_ssl=False)
153 con._connect()
153 con._connect()
154 con.sock.data = ['HTTP/1.1 200 OK\r\n',
154 con.sock.data = ['HTTP/1.1 200 OK\r\n',
155 '\r\n'
155 '\r\n'
156 '1234567890'
156 '1234567890'
157 ]
157 ]
158 con.sock.close_on_empty = True
158 con.request('GET', '/')
159 con.request('GET', '/')
159
160
160 expected_req = ('GET / HTTP/1.1\r\n'
161 expected_req = ('GET / HTTP/1.1\r\n'
@@ -169,7 +170,30 b' dotencode'
169 self.assertEqual(resp.status, 200)
170 self.assertEqual(resp.status, 200)
170
171
171 def testReadline(self):
172 def testReadline(self):
172 con = http.HTTPConnection('1.2.3.4')
173 con = httpplus.HTTPConnection('1.2.3.4')
174 con._connect()
175 con.sock.data = ['HTTP/1.1 200 OK\r\n',
176 'Server: BogusServer 1.0\r\n',
177 'Connection: Close\r\n',
178 '\r\n'
179 '1\n2\nabcdefg\n4\n5']
180 con.sock.close_on_empty = True
181
182 expected_req = ('GET / HTTP/1.1\r\n'
183 'Host: 1.2.3.4\r\n'
184 'accept-encoding: identity\r\n\r\n')
185
186 con.request('GET', '/')
187 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
188 self.assertEqual(expected_req, con.sock.sent)
189 r = con.getresponse()
190 for expected in ['1\n', '2\n', 'abcdefg\n', '4\n', '5']:
191 actual = r.readline()
192 self.assertEqual(expected, actual,
193 'Expected %r, got %r' % (expected, actual))
194
195 def testReadlineTrickle(self):
196 con = httpplus.HTTPConnection('1.2.3.4')
173 con._connect()
197 con._connect()
174 # make sure it trickles in one byte at a time
198 # make sure it trickles in one byte at a time
175 # so that we touch all the cases in readline
199 # so that we touch all the cases in readline
@@ -179,6 +203,7 b' dotencode'
179 'Connection: Close\r\n',
203 'Connection: Close\r\n',
180 '\r\n'
204 '\r\n'
181 '1\n2\nabcdefg\n4\n5']))
205 '1\n2\nabcdefg\n4\n5']))
206 con.sock.close_on_empty = True
182
207
183 expected_req = ('GET / HTTP/1.1\r\n'
208 expected_req = ('GET / HTTP/1.1\r\n'
184 'Host: 1.2.3.4\r\n'
209 'Host: 1.2.3.4\r\n'
@@ -193,6 +218,59 b' dotencode'
193 self.assertEqual(expected, actual,
218 self.assertEqual(expected, actual,
194 'Expected %r, got %r' % (expected, actual))
219 'Expected %r, got %r' % (expected, actual))
195
220
221 def testVariousReads(self):
222 con = httpplus.HTTPConnection('1.2.3.4')
223 con._connect()
224 # make sure it trickles in one byte at a time
225 # so that we touch all the cases in readline
226 con.sock.data = list(''.join(
227 ['HTTP/1.1 200 OK\r\n',
228 'Server: BogusServer 1.0\r\n',
229 'Connection: Close\r\n',
230 '\r\n'
231 '1\n2',
232 '\na', 'bc',
233 'defg\n4\n5']))
234 con.sock.close_on_empty = True
235
236 expected_req = ('GET / HTTP/1.1\r\n'
237 'Host: 1.2.3.4\r\n'
238 'accept-encoding: identity\r\n\r\n')
239
240 con.request('GET', '/')
241 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
242 self.assertEqual(expected_req, con.sock.sent)
243 r = con.getresponse()
244 for read_amt, expect in [(1, '1'), (1, '\n'),
245 (4, '2\nab'),
246 ('line', 'cdefg\n'),
247 (None, '4\n5')]:
248 if read_amt == 'line':
249 self.assertEqual(expect, r.readline())
250 else:
251 self.assertEqual(expect, r.read(read_amt))
252
253 def testZeroLengthBody(self):
254 con = httpplus.HTTPConnection('1.2.3.4')
255 con._connect()
256 # make sure it trickles in one byte at a time
257 # so that we touch all the cases in readline
258 con.sock.data = list(''.join(
259 ['HTTP/1.1 200 OK\r\n',
260 'Server: BogusServer 1.0\r\n',
261 'Content-length: 0\r\n',
262 '\r\n']))
263
264 expected_req = ('GET / HTTP/1.1\r\n'
265 'Host: 1.2.3.4\r\n'
266 'accept-encoding: identity\r\n\r\n')
267
268 con.request('GET', '/')
269 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
270 self.assertEqual(expected_req, con.sock.sent)
271 r = con.getresponse()
272 self.assertEqual('', r.read())
273
196 def testIPv6(self):
274 def testIPv6(self):
197 self._run_simple_test('[::1]:8221',
275 self._run_simple_test('[::1]:8221',
198 ['HTTP/1.1 200 OK\r\n',
276 ['HTTP/1.1 200 OK\r\n',
@@ -226,7 +304,7 b' dotencode'
226 '1234567890')
304 '1234567890')
227
305
228 def testEarlyContinueResponse(self):
306 def testEarlyContinueResponse(self):
229 con = http.HTTPConnection('1.2.3.4:80')
307 con = httpplus.HTTPConnection('1.2.3.4:80')
230 con._connect()
308 con._connect()
231 sock = con.sock
309 sock = con.sock
232 sock.data = ['HTTP/1.1 403 Forbidden\r\n',
310 sock.data = ['HTTP/1.1 403 Forbidden\r\n',
@@ -240,8 +318,23 b' dotencode'
240 self.assertEqual("You can't do that.", con.getresponse().read())
318 self.assertEqual("You can't do that.", con.getresponse().read())
241 self.assertEqual(sock.closed, True)
319 self.assertEqual(sock.closed, True)
242
320
321 def testEarlyContinueResponseNoContentLength(self):
322 con = httpplus.HTTPConnection('1.2.3.4:80')
323 con._connect()
324 sock = con.sock
325 sock.data = ['HTTP/1.1 403 Forbidden\r\n',
326 'Server: BogusServer 1.0\r\n',
327 '\r\n'
328 "You can't do that."]
329 sock.close_on_empty = True
330 expected_req = self.doPost(con, expect_body=False)
331 self.assertEqual(('1.2.3.4', 80), sock.sa)
332 self.assertStringEqual(expected_req, sock.sent)
333 self.assertEqual("You can't do that.", con.getresponse().read())
334 self.assertEqual(sock.closed, True)
335
243 def testDeniedAfterContinueTimeoutExpires(self):
336 def testDeniedAfterContinueTimeoutExpires(self):
244 con = http.HTTPConnection('1.2.3.4:80')
337 con = httpplus.HTTPConnection('1.2.3.4:80')
245 con._connect()
338 con._connect()
246 sock = con.sock
339 sock = con.sock
247 sock.data = ['HTTP/1.1 403 Forbidden\r\n',
340 sock.data = ['HTTP/1.1 403 Forbidden\r\n',
@@ -269,7 +362,7 b' dotencode'
269 self.assertEqual(sock.closed, True)
362 self.assertEqual(sock.closed, True)
270
363
271 def testPostData(self):
364 def testPostData(self):
272 con = http.HTTPConnection('1.2.3.4:80')
365 con = httpplus.HTTPConnection('1.2.3.4:80')
273 con._connect()
366 con._connect()
274 sock = con.sock
367 sock = con.sock
275 sock.read_wait_sentinel = 'POST data'
368 sock.read_wait_sentinel = 'POST data'
@@ -286,7 +379,7 b' dotencode'
286 self.assertEqual(sock.closed, False)
379 self.assertEqual(sock.closed, False)
287
380
288 def testServerWithoutContinue(self):
381 def testServerWithoutContinue(self):
289 con = http.HTTPConnection('1.2.3.4:80')
382 con = httpplus.HTTPConnection('1.2.3.4:80')
290 con._connect()
383 con._connect()
291 sock = con.sock
384 sock = con.sock
292 sock.read_wait_sentinel = 'POST data'
385 sock.read_wait_sentinel = 'POST data'
@@ -302,7 +395,7 b' dotencode'
302 self.assertEqual(sock.closed, False)
395 self.assertEqual(sock.closed, False)
303
396
304 def testServerWithSlowContinue(self):
397 def testServerWithSlowContinue(self):
305 con = http.HTTPConnection('1.2.3.4:80')
398 con = httpplus.HTTPConnection('1.2.3.4:80')
306 con._connect()
399 con._connect()
307 sock = con.sock
400 sock = con.sock
308 sock.read_wait_sentinel = 'POST data'
401 sock.read_wait_sentinel = 'POST data'
@@ -321,7 +414,7 b' dotencode'
321 self.assertEqual(sock.closed, False)
414 self.assertEqual(sock.closed, False)
322
415
323 def testSlowConnection(self):
416 def testSlowConnection(self):
324 con = http.HTTPConnection('1.2.3.4:80')
417 con = httpplus.HTTPConnection('1.2.3.4:80')
325 con._connect()
418 con._connect()
326 # simulate one byte arriving at a time, to check for various
419 # simulate one byte arriving at a time, to check for various
327 # corner cases
420 # corner cases
@@ -340,12 +433,26 b' dotencode'
340 self.assertEqual(expected_req, con.sock.sent)
433 self.assertEqual(expected_req, con.sock.sent)
341 self.assertEqual('1234567890', con.getresponse().read())
434 self.assertEqual('1234567890', con.getresponse().read())
342
435
436 def testCloseAfterNotAllOfHeaders(self):
437 con = httpplus.HTTPConnection('1.2.3.4:80')
438 con._connect()
439 con.sock.data = ['HTTP/1.1 200 OK\r\n',
440 'Server: NO CARRIER']
441 con.sock.close_on_empty = True
442 con.request('GET', '/')
443 self.assertRaises(httpplus.HTTPRemoteClosedError,
444 con.getresponse)
445
446 expected_req = ('GET / HTTP/1.1\r\n'
447 'Host: 1.2.3.4\r\n'
448 'accept-encoding: identity\r\n\r\n')
449
343 def testTimeout(self):
450 def testTimeout(self):
344 con = http.HTTPConnection('1.2.3.4:80')
451 con = httpplus.HTTPConnection('1.2.3.4:80')
345 con._connect()
452 con._connect()
346 con.sock.data = []
453 con.sock.data = []
347 con.request('GET', '/')
454 con.request('GET', '/')
348 self.assertRaises(http.HTTPTimeoutException,
455 self.assertRaises(httpplus.HTTPTimeoutException,
349 con.getresponse)
456 con.getresponse)
350
457
351 expected_req = ('GET / HTTP/1.1\r\n'
458 expected_req = ('GET / HTTP/1.1\r\n'
@@ -370,7 +477,7 b' dotencode'
370 return s
477 return s
371
478
372 socket.socket = closingsocket
479 socket.socket = closingsocket
373 con = http.HTTPConnection('1.2.3.4:80')
480 con = httpplus.HTTPConnection('1.2.3.4:80')
374 con._connect()
481 con._connect()
375 con.request('GET', '/')
482 con.request('GET', '/')
376 r1 = con.getresponse()
483 r1 = con.getresponse()
@@ -381,7 +488,7 b' dotencode'
381 self.assertEqual(2, len(sockets))
488 self.assertEqual(2, len(sockets))
382
489
383 def test_server_closes_before_end_of_body(self):
490 def test_server_closes_before_end_of_body(self):
384 con = http.HTTPConnection('1.2.3.4:80')
491 con = httpplus.HTTPConnection('1.2.3.4:80')
385 con._connect()
492 con._connect()
386 s = con.sock
493 s = con.sock
387 s.data = ['HTTP/1.1 200 OK\r\n',
494 s.data = ['HTTP/1.1 200 OK\r\n',
@@ -393,9 +500,9 b' dotencode'
393 s.close_on_empty = True
500 s.close_on_empty = True
394 con.request('GET', '/')
501 con.request('GET', '/')
395 r1 = con.getresponse()
502 r1 = con.getresponse()
396 self.assertRaises(http.HTTPRemoteClosedError, r1.read)
503 self.assertRaises(httpplus.HTTPRemoteClosedError, r1.read)
397
504
398 def test_no_response_raises_response_not_ready(self):
505 def test_no_response_raises_response_not_ready(self):
399 con = http.HTTPConnection('foo')
506 con = httpplus.HTTPConnection('foo')
400 self.assertRaises(http.httplib.ResponseNotReady, con.getresponse)
507 self.assertRaises(httpplus.httplib.ResponseNotReady, con.getresponse)
401 # no-check-code
508 # no-check-code
@@ -34,7 +34,7 b' against that potential insanit.y'
34 """
34 """
35 import unittest
35 import unittest
36
36
37 import http
37 import httpplus
38
38
39 # relative import to ease embedding the library
39 # relative import to ease embedding the library
40 import util
40 import util
@@ -43,7 +43,7 b' import util'
43 class SimpleHttpTest(util.HttpTestBase, unittest.TestCase):
43 class SimpleHttpTest(util.HttpTestBase, unittest.TestCase):
44
44
45 def bogusEOL(self, eol):
45 def bogusEOL(self, eol):
46 con = http.HTTPConnection('1.2.3.4:80')
46 con = httpplus.HTTPConnection('1.2.3.4:80')
47 con._connect()
47 con._connect()
48 con.sock.data = ['HTTP/1.1 200 OK%s' % eol,
48 con.sock.data = ['HTTP/1.1 200 OK%s' % eol,
49 'Server: BogusServer 1.0%s' % eol,
49 'Server: BogusServer 1.0%s' % eol,
@@ -29,7 +29,7 b''
29 import cStringIO
29 import cStringIO
30 import unittest
30 import unittest
31
31
32 import http
32 import httpplus
33
33
34 # relative import to ease embedding the library
34 # relative import to ease embedding the library
35 import util
35 import util
@@ -50,7 +50,7 b" def chunkedblock(x, eol='\\r\\n'):"
50
50
51 class ChunkedTransferTest(util.HttpTestBase, unittest.TestCase):
51 class ChunkedTransferTest(util.HttpTestBase, unittest.TestCase):
52 def testChunkedUpload(self):
52 def testChunkedUpload(self):
53 con = http.HTTPConnection('1.2.3.4:80')
53 con = httpplus.HTTPConnection('1.2.3.4:80')
54 con._connect()
54 con._connect()
55 sock = con.sock
55 sock = con.sock
56 sock.read_wait_sentinel = '0\r\n\r\n'
56 sock.read_wait_sentinel = '0\r\n\r\n'
@@ -77,7 +77,7 b' class ChunkedTransferTest(util.HttpTestB'
77 self.assertEqual(sock.closed, False)
77 self.assertEqual(sock.closed, False)
78
78
79 def testChunkedDownload(self):
79 def testChunkedDownload(self):
80 con = http.HTTPConnection('1.2.3.4:80')
80 con = httpplus.HTTPConnection('1.2.3.4:80')
81 con._connect()
81 con._connect()
82 sock = con.sock
82 sock = con.sock
83 sock.data = ['HTTP/1.1 200 OK\r\n',
83 sock.data = ['HTTP/1.1 200 OK\r\n',
@@ -85,14 +85,31 b' class ChunkedTransferTest(util.HttpTestB'
85 'transfer-encoding: chunked',
85 'transfer-encoding: chunked',
86 '\r\n\r\n',
86 '\r\n\r\n',
87 chunkedblock('hi '),
87 chunkedblock('hi '),
88 chunkedblock('there'),
88 ] + list(chunkedblock('there')) + [
89 chunkedblock(''),
89 chunkedblock(''),
90 ]
90 ]
91 con.request('GET', '/')
91 con.request('GET', '/')
92 self.assertStringEqual('hi there', con.getresponse().read())
92 self.assertStringEqual('hi there', con.getresponse().read())
93
93
94 def testChunkedDownloadOddReadBoundaries(self):
95 con = httpplus.HTTPConnection('1.2.3.4:80')
96 con._connect()
97 sock = con.sock
98 sock.data = ['HTTP/1.1 200 OK\r\n',
99 'Server: BogusServer 1.0\r\n',
100 'transfer-encoding: chunked',
101 '\r\n\r\n',
102 chunkedblock('hi '),
103 ] + list(chunkedblock('there')) + [
104 chunkedblock(''),
105 ]
106 con.request('GET', '/')
107 resp = con.getresponse()
108 for amt, expect in [(1, 'h'), (5, 'i the'), (100, 're')]:
109 self.assertEqual(expect, resp.read(amt))
110
94 def testChunkedDownloadBadEOL(self):
111 def testChunkedDownloadBadEOL(self):
95 con = http.HTTPConnection('1.2.3.4:80')
112 con = httpplus.HTTPConnection('1.2.3.4:80')
96 con._connect()
113 con._connect()
97 sock = con.sock
114 sock = con.sock
98 sock.data = ['HTTP/1.1 200 OK\n',
115 sock.data = ['HTTP/1.1 200 OK\n',
@@ -107,7 +124,7 b' class ChunkedTransferTest(util.HttpTestB'
107 self.assertStringEqual('hi there', con.getresponse().read())
124 self.assertStringEqual('hi there', con.getresponse().read())
108
125
109 def testChunkedDownloadPartialChunkBadEOL(self):
126 def testChunkedDownloadPartialChunkBadEOL(self):
110 con = http.HTTPConnection('1.2.3.4:80')
127 con = httpplus.HTTPConnection('1.2.3.4:80')
111 con._connect()
128 con._connect()
112 sock = con.sock
129 sock = con.sock
113 sock.data = ['HTTP/1.1 200 OK\n',
130 sock.data = ['HTTP/1.1 200 OK\n',
@@ -122,7 +139,7 b' class ChunkedTransferTest(util.HttpTestB'
122 con.getresponse().read())
139 con.getresponse().read())
123
140
124 def testChunkedDownloadPartialChunk(self):
141 def testChunkedDownloadPartialChunk(self):
125 con = http.HTTPConnection('1.2.3.4:80')
142 con = httpplus.HTTPConnection('1.2.3.4:80')
126 con._connect()
143 con._connect()
127 sock = con.sock
144 sock = con.sock
128 sock.data = ['HTTP/1.1 200 OK\r\n',
145 sock.data = ['HTTP/1.1 200 OK\r\n',
@@ -136,7 +153,7 b' class ChunkedTransferTest(util.HttpTestB'
136 con.getresponse().read())
153 con.getresponse().read())
137
154
138 def testChunkedDownloadEarlyHangup(self):
155 def testChunkedDownloadEarlyHangup(self):
139 con = http.HTTPConnection('1.2.3.4:80')
156 con = httpplus.HTTPConnection('1.2.3.4:80')
140 con._connect()
157 con._connect()
141 sock = con.sock
158 sock = con.sock
142 broken = chunkedblock('hi'*20)[:-1]
159 broken = chunkedblock('hi'*20)[:-1]
@@ -149,5 +166,5 b' class ChunkedTransferTest(util.HttpTestB'
149 sock.close_on_empty = True
166 sock.close_on_empty = True
150 con.request('GET', '/')
167 con.request('GET', '/')
151 resp = con.getresponse()
168 resp = con.getresponse()
152 self.assertRaises(http.HTTPRemoteClosedError, resp.read)
169 self.assertRaises(httpplus.HTTPRemoteClosedError, resp.read)
153 # no-check-code
170 # no-check-code
@@ -29,13 +29,13 b''
29 import unittest
29 import unittest
30 import socket
30 import socket
31
31
32 import http
32 import httpplus
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, close=False):
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
@@ -44,6 +44,7 b' def make_preloaded_socket(data):'
44 def s(*args, **kwargs):
44 def s(*args, **kwargs):
45 sock = util.MockSocket(*args, **kwargs)
45 sock = util.MockSocket(*args, **kwargs)
46 sock.early_data = data[:]
46 sock.early_data = data[:]
47 sock.close_on_empty = close
47 return sock
48 return sock
48 return s
49 return s
49
50
@@ -51,7 +52,7 b' def make_preloaded_socket(data):'
51 class ProxyHttpTest(util.HttpTestBase, unittest.TestCase):
52 class ProxyHttpTest(util.HttpTestBase, unittest.TestCase):
52
53
53 def _run_simple_test(self, host, server_data, expected_req, expected_data):
54 def _run_simple_test(self, host, server_data, expected_req, expected_data):
54 con = http.HTTPConnection(host)
55 con = httpplus.HTTPConnection(host)
55 con._connect()
56 con._connect()
56 con.sock.data = server_data
57 con.sock.data = server_data
57 con.request('GET', '/')
58 con.request('GET', '/')
@@ -60,7 +61,7 b' class ProxyHttpTest(util.HttpTestBase, u'
60 self.assertEqual(expected_data, con.getresponse().read())
61 self.assertEqual(expected_data, con.getresponse().read())
61
62
62 def testSimpleRequest(self):
63 def testSimpleRequest(self):
63 con = http.HTTPConnection('1.2.3.4:80',
64 con = httpplus.HTTPConnection('1.2.3.4:80',
64 proxy_hostport=('magicproxy', 4242))
65 proxy_hostport=('magicproxy', 4242))
65 con._connect()
66 con._connect()
66 con.sock.data = ['HTTP/1.1 200 OK\r\n',
67 con.sock.data = ['HTTP/1.1 200 OK\r\n',
@@ -88,7 +89,7 b' class ProxyHttpTest(util.HttpTestBase, u'
88 resp.headers.getheaders('server'))
89 resp.headers.getheaders('server'))
89
90
90 def testSSLRequest(self):
91 def testSSLRequest(self):
91 con = http.HTTPConnection('1.2.3.4:443',
92 con = httpplus.HTTPConnection('1.2.3.4:443',
92 proxy_hostport=('magicproxy', 4242))
93 proxy_hostport=('magicproxy', 4242))
93 socket.socket = make_preloaded_socket(
94 socket.socket = make_preloaded_socket(
94 ['HTTP/1.1 200 OK\r\n',
95 ['HTTP/1.1 200 OK\r\n',
@@ -124,12 +125,47 b' class ProxyHttpTest(util.HttpTestBase, u'
124 self.assertEqual(['BogusServer 1.0'],
125 self.assertEqual(['BogusServer 1.0'],
125 resp.headers.getheaders('server'))
126 resp.headers.getheaders('server'))
126
127
127 def testSSLProxyFailure(self):
128 def testSSLRequestNoConnectBody(self):
128 con = http.HTTPConnection('1.2.3.4:443',
129 con = httpplus.HTTPConnection('1.2.3.4:443',
129 proxy_hostport=('magicproxy', 4242))
130 proxy_hostport=('magicproxy', 4242))
130 socket.socket = make_preloaded_socket(
131 socket.socket = make_preloaded_socket(
131 ['HTTP/1.1 407 Proxy Authentication Required\r\n\r\n'])
132 ['HTTP/1.1 200 OK\r\n',
132 self.assertRaises(http.HTTPProxyConnectFailedException, con._connect)
133 'Server: BogusServer 1.0\r\n',
133 self.assertRaises(http.HTTPProxyConnectFailedException,
134 '\r\n'])
135 con._connect()
136 con.sock.data = ['HTTP/1.1 200 OK\r\n',
137 'Server: BogusServer 1.0\r\n',
138 'Content-Length: 10\r\n',
139 '\r\n'
140 '1234567890'
141 ]
142 connect_sent = con.sock.sent
143 con.sock.sent = ''
144 con.request('GET', '/')
145
146 expected_connect = ('CONNECT 1.2.3.4:443 HTTP/1.0\r\n'
147 'Host: 1.2.3.4\r\n'
148 'accept-encoding: identity\r\n'
149 '\r\n')
150 expected_request = ('GET / HTTP/1.1\r\n'
151 'Host: 1.2.3.4\r\n'
152 'accept-encoding: identity\r\n\r\n')
153
154 self.assertEqual(('127.0.0.42', 4242), con.sock.sa)
155 self.assertStringEqual(expected_connect, connect_sent)
156 self.assertStringEqual(expected_request, con.sock.sent)
157 resp = con.getresponse()
158 self.assertEqual(resp.status, 200)
159 self.assertEqual('1234567890', resp.read())
160 self.assertEqual(['BogusServer 1.0'],
161 resp.headers.getheaders('server'))
162
163 def testSSLProxyFailure(self):
164 con = httpplus.HTTPConnection('1.2.3.4:443',
165 proxy_hostport=('magicproxy', 4242))
166 socket.socket = make_preloaded_socket(
167 ['HTTP/1.1 407 Proxy Authentication Required\r\n\r\n'], close=True)
168 self.assertRaises(httpplus.HTTPProxyConnectFailedException, con._connect)
169 self.assertRaises(httpplus.HTTPProxyConnectFailedException,
134 con.request, 'GET', '/')
170 con.request, 'GET', '/')
135 # no-check-code
171 # no-check-code
@@ -28,7 +28,7 b''
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 httpplus
32
32
33 # relative import to ease embedding the library
33 # relative import to ease embedding the library
34 import util
34 import util
@@ -37,7 +37,7 b' import util'
37
37
38 class HttpSslTest(util.HttpTestBase, unittest.TestCase):
38 class HttpSslTest(util.HttpTestBase, unittest.TestCase):
39 def testSslRereadRequired(self):
39 def testSslRereadRequired(self):
40 con = http.HTTPConnection('1.2.3.4:443')
40 con = httpplus.HTTPConnection('1.2.3.4:443')
41 con._connect()
41 con._connect()
42 # extend the list instead of assign because of how
42 # extend the list instead of assign because of how
43 # MockSSLSocket works.
43 # MockSSLSocket works.
@@ -66,7 +66,7 b' class HttpSslTest(util.HttpTestBase, uni'
66 resp.headers.getheaders('server'))
66 resp.headers.getheaders('server'))
67
67
68 def testSslRereadInEarlyResponse(self):
68 def testSslRereadInEarlyResponse(self):
69 con = http.HTTPConnection('1.2.3.4:443')
69 con = httpplus.HTTPConnection('1.2.3.4:443')
70 con._connect()
70 con._connect()
71 con.sock.early_data = ['HTTP/1.1 200 OK\r\n',
71 con.sock.early_data = ['HTTP/1.1 200 OK\r\n',
72 'Server: BogusServer 1.0\r\n',
72 'Server: BogusServer 1.0\r\n',
@@ -29,7 +29,7 b''
29 import difflib
29 import difflib
30 import socket
30 import socket
31
31
32 import http
32 import httpplus
33
33
34
34
35 class MockSocket(object):
35 class MockSocket(object):
@@ -57,7 +57,7 b' class MockSocket(object):'
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 = httpplus._END_HEADERS
61
61
62 def close(self):
62 def close(self):
63 self.closed = True
63 self.closed = True
@@ -86,7 +86,7 b' class MockSocket(object):'
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 httpplus._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 or self.remote_closed)
91 or self.closed or self.remote_closed)
92
92
@@ -132,7 +132,7 b' class MockSSLSocket(object):'
132
132
133
133
134 def mocksslwrap(sock, keyfile=None, certfile=None,
134 def mocksslwrap(sock, keyfile=None, certfile=None,
135 server_side=False, cert_reqs=http.socketutil.CERT_NONE,
135 server_side=False, cert_reqs=httpplus.socketutil.CERT_NONE,
136 ssl_version=None, ca_certs=None,
136 ssl_version=None, ca_certs=None,
137 do_handshake_on_connect=True,
137 do_handshake_on_connect=True,
138 suppress_ragged_eofs=True):
138 suppress_ragged_eofs=True):
@@ -156,16 +156,16 b' class HttpTestBase(object):'
156 self.orig_getaddrinfo = socket.getaddrinfo
156 self.orig_getaddrinfo = socket.getaddrinfo
157 socket.getaddrinfo = mockgetaddrinfo
157 socket.getaddrinfo = mockgetaddrinfo
158
158
159 self.orig_select = http.select.select
159 self.orig_select = httpplus.select.select
160 http.select.select = mockselect
160 httpplus.select.select = mockselect
161
161
162 self.orig_sslwrap = http.socketutil.wrap_socket
162 self.orig_sslwrap = httpplus.socketutil.wrap_socket
163 http.socketutil.wrap_socket = mocksslwrap
163 httpplus.socketutil.wrap_socket = mocksslwrap
164
164
165 def tearDown(self):
165 def tearDown(self):
166 socket.socket = self.orig_socket
166 socket.socket = self.orig_socket
167 http.select.select = self.orig_select
167 httpplus.select.select = self.orig_select
168 http.socketutil.wrap_socket = self.orig_sslwrap
168 httpplus.socketutil.wrap_socket = self.orig_sslwrap
169 socket.getaddrinfo = self.orig_getaddrinfo
169 socket.getaddrinfo = self.orig_getaddrinfo
170
170
171 def assertStringEqual(self, l, r):
171 def assertStringEqual(self, l, r):
General Comments 0
You need to be logged in to leave comments. Login now