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