blob: 29d67af4272a9259e39d4b1a75b53cdb8cd166f3 [file] [log] [blame]
Sergey Shepelev0112eff2017-05-05 06:46:43 +03001import email.utils
2import httplib2
3import mock
4import os
5import pytest
6import socket
7import tests
8from six.moves import http_client, urllib
9
10
11dummy_url = 'http://127.0.0.1:1'
12
13
14def test_connection_type():
15 http = httplib2.Http()
16 http.force_exception_to_status_code = False
17 response, content = http.request(dummy_url, connection_type=tests.MockHTTPConnection)
18 assert response['content-location'] == dummy_url
19 assert content == b'the body'
20
21
22def test_bad_status_line_retry():
23 http = httplib2.Http()
24 old_retries = httplib2.RETRIES
25 httplib2.RETRIES = 1
26 http.force_exception_to_status_code = False
27 try:
28 response, content = http.request(dummy_url, connection_type=tests.MockHTTPBadStatusConnection)
29 except http_client.BadStatusLine:
30 assert tests.MockHTTPBadStatusConnection.num_calls == 2
31 httplib2.RETRIES = old_retries
32
33
34def test_unknown_server():
35 http = httplib2.Http()
36 http.force_exception_to_status_code = False
37 with tests.assert_raises(httplib2.ServerNotFoundError):
38 with mock.patch('socket.socket.connect', side_effect=socket.gaierror):
39 http.request("http://no-such-hostname./")
40
41 # Now test with exceptions turned off
42 http.force_exception_to_status_code = True
43 response, content = http.request("http://no-such-hostname./")
44 assert response['content-type'] == 'text/plain'
45 assert content.startswith(b"Unable to find")
46 assert response.status == 400
47
48
49def test_connection_refused():
50 http = httplib2.Http()
51 http.force_exception_to_status_code = False
52 with tests.assert_raises(socket.error):
53 http.request(dummy_url)
54
55 # Now test with exceptions turned off
56 http.force_exception_to_status_code = True
57 response, content = http.request(dummy_url)
58 assert response['content-type'] == 'text/plain'
59 assert (b"Connection refused" in content or b"actively refused" in content)
60 assert response.status == 400
61
62
63def test_get_iri():
64 http = httplib2.Http()
65 query = u'?a=\N{CYRILLIC CAPITAL LETTER DJE}'
66 with tests.server_reflect() as uri:
67 response, content = http.request(uri + query, 'GET')
68 assert response.status == 200
69 reflected = tests.HttpRequest.from_bytes(content)
70 assert reflected.uri == '/?a=%D0%82'
71
72
73def test_get_is_default_method():
74 # Test that GET is the default method
75 http = httplib2.Http()
76 with tests.server_reflect() as uri:
77 response, content = http.request(uri)
78 assert response.status == 200
79 reflected = tests.HttpRequest.from_bytes(content)
80 assert reflected.method == 'GET'
81
82
83def test_different_methods():
84 # Test that all methods can be used
85 http = httplib2.Http()
86 methods = ['GET', 'PUT', 'DELETE', 'POST', 'unknown']
87 with tests.server_reflect(request_count=len(methods)) as uri:
88 for method in methods:
89 response, content = http.request(uri, method, body=b" ")
90 assert response.status == 200
91 reflected = tests.HttpRequest.from_bytes(content)
92 assert reflected.method == method
93
94
95def test_head_read():
96 # Test that we don't try to read the response of a HEAD request
97 # since httplib blocks response.read() for HEAD requests.
98 http = httplib2.Http()
99 respond_with = b'HTTP/1.0 200 OK\r\ncontent-length: 14\r\n\r\nnon-empty-body'
100 with tests.server_const_bytes(respond_with) as uri:
101 response, content = http.request(uri, 'HEAD')
102 assert response.status == 200
103 assert content == b""
104
105
106def test_get_no_cache():
107 # Test that can do a GET w/o the cache turned on.
108 http = httplib2.Http()
109 with tests.server_const_http() as uri:
110 response, content = http.request(uri, 'GET')
111 assert response.status == 200
112 assert response.previous is None
113
114
115def test_user_agent():
116 # Test that we provide a default user-agent
117 http = httplib2.Http()
118 with tests.server_reflect() as uri:
119 response, content = http.request(uri, 'GET')
120 assert response.status == 200
121 reflected = tests.HttpRequest.from_bytes(content)
122 assert reflected.headers.get('user-agent', '').startswith('Python-httplib2/')
123
124
125def test_user_agent_non_default():
126 # Test that the default user-agent can be over-ridden
127 http = httplib2.Http()
128 with tests.server_reflect() as uri:
129 response, content = http.request(uri, 'GET', headers={'User-Agent': 'fred/1.0'})
130 assert response.status == 200
131 reflected = tests.HttpRequest.from_bytes(content)
132 assert reflected.headers.get('user-agent') == 'fred/1.0'
133
134
135def test_get_300_with_location():
136 # Test the we automatically follow 300 redirects if a Location: header is provided
137 http = httplib2.Http()
138 final_content = b'This is the final destination.\n'
139 routes = {
140 '/final': tests.http_response_bytes(body=final_content),
141 '': tests.http_response_bytes(status='300 Multiple Choices', headers={'location': '/final'}),
142 }
143 with tests.server_route(routes, request_count=2) as uri:
144 response, content = http.request(uri, 'GET')
145 assert response.status == 200
146 assert content == final_content
147 assert response.previous.status == 300
148 assert not response.previous.fromcache
149
150 # Confirm that the intermediate 300 is not cached
151 with tests.server_route(routes, request_count=2) as uri:
152 response, content = http.request(uri, 'GET')
153 assert response.status == 200
154 assert content == final_content
155 assert response.previous.status == 300
156 assert not response.previous.fromcache
157
158
159def test_get_300_with_location_noredirect():
160 # Test the we automatically follow 300 redirects if a Location: header is provided
161 http = httplib2.Http()
162 http.follow_redirects = False
163 response = tests.http_response_bytes(
164 status='300 Multiple Choices',
165 headers={'location': '/final'},
166 body=b'redirect body')
167 with tests.server_const_bytes(response) as uri:
168 response, content = http.request(uri, 'GET')
169 assert response.status == 300
170
171
172def test_get_300_without_location():
173 # Not giving a Location: header in a 300 response is acceptable
174 # In which case we just return the 300 response
175 http = httplib2.Http()
176 with tests.server_const_http(status='300 Multiple Choices', body=b'redirect body') as uri:
177 response, content = http.request(uri, 'GET')
178 assert response.status == 300
179 assert response.previous is None
180 assert content == b'redirect body'
181
182
183def test_get_301():
184 # Test that we automatically follow 301 redirects
185 # and that we cache the 301 response
186 http = httplib2.Http(cache=tests.get_cache_path())
187 destination = ''
188 routes = {
189 '/final': tests.http_response_bytes(body=b'This is the final destination.\n'),
190 '': tests.http_response_bytes(
191 status='301 Now where did I leave that URL', headers={'location': '/final'}, body=b'redirect body'),
192 }
193 with tests.server_route(routes, request_count=3) as uri:
194 destination = urllib.parse.urljoin(uri, '/final')
195 response1, content1 = http.request(uri, 'GET')
196 response2, content2 = http.request(uri, 'GET')
197 assert response1.status == 200
198 assert 'content-location' in response2
199 assert response1['content-location'] == destination
200 assert content1 == b'This is the final destination.\n'
201 assert response1.previous.status == 301
202 assert not response1.previous.fromcache
203
204 assert response2.status == 200
205 assert response2['content-location'] == destination
206 assert content2 == b'This is the final destination.\n'
207 assert response2.previous.status == 301
208 assert response2.previous.fromcache
209
210
211@pytest.mark.skip(
212 not os.environ.get('httplib2_test_still_run_skipped') and
213 os.environ.get('TRAVIS_PYTHON_VERSION') in ('2.7', 'pypy'),
214 reason='FIXME: timeout on Travis py27 and pypy, works elsewhere',
215)
216def test_head_301():
217 # Test that we automatically follow 301 redirects
218 http = httplib2.Http()
219 destination = ''
220 routes = {
221 '/final': tests.http_response_bytes(body=b'This is the final destination.\n'),
222 '': tests.http_response_bytes(
223 status='301 Now where did I leave that URL', headers={'location': '/final'}, body=b'redirect body'),
224 }
225 with tests.server_route(routes, request_count=2) as uri:
226 destination = urllib.parse.urljoin(uri, '/final')
227 response, content = http.request(uri, 'HEAD')
228 assert response.status == 200
229 assert response['content-location'] == destination
230 assert response.previous.status == 301
231 assert not response.previous.fromcache
232
233
234@pytest.mark.xfail(reason='FIXME: 301 cache works only with follow_redirects, should work regardless')
235def test_get_301_no_redirect():
236 # Test that we cache the 301 response
237 http = httplib2.Http(cache=tests.get_cache_path(), timeout=0.5)
238 http.follow_redirects = False
239 response = tests.http_response_bytes(
240 status='301 Now where did I leave that URL',
241 headers={'location': '/final', 'cache-control': 'max-age=300'},
242 body=b'redirect body',
243 add_date=True,
244 )
245 with tests.server_const_bytes(response) as uri:
246 response, _ = http.request(uri, 'GET')
247 assert response.status == 301
248 assert not response.fromcache
249 response, _ = http.request(uri, 'GET')
250 assert response.status == 301
251 assert response.fromcache
252
253
254def test_get_302():
255 # Test that we automatically follow 302 redirects
256 # and that we DO NOT cache the 302 response
257 http = httplib2.Http(cache=tests.get_cache_path())
258 second_url, final_url = '', ''
259 routes = {
260 '/final': tests.http_response_bytes(body=b'This is the final destination.\n'),
261 '/second': tests.http_response_bytes(
262 status='302 Found', headers={'location': '/final'}, body=b'second redirect'),
263 '': tests.http_response_bytes(
264 status='302 Found', headers={'location': '/second'}, body=b'redirect body'),
265 }
266 with tests.server_route(routes, request_count=7) as uri:
267 second_url = urllib.parse.urljoin(uri, '/second')
268 final_url = urllib.parse.urljoin(uri, '/final')
269 response1, content1 = http.request(second_url, 'GET')
270 response2, content2 = http.request(second_url, 'GET')
271 response3, content3 = http.request(uri, 'GET')
272 assert response1.status == 200
273 assert response1['content-location'] == final_url
274 assert content1 == b'This is the final destination.\n'
275 assert response1.previous.status == 302
276 assert not response1.previous.fromcache
277
278 assert response2.status == 200
279 # FIXME:
280 # assert response2.fromcache
281 assert response2['content-location'] == final_url
282 assert content2 == b'This is the final destination.\n'
283 assert response2.previous.status == 302
284 assert not response2.previous.fromcache
285 assert response2.previous['content-location'] == second_url
286
287 assert response3.status == 200
288 # FIXME:
289 # assert response3.fromcache
290 assert content3 == b'This is the final destination.\n'
291 assert response3.previous.status == 302
292 assert not response3.previous.fromcache
293
294
295def test_get_302_redirection_limit():
296 # Test that we can set a lower redirection limit
297 # and that we raise an exception when we exceed
298 # that limit.
299 http = httplib2.Http()
300 http.force_exception_to_status_code = False
301 routes = {
302 '/second': tests.http_response_bytes(
303 status='302 Found', headers={'location': '/final'}, body=b'second redirect'),
304 '': tests.http_response_bytes(
305 status='302 Found', headers={'location': '/second'}, body=b'redirect body'),
306 }
307 with tests.server_route(routes, request_count=4) as uri:
308 try:
309 http.request(uri, 'GET', redirections=1)
310 assert False, 'This should not happen'
311 except httplib2.RedirectLimit:
312 pass
313 except Exception:
314 assert False, 'Threw wrong kind of exception '
315
316 # Re-run the test with out the exceptions
317 http.force_exception_to_status_code = True
318 response, content = http.request(uri, 'GET', redirections=1)
319
320 assert response.status == 500
321 assert response.reason.startswith('Redirected more')
322 assert response['status'] == '302'
323 assert content == b'second redirect'
324 assert response.previous is not None
325
326
327def test_get_302_no_location():
328 # Test that we throw an exception when we get
329 # a 302 with no Location: header.
330 http = httplib2.Http()
331 http.force_exception_to_status_code = False
332 with tests.server_const_http(status='302 Found', request_count=2) as uri:
333 try:
334 http.request(uri, 'GET')
335 assert False, 'Should never reach here'
336 except httplib2.RedirectMissingLocation:
337 pass
338 except Exception:
339 assert False, 'Threw wrong kind of exception '
340
341 # Re-run the test with out the exceptions
342 http.force_exception_to_status_code = True
343 response, content = http.request(uri, 'GET')
344
345 assert response.status == 500
346 assert response.reason.startswith('Redirected but')
347 assert '302' == response['status']
348 assert content == b''
349
350
351@pytest.mark.skip(
352 not os.environ.get('httplib2_test_still_run_skipped') and
353 os.environ.get('TRAVIS_PYTHON_VERSION') in ('2.7', 'pypy'),
354 reason='FIXME: timeout on Travis py27 and pypy, works elsewhere',
355)
356def test_303():
357 # Do a follow-up GET on a Location: header
358 # returned from a POST that gave a 303.
359 http = httplib2.Http()
360 routes = {
361 '/final': tests.make_http_reflect(),
362 '': tests.make_http_reflect(status='303 See Other', headers={'location': '/final'}),
363 }
364 with tests.server_route(routes, request_count=2) as uri:
365 response, content = http.request(uri, 'POST', " ")
366 assert response.status == 200
367 reflected = tests.HttpRequest.from_bytes(content)
368 assert reflected.uri == '/final'
369 assert response.previous.status == 303
370
371 # Skip follow-up GET
372 http = httplib2.Http()
373 http.follow_redirects = False
374 with tests.server_route(routes, request_count=1) as uri:
375 response, content = http.request(uri, 'POST', " ")
376 assert response.status == 303
377
378 # All methods can be used
379 http = httplib2.Http()
380 cases = 'DELETE GET HEAD POST PUT EVEN_NEW_ONES'.split(' ')
381 with tests.server_route(routes, request_count=len(cases) * 2) as uri:
382 for method in cases:
383 response, content = http.request(uri, method, body=b'q q')
384 assert response.status == 200
385 reflected = tests.HttpRequest.from_bytes(content)
386 assert reflected.method == 'GET'
387
388
389def test_etag_used():
390 # Test that we use ETags properly to validate our cache
391 cache_path = tests.get_cache_path()
392 http = httplib2.Http(cache=cache_path)
393 response_kwargs = dict(
394 add_date=True,
395 add_etag=True,
396 body=b'something',
397 headers={
398 'cache-control': 'public,max-age=300',
399 },
400 )
401
402 def handler(request):
403 if request.headers.get('range'):
404 return tests.http_response_bytes(status=206, **response_kwargs)
405 return tests.http_response_bytes(**response_kwargs)
406
407 with tests.server_request(handler, request_count=2) as uri:
408 response, _ = http.request(uri, 'GET', headers={'accept-encoding': 'identity'})
409 assert response['etag'] == '"437b930db84b8079c2dd804a71936b5f"'
410
411 http.request(uri, 'GET', headers={'accept-encoding': 'identity'})
412 response, _ = http.request(
413 uri, 'GET',
414 headers={'accept-encoding': 'identity', 'cache-control': 'must-revalidate'},
415 )
416 assert response.status == 200
417 assert response.fromcache
418
419 # TODO: API to read cache item, at least internal to tests
420 cache_file_name = os.path.join(cache_path, httplib2.safename(httplib2.urlnorm(uri)[-1]))
421 with open(cache_file_name, 'r') as f:
422 status_line = f.readline()
423 assert status_line.startswith("status:")
424
425 response, content = http.request(uri, 'HEAD', headers={'accept-encoding': 'identity'})
426 assert response.status == 200
427 assert response.fromcache
428
429 response, content = http.request(uri, 'GET', headers={'accept-encoding': 'identity', 'range': 'bytes=0-0'})
430 assert response.status == 206
431 assert not response.fromcache
432
433
434def test_etag_ignore():
435 # Test that we can forcibly ignore ETags
436 http = httplib2.Http(cache=tests.get_cache_path())
437 response_kwargs = dict(
438 add_date=True,
439 add_etag=True,
440 )
441 with tests.server_reflect(request_count=3, **response_kwargs) as uri:
442 response, content = http.request(uri, 'GET', headers={'accept-encoding': 'identity'})
443 assert response.status == 200
444 assert response['etag'] != ""
445
446 response, content = http.request(
447 uri, 'GET',
448 headers={'accept-encoding': 'identity', 'cache-control': 'max-age=0'},
449 )
450 reflected = tests.HttpRequest.from_bytes(content)
451 assert reflected.headers.get('if-none-match')
452
453 http.ignore_etag = True
454 response, content = http.request(
455 uri, 'GET',
456 headers={'accept-encoding': 'identity', 'cache-control': 'max-age=0'},
457 )
458 assert not response.fromcache
459 reflected = tests.HttpRequest.from_bytes(content)
460 assert not reflected.headers.get('if-none-match')
461
462
463def test_etag_override():
464 # Test that we can forcibly ignore ETags
465 http = httplib2.Http(cache=tests.get_cache_path())
466 response_kwargs = dict(
467 add_date=True,
468 add_etag=True,
469 )
470 with tests.server_reflect(request_count=3, **response_kwargs) as uri:
471 response, _ = http.request(uri, 'GET', headers={'accept-encoding': 'identity'})
472 assert response.status == 200
473 assert response['etag'] != ''
474
475 response, content = http.request(
476 uri, 'GET',
477 headers={'accept-encoding': 'identity', 'cache-control': 'max-age=0'},
478 )
479 assert response.status == 200
480 reflected = tests.HttpRequest.from_bytes(content)
481 assert reflected.headers.get('if-none-match')
482 assert reflected.headers.get('if-none-match') != 'fred'
483
484 response, content = http.request(
485 uri, 'GET',
486 headers={'accept-encoding': 'identity', 'cache-control': 'max-age=0', 'if-none-match': 'fred'},
487 )
488 assert response.status == 200
489 reflected = tests.HttpRequest.from_bytes(content)
490 assert reflected.headers.get('if-none-match') == 'fred'
491
492
493@pytest.mark.skip(reason='was commented in legacy code')
494def test_get_304_end_to_end():
495 pass
496 # Test that end to end headers get overwritten in the cache
497 # uri = urllib.parse.urljoin(base, "304/end2end.cgi")
498 # response, content = http.request(uri, 'GET')
499 # assertNotEqual(response['etag'], "")
500 # old_date = response['date']
501 # time.sleep(2)
502
503 # response, content = http.request(uri, 'GET', headers = {'Cache-Control': 'max-age=0'})
504 # # The response should be from the cache, but the Date: header should be updated.
505 # new_date = response['date']
506 # assert new_date != old_date
507 # assert response.status == 200
508 # assert response.fromcache == True
509
510
511def test_get_304_last_modified():
512 # Test that we can still handle a 304
513 # by only using the last-modified cache validator.
514 http = httplib2.Http(cache=tests.get_cache_path())
515 date = email.utils.formatdate()
516
517 def handler(read):
518 read()
519 yield tests.http_response_bytes(
520 status=200,
521 body=b'something',
522 headers={
523 'date': date,
524 'last-modified': date,
525 },
526 )
527
528 request2 = read()
529 assert request2.headers['if-modified-since'] == date
530 yield tests.http_response_bytes(status=304)
531
532 with tests.server_yield(handler, request_count=2) as uri:
533 response, content = http.request(uri, 'GET')
534 assert response.get('last-modified') == date
535
536 response, content = http.request(uri, 'GET')
537 assert response.status == 200
538 assert response.fromcache
539
540
541def test_get_307():
542 # Test that we do follow 307 redirects but
543 # do not cache the 307
544 http = httplib2.Http(cache=tests.get_cache_path(), timeout=1)
545 r307 = tests.http_response_bytes(
546 status=307,
547 headers={'location': '/final'},
548 )
549 r200 = tests.http_response_bytes(
550 status=200,
551 add_date=True,
552 body=b'final content\n',
553 headers={'cache-control': 'max-age=300'},
554 )
555
556 with tests.server_list_http([r307, r200, r307]) as uri:
557 response, content = http.request(uri, 'GET')
558 assert response.previous.status == 307
559 assert not response.previous.fromcache
560 assert response.status == 200
561 assert not response.fromcache
562 assert content == b'final content\n'
563
564 response, content = http.request(uri, 'GET')
565 assert response.previous.status == 307
566 assert not response.previous.fromcache
567 assert response.status == 200
568 assert response.fromcache
569 assert content == b'final content\n'
570
571
572def test_get_410():
573 # Test that we pass 410's through
574 http = httplib2.Http()
575 with tests.server_const_http(status=410) as uri:
576 response, content = http.request(uri, 'GET')
577 assert response.status == 410
578
579
580def test_get_duplicate_headers():
581 # Test that duplicate headers get concatenated via ','
582 http = httplib2.Http()
583 response = b'''HTTP/1.0 200 OK\r\n\
584Link: link1\r\n\
585Content-Length: 7\r\n\
586Link: link2\r\n\r\n\
587content'''
588 with tests.server_const_bytes(response) as uri:
589 response, content = http.request(uri, 'GET')
590 assert response.status == 200
591 assert content == b"content"
592 assert response['link'], 'link1, link2'