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 import socket |
|
30 import unittest |
|
31 |
|
32 import http |
|
33 |
|
34 # relative import to ease embedding the library |
|
35 import util |
|
36 |
|
37 |
|
38 class SimpleHttpTest(util.HttpTestBase, unittest.TestCase): |
|
39 |
|
40 def _run_simple_test(self, host, server_data, expected_req, expected_data): |
|
41 con = http.HTTPConnection(host) |
|
42 con._connect() |
|
43 con.sock.data = server_data |
|
44 con.request('GET', '/') |
|
45 |
|
46 self.assertStringEqual(expected_req, con.sock.sent) |
|
47 self.assertEqual(expected_data, con.getresponse().read()) |
|
48 |
|
49 def test_broken_data_obj(self): |
|
50 con = http.HTTPConnection('1.2.3.4:80') |
|
51 con._connect() |
|
52 self.assertRaises(http.BadRequestData, |
|
53 con.request, 'POST', '/', body=1) |
|
54 |
|
55 def test_no_keepalive_http_1_0(self): |
|
56 expected_request_one = """GET /remote/.hg/requires HTTP/1.1 |
|
57 Host: localhost:9999 |
|
58 range: bytes=0- |
|
59 accept-encoding: identity |
|
60 accept: application/mercurial-0.1 |
|
61 user-agent: mercurial/proto-1.0 |
|
62 |
|
63 """.replace('\n', '\r\n') |
|
64 expected_response_headers = """HTTP/1.0 200 OK |
|
65 Server: SimpleHTTP/0.6 Python/2.6.1 |
|
66 Date: Sun, 01 May 2011 13:56:57 GMT |
|
67 Content-type: application/octet-stream |
|
68 Content-Length: 33 |
|
69 Last-Modified: Sun, 01 May 2011 13:56:56 GMT |
|
70 |
|
71 """.replace('\n', '\r\n') |
|
72 expected_response_body = """revlogv1 |
|
73 store |
|
74 fncache |
|
75 dotencode |
|
76 """ |
|
77 con = http.HTTPConnection('localhost:9999') |
|
78 con._connect() |
|
79 con.sock.data = [expected_response_headers, expected_response_body] |
|
80 con.request('GET', '/remote/.hg/requires', |
|
81 headers={'accept-encoding': 'identity', |
|
82 'range': 'bytes=0-', |
|
83 'accept': 'application/mercurial-0.1', |
|
84 'user-agent': 'mercurial/proto-1.0', |
|
85 }) |
|
86 self.assertStringEqual(expected_request_one, con.sock.sent) |
|
87 self.assertEqual(con.sock.closed, False) |
|
88 self.assertNotEqual(con.sock.data, []) |
|
89 self.assert_(con.busy()) |
|
90 resp = con.getresponse() |
|
91 self.assertStringEqual(resp.read(), expected_response_body) |
|
92 self.failIf(con.busy()) |
|
93 self.assertEqual(con.sock, None) |
|
94 self.assertEqual(resp.sock.data, []) |
|
95 self.assert_(resp.sock.closed) |
|
96 |
|
97 def test_multiline_header(self): |
|
98 con = http.HTTPConnection('1.2.3.4:80') |
|
99 con._connect() |
|
100 con.sock.data = ['HTTP/1.1 200 OK\r\n', |
|
101 'Server: BogusServer 1.0\r\n', |
|
102 'Multiline: Value\r\n', |
|
103 ' Rest of value\r\n', |
|
104 'Content-Length: 10\r\n', |
|
105 '\r\n' |
|
106 '1234567890' |
|
107 ] |
|
108 con.request('GET', '/') |
|
109 |
|
110 expected_req = ('GET / HTTP/1.1\r\n' |
|
111 'Host: 1.2.3.4\r\n' |
|
112 'accept-encoding: identity\r\n\r\n') |
|
113 |
|
114 self.assertEqual(('1.2.3.4', 80), con.sock.sa) |
|
115 self.assertEqual(expected_req, con.sock.sent) |
|
116 resp = con.getresponse() |
|
117 self.assertEqual('1234567890', resp.read()) |
|
118 self.assertEqual(['Value\n Rest of value'], |
|
119 resp.headers.getheaders('multiline')) |
|
120 # Socket should not be closed |
|
121 self.assertEqual(resp.sock.closed, False) |
|
122 self.assertEqual(con.sock.closed, False) |
|
123 |
|
124 def testSimpleRequest(self): |
|
125 con = http.HTTPConnection('1.2.3.4:80') |
|
126 con._connect() |
|
127 con.sock.data = ['HTTP/1.1 200 OK\r\n', |
|
128 'Server: BogusServer 1.0\r\n', |
|
129 'MultiHeader: Value\r\n' |
|
130 'MultiHeader: Other Value\r\n' |
|
131 'MultiHeader: One More!\r\n' |
|
132 'Content-Length: 10\r\n', |
|
133 '\r\n' |
|
134 '1234567890' |
|
135 ] |
|
136 con.request('GET', '/') |
|
137 |
|
138 expected_req = ('GET / HTTP/1.1\r\n' |
|
139 'Host: 1.2.3.4\r\n' |
|
140 'accept-encoding: identity\r\n\r\n') |
|
141 |
|
142 self.assertEqual(('1.2.3.4', 80), con.sock.sa) |
|
143 self.assertEqual(expected_req, con.sock.sent) |
|
144 resp = con.getresponse() |
|
145 self.assertEqual('1234567890', resp.read()) |
|
146 self.assertEqual(['Value', 'Other Value', 'One More!'], |
|
147 resp.headers.getheaders('multiheader')) |
|
148 self.assertEqual(['BogusServer 1.0'], |
|
149 resp.headers.getheaders('server')) |
|
150 |
|
151 def testHeaderlessResponse(self): |
|
152 con = http.HTTPConnection('1.2.3.4', use_ssl=False) |
|
153 con._connect() |
|
154 con.sock.data = ['HTTP/1.1 200 OK\r\n', |
|
155 '\r\n' |
|
156 '1234567890' |
|
157 ] |
|
158 con.request('GET', '/') |
|
159 |
|
160 expected_req = ('GET / HTTP/1.1\r\n' |
|
161 'Host: 1.2.3.4\r\n' |
|
162 'accept-encoding: identity\r\n\r\n') |
|
163 |
|
164 self.assertEqual(('1.2.3.4', 80), con.sock.sa) |
|
165 self.assertEqual(expected_req, con.sock.sent) |
|
166 resp = con.getresponse() |
|
167 self.assertEqual('1234567890', resp.read()) |
|
168 self.assertEqual({}, dict(resp.headers)) |
|
169 self.assertEqual(resp.status, 200) |
|
170 |
|
171 def testReadline(self): |
|
172 con = http.HTTPConnection('1.2.3.4') |
|
173 con._connect() |
|
174 # make sure it trickles in one byte at a time |
|
175 # so that we touch all the cases in readline |
|
176 con.sock.data = list(''.join( |
|
177 ['HTTP/1.1 200 OK\r\n', |
|
178 'Server: BogusServer 1.0\r\n', |
|
179 'Connection: Close\r\n', |
|
180 '\r\n' |
|
181 '1\n2\nabcdefg\n4\n5'])) |
|
182 |
|
183 expected_req = ('GET / HTTP/1.1\r\n' |
|
184 'Host: 1.2.3.4\r\n' |
|
185 'accept-encoding: identity\r\n\r\n') |
|
186 |
|
187 con.request('GET', '/') |
|
188 self.assertEqual(('1.2.3.4', 80), con.sock.sa) |
|
189 self.assertEqual(expected_req, con.sock.sent) |
|
190 r = con.getresponse() |
|
191 for expected in ['1\n', '2\n', 'abcdefg\n', '4\n', '5']: |
|
192 actual = r.readline() |
|
193 self.assertEqual(expected, actual, |
|
194 'Expected %r, got %r' % (expected, actual)) |
|
195 |
|
196 def testIPv6(self): |
|
197 self._run_simple_test('[::1]:8221', |
|
198 ['HTTP/1.1 200 OK\r\n', |
|
199 'Server: BogusServer 1.0\r\n', |
|
200 'Content-Length: 10', |
|
201 '\r\n\r\n' |
|
202 '1234567890'], |
|
203 ('GET / HTTP/1.1\r\n' |
|
204 'Host: [::1]:8221\r\n' |
|
205 'accept-encoding: identity\r\n\r\n'), |
|
206 '1234567890') |
|
207 self._run_simple_test('::2', |
|
208 ['HTTP/1.1 200 OK\r\n', |
|
209 'Server: BogusServer 1.0\r\n', |
|
210 'Content-Length: 10', |
|
211 '\r\n\r\n' |
|
212 '1234567890'], |
|
213 ('GET / HTTP/1.1\r\n' |
|
214 'Host: ::2\r\n' |
|
215 'accept-encoding: identity\r\n\r\n'), |
|
216 '1234567890') |
|
217 self._run_simple_test('[::3]:443', |
|
218 ['HTTP/1.1 200 OK\r\n', |
|
219 'Server: BogusServer 1.0\r\n', |
|
220 'Content-Length: 10', |
|
221 '\r\n\r\n' |
|
222 '1234567890'], |
|
223 ('GET / HTTP/1.1\r\n' |
|
224 'Host: ::3\r\n' |
|
225 'accept-encoding: identity\r\n\r\n'), |
|
226 '1234567890') |
|
227 |
|
228 def testEarlyContinueResponse(self): |
|
229 con = http.HTTPConnection('1.2.3.4:80') |
|
230 con._connect() |
|
231 sock = con.sock |
|
232 sock.data = ['HTTP/1.1 403 Forbidden\r\n', |
|
233 'Server: BogusServer 1.0\r\n', |
|
234 'Content-Length: 18', |
|
235 '\r\n\r\n' |
|
236 "You can't do that."] |
|
237 expected_req = self.doPost(con, expect_body=False) |
|
238 self.assertEqual(('1.2.3.4', 80), sock.sa) |
|
239 self.assertStringEqual(expected_req, sock.sent) |
|
240 self.assertEqual("You can't do that.", con.getresponse().read()) |
|
241 self.assertEqual(sock.closed, True) |
|
242 |
|
243 def testDeniedAfterContinueTimeoutExpires(self): |
|
244 con = http.HTTPConnection('1.2.3.4:80') |
|
245 con._connect() |
|
246 sock = con.sock |
|
247 sock.data = ['HTTP/1.1 403 Forbidden\r\n', |
|
248 'Server: BogusServer 1.0\r\n', |
|
249 'Content-Length: 18\r\n', |
|
250 'Connection: close', |
|
251 '\r\n\r\n' |
|
252 "You can't do that."] |
|
253 sock.read_wait_sentinel = 'Dear server, send response!' |
|
254 sock.close_on_empty = True |
|
255 # send enough data out that we'll chunk it into multiple |
|
256 # blocks and the socket will close before we can send the |
|
257 # whole request. |
|
258 post_body = ('This is some POST data\n' * 1024 * 32 + |
|
259 'Dear server, send response!\n' + |
|
260 'This is some POST data\n' * 1024 * 32) |
|
261 expected_req = self.doPost(con, expect_body=False, |
|
262 body_to_send=post_body) |
|
263 self.assertEqual(('1.2.3.4', 80), sock.sa) |
|
264 self.assert_('POST data\n' in sock.sent) |
|
265 self.assert_('Dear server, send response!\n' in sock.sent) |
|
266 # We expect not all of our data was sent. |
|
267 self.assertNotEqual(sock.sent, expected_req) |
|
268 self.assertEqual("You can't do that.", con.getresponse().read()) |
|
269 self.assertEqual(sock.closed, True) |
|
270 |
|
271 def testPostData(self): |
|
272 con = http.HTTPConnection('1.2.3.4:80') |
|
273 con._connect() |
|
274 sock = con.sock |
|
275 sock.read_wait_sentinel = 'POST data' |
|
276 sock.early_data = ['HTTP/1.1 100 Co', 'ntinue\r\n\r\n'] |
|
277 sock.data = ['HTTP/1.1 200 OK\r\n', |
|
278 'Server: BogusServer 1.0\r\n', |
|
279 'Content-Length: 16', |
|
280 '\r\n\r\n', |
|
281 "You can do that."] |
|
282 expected_req = self.doPost(con, expect_body=True) |
|
283 self.assertEqual(('1.2.3.4', 80), sock.sa) |
|
284 self.assertEqual(expected_req, sock.sent) |
|
285 self.assertEqual("You can do that.", con.getresponse().read()) |
|
286 self.assertEqual(sock.closed, False) |
|
287 |
|
288 def testServerWithoutContinue(self): |
|
289 con = http.HTTPConnection('1.2.3.4:80') |
|
290 con._connect() |
|
291 sock = con.sock |
|
292 sock.read_wait_sentinel = 'POST data' |
|
293 sock.data = ['HTTP/1.1 200 OK\r\n', |
|
294 'Server: BogusServer 1.0\r\n', |
|
295 'Content-Length: 16', |
|
296 '\r\n\r\n', |
|
297 "You can do that."] |
|
298 expected_req = self.doPost(con, expect_body=True) |
|
299 self.assertEqual(('1.2.3.4', 80), sock.sa) |
|
300 self.assertEqual(expected_req, sock.sent) |
|
301 self.assertEqual("You can do that.", con.getresponse().read()) |
|
302 self.assertEqual(sock.closed, False) |
|
303 |
|
304 def testServerWithSlowContinue(self): |
|
305 con = http.HTTPConnection('1.2.3.4:80') |
|
306 con._connect() |
|
307 sock = con.sock |
|
308 sock.read_wait_sentinel = 'POST data' |
|
309 sock.data = ['HTTP/1.1 100 ', 'Continue\r\n\r\n', |
|
310 'HTTP/1.1 200 OK\r\n', |
|
311 'Server: BogusServer 1.0\r\n', |
|
312 'Content-Length: 16', |
|
313 '\r\n\r\n', |
|
314 "You can do that."] |
|
315 expected_req = self.doPost(con, expect_body=True) |
|
316 self.assertEqual(('1.2.3.4', 80), sock.sa) |
|
317 self.assertEqual(expected_req, sock.sent) |
|
318 resp = con.getresponse() |
|
319 self.assertEqual("You can do that.", resp.read()) |
|
320 self.assertEqual(200, resp.status) |
|
321 self.assertEqual(sock.closed, False) |
|
322 |
|
323 def testSlowConnection(self): |
|
324 con = http.HTTPConnection('1.2.3.4:80') |
|
325 con._connect() |
|
326 # simulate one byte arriving at a time, to check for various |
|
327 # corner cases |
|
328 con.sock.data = list('HTTP/1.1 200 OK\r\n' |
|
329 'Server: BogusServer 1.0\r\n' |
|
330 'Content-Length: 10' |
|
331 '\r\n\r\n' |
|
332 '1234567890') |
|
333 con.request('GET', '/') |
|
334 |
|
335 expected_req = ('GET / HTTP/1.1\r\n' |
|
336 'Host: 1.2.3.4\r\n' |
|
337 'accept-encoding: identity\r\n\r\n') |
|
338 |
|
339 self.assertEqual(('1.2.3.4', 80), con.sock.sa) |
|
340 self.assertEqual(expected_req, con.sock.sent) |
|
341 self.assertEqual('1234567890', con.getresponse().read()) |
|
342 |
|
343 def testTimeout(self): |
|
344 con = http.HTTPConnection('1.2.3.4:80') |
|
345 con._connect() |
|
346 con.sock.data = [] |
|
347 con.request('GET', '/') |
|
348 self.assertRaises(http.HTTPTimeoutException, |
|
349 con.getresponse) |
|
350 |
|
351 expected_req = ('GET / HTTP/1.1\r\n' |
|
352 'Host: 1.2.3.4\r\n' |
|
353 'accept-encoding: identity\r\n\r\n') |
|
354 |
|
355 self.assertEqual(('1.2.3.4', 80), con.sock.sa) |
|
356 self.assertEqual(expected_req, con.sock.sent) |
|
357 |
|
358 def test_conn_keep_alive_but_server_close_anyway(self): |
|
359 sockets = [] |
|
360 def closingsocket(*args, **kwargs): |
|
361 s = util.MockSocket(*args, **kwargs) |
|
362 sockets.append(s) |
|
363 s.data = ['HTTP/1.1 200 OK\r\n', |
|
364 'Server: BogusServer 1.0\r\n', |
|
365 'Connection: Keep-Alive\r\n', |
|
366 'Content-Length: 16', |
|
367 '\r\n\r\n', |
|
368 'You can do that.'] |
|
369 s.close_on_empty = True |
|
370 return s |
|
371 |
|
372 socket.socket = closingsocket |
|
373 con = http.HTTPConnection('1.2.3.4:80') |
|
374 con._connect() |
|
375 con.request('GET', '/') |
|
376 r1 = con.getresponse() |
|
377 r1.read() |
|
378 self.assertFalse(con.sock.closed) |
|
379 self.assert_(con.sock.remote_closed) |
|
380 con.request('GET', '/') |
|
381 self.assertEqual(2, len(sockets)) |
|
382 |
|
383 def test_server_closes_before_end_of_body(self): |
|
384 con = http.HTTPConnection('1.2.3.4:80') |
|
385 con._connect() |
|
386 s = con.sock |
|
387 s.data = ['HTTP/1.1 200 OK\r\n', |
|
388 'Server: BogusServer 1.0\r\n', |
|
389 'Connection: Keep-Alive\r\n', |
|
390 'Content-Length: 16', |
|
391 '\r\n\r\n', |
|
392 'You can '] # Note: this is shorter than content-length |
|
393 s.close_on_empty = True |
|
394 con.request('GET', '/') |
|
395 r1 = con.getresponse() |
|
396 self.assertRaises(http.HTTPRemoteClosedError, r1.read) |
|
397 |
|
398 def test_no_response_raises_response_not_ready(self): |
|
399 con = http.HTTPConnection('foo') |
|
400 self.assertRaises(http.httplib.ResponseNotReady, con.getresponse) |
|
401 # no-check-code |
|