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