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