| from __future__ import absolute_import | |
| from __future__ import division | |
| from __future__ import print_function | |
| import email.utils | |
| import errno | |
| import httplib2 | |
| import mock | |
| import os | |
| import pytest | |
| from six.moves import http_client, urllib | |
| import socket | |
| import tests | |
| DUMMY_URL = "http://127.0.0.1:1" | |
| def _raise_connection_refused_exception(*args, **kwargs): | |
| raise socket.error(errno.ECONNREFUSED, "Connection refused.") | |
| def test_connection_type(): | |
| http = httplib2.Http() | |
| http.force_exception_to_status_code = False | |
| response, content = http.request( | |
| DUMMY_URL, connection_type=tests.MockHTTPConnection | |
| ) | |
| assert response["content-location"] == DUMMY_URL | |
| assert content == b"the body" | |
| def test_bad_status_line_retry(): | |
| http = httplib2.Http() | |
| old_retries = httplib2.RETRIES | |
| httplib2.RETRIES = 1 | |
| http.force_exception_to_status_code = False | |
| try: | |
| response, content = http.request( | |
| DUMMY_URL, connection_type=tests.MockHTTPBadStatusConnection | |
| ) | |
| except http_client.BadStatusLine: | |
| assert tests.MockHTTPBadStatusConnection.num_calls == 2 | |
| httplib2.RETRIES = old_retries | |
| def test_unknown_server(): | |
| http = httplib2.Http() | |
| http.force_exception_to_status_code = False | |
| with tests.assert_raises(httplib2.ServerNotFoundError): | |
| with mock.patch("socket.socket.connect", side_effect=socket.gaierror): | |
| http.request("http://no-such-hostname./") | |
| # Now test with exceptions turned off | |
| http.force_exception_to_status_code = True | |
| response, content = http.request("http://no-such-hostname./") | |
| assert response["content-type"] == "text/plain" | |
| assert content.startswith(b"Unable to find") | |
| assert response.status == 400 | |
| @pytest.mark.skipif( | |
| os.environ.get("TRAVIS_PYTHON_VERSION") in ("2.7", "pypy"), | |
| reason="Fails on Travis py27/pypy, works elsewhere. " | |
| "See https://travis-ci.org/httplib2/httplib2/jobs/408769880.", | |
| ) | |
| @mock.patch("socket.socket.connect", spec=True) | |
| def test_connection_refused_raises_exception(mock_socket_connect): | |
| mock_socket_connect.side_effect = _raise_connection_refused_exception | |
| http = httplib2.Http() | |
| http.force_exception_to_status_code = False | |
| with tests.assert_raises(socket.error): | |
| http.request(DUMMY_URL) | |
| @pytest.mark.skipif( | |
| os.environ.get("TRAVIS_PYTHON_VERSION") in ("2.7", "pypy"), | |
| reason="Fails on Travis py27/pypy, works elsewhere. " | |
| "See https://travis-ci.org/httplib2/httplib2/jobs/408769880.", | |
| ) | |
| @mock.patch("socket.socket.connect", spec=True) | |
| def test_connection_refused_returns_response(mock_socket_connect): | |
| mock_socket_connect.side_effect = _raise_connection_refused_exception | |
| http = httplib2.Http() | |
| http.force_exception_to_status_code = True | |
| response, content = http.request(DUMMY_URL) | |
| content = content.lower() | |
| assert response["content-type"] == "text/plain" | |
| assert ( | |
| b"connection refused" in content | |
| or b"actively refused" in content | |
| or b"socket is not connected" in content | |
| ) | |
| assert response.status == 400 | |
| def test_get_iri(): | |
| http = httplib2.Http() | |
| query = u"?a=\N{CYRILLIC CAPITAL LETTER DJE}" | |
| with tests.server_reflect() as uri: | |
| response, content = http.request(uri + query, "GET") | |
| assert response.status == 200 | |
| reflected = tests.HttpRequest.from_bytes(content) | |
| assert reflected.uri == "/?a=%D0%82" | |
| def test_get_is_default_method(): | |
| # Test that GET is the default method | |
| http = httplib2.Http() | |
| with tests.server_reflect() as uri: | |
| response, content = http.request(uri) | |
| assert response.status == 200 | |
| reflected = tests.HttpRequest.from_bytes(content) | |
| assert reflected.method == "GET" | |
| def test_different_methods(): | |
| # Test that all methods can be used | |
| http = httplib2.Http() | |
| methods = ["GET", "PUT", "DELETE", "POST", "unknown"] | |
| with tests.server_reflect(request_count=len(methods)) as uri: | |
| for method in methods: | |
| response, content = http.request(uri, method, body=b" ") | |
| assert response.status == 200 | |
| reflected = tests.HttpRequest.from_bytes(content) | |
| assert reflected.method == method | |
| def test_head_read(): | |
| # Test that we don't try to read the response of a HEAD request | |
| # since httplib blocks response.read() for HEAD requests. | |
| http = httplib2.Http() | |
| respond_with = b"HTTP/1.0 200 OK\r\ncontent-length: " b"14\r\n\r\nnon-empty-body" | |
| with tests.server_const_bytes(respond_with) as uri: | |
| response, content = http.request(uri, "HEAD") | |
| assert response.status == 200 | |
| assert content == b"" | |
| def test_get_no_cache(): | |
| # Test that can do a GET w/o the cache turned on. | |
| http = httplib2.Http() | |
| with tests.server_const_http() as uri: | |
| response, content = http.request(uri, "GET") | |
| assert response.status == 200 | |
| assert response.previous is None | |
| def test_user_agent(): | |
| # Test that we provide a default user-agent | |
| http = httplib2.Http() | |
| with tests.server_reflect() as uri: | |
| response, content = http.request(uri, "GET") | |
| assert response.status == 200 | |
| reflected = tests.HttpRequest.from_bytes(content) | |
| assert reflected.headers.get("user-agent", "").startswith("Python-httplib2/") | |
| def test_user_agent_non_default(): | |
| # Test that the default user-agent can be over-ridden | |
| http = httplib2.Http() | |
| with tests.server_reflect() as uri: | |
| response, content = http.request(uri, "GET", headers={"User-Agent": "fred/1.0"}) | |
| assert response.status == 200 | |
| reflected = tests.HttpRequest.from_bytes(content) | |
| assert reflected.headers.get("user-agent") == "fred/1.0" | |
| def test_get_300_with_location(): | |
| # Test the we automatically follow 300 redirects if a Location: header is provided | |
| http = httplib2.Http() | |
| final_content = b"This is the final destination.\n" | |
| routes = { | |
| "/final": tests.http_response_bytes(body=final_content), | |
| "": tests.http_response_bytes( | |
| status="300 Multiple Choices", headers={"location": "/final"} | |
| ), | |
| } | |
| with tests.server_route(routes, request_count=2) as uri: | |
| response, content = http.request(uri, "GET") | |
| assert response.status == 200 | |
| assert content == final_content | |
| assert response.previous.status == 300 | |
| assert not response.previous.fromcache | |
| # Confirm that the intermediate 300 is not cached | |
| with tests.server_route(routes, request_count=2) as uri: | |
| response, content = http.request(uri, "GET") | |
| assert response.status == 200 | |
| assert content == final_content | |
| assert response.previous.status == 300 | |
| assert not response.previous.fromcache | |
| def test_get_300_with_location_noredirect(): | |
| # Test the we automatically follow 300 redirects if a Location: header is provided | |
| http = httplib2.Http() | |
| http.follow_redirects = False | |
| response = tests.http_response_bytes( | |
| status="300 Multiple Choices", | |
| headers={"location": "/final"}, | |
| body=b"redirect body", | |
| ) | |
| with tests.server_const_bytes(response) as uri: | |
| response, content = http.request(uri, "GET") | |
| assert response.status == 300 | |
| def test_get_300_without_location(): | |
| # Not giving a Location: header in a 300 response is acceptable | |
| # In which case we just return the 300 response | |
| http = httplib2.Http() | |
| with tests.server_const_http( | |
| status="300 Multiple Choices", body=b"redirect body" | |
| ) as uri: | |
| response, content = http.request(uri, "GET") | |
| assert response.status == 300 | |
| assert response.previous is None | |
| assert content == b"redirect body" | |
| def test_get_301(): | |
| # Test that we automatically follow 301 redirects | |
| # and that we cache the 301 response | |
| http = httplib2.Http(cache=tests.get_cache_path()) | |
| destination = "" | |
| routes = { | |
| "/final": tests.http_response_bytes(body=b"This is the final destination.\n"), | |
| "": tests.http_response_bytes( | |
| status="301 Now where did I leave that URL", | |
| headers={"location": "/final"}, | |
| body=b"redirect body", | |
| ), | |
| } | |
| with tests.server_route(routes, request_count=3) as uri: | |
| destination = urllib.parse.urljoin(uri, "/final") | |
| response1, content1 = http.request(uri, "GET") | |
| response2, content2 = http.request(uri, "GET") | |
| assert response1.status == 200 | |
| assert "content-location" in response2 | |
| assert response1["content-location"] == destination | |
| assert content1 == b"This is the final destination.\n" | |
| assert response1.previous.status == 301 | |
| assert not response1.previous.fromcache | |
| assert response2.status == 200 | |
| assert response2["content-location"] == destination | |
| assert content2 == b"This is the final destination.\n" | |
| assert response2.previous.status == 301 | |
| assert response2.previous.fromcache | |
| @pytest.mark.skip( | |
| not os.environ.get("httplib2_test_still_run_skipped") | |
| and os.environ.get("TRAVIS_PYTHON_VERSION") in ("2.7", "pypy"), | |
| reason="FIXME: timeout on Travis py27 and pypy, works elsewhere", | |
| ) | |
| def test_head_301(): | |
| # Test that we automatically follow 301 redirects | |
| http = httplib2.Http() | |
| destination = "" | |
| routes = { | |
| "/final": tests.http_response_bytes(body=b"This is the final destination.\n"), | |
| "": tests.http_response_bytes( | |
| status="301 Now where did I leave that URL", | |
| headers={"location": "/final"}, | |
| body=b"redirect body", | |
| ), | |
| } | |
| with tests.server_route(routes, request_count=2) as uri: | |
| destination = urllib.parse.urljoin(uri, "/final") | |
| response, content = http.request(uri, "HEAD") | |
| assert response.status == 200 | |
| assert response["content-location"] == destination | |
| assert response.previous.status == 301 | |
| assert not response.previous.fromcache | |
| @pytest.mark.xfail( | |
| reason=( | |
| "FIXME: 301 cache works only with follow_redirects, should work " "regardless" | |
| ) | |
| ) | |
| def test_get_301_no_redirect(): | |
| # Test that we cache the 301 response | |
| http = httplib2.Http(cache=tests.get_cache_path(), timeout=0.5) | |
| http.follow_redirects = False | |
| response = tests.http_response_bytes( | |
| status="301 Now where did I leave that URL", | |
| headers={"location": "/final", "cache-control": "max-age=300"}, | |
| body=b"redirect body", | |
| add_date=True, | |
| ) | |
| with tests.server_const_bytes(response) as uri: | |
| response, _ = http.request(uri, "GET") | |
| assert response.status == 301 | |
| assert not response.fromcache | |
| response, _ = http.request(uri, "GET") | |
| assert response.status == 301 | |
| assert response.fromcache | |
| def test_get_302(): | |
| # Test that we automatically follow 302 redirects | |
| # and that we DO NOT cache the 302 response | |
| http = httplib2.Http(cache=tests.get_cache_path()) | |
| second_url, final_url = "", "" | |
| routes = { | |
| "/final": tests.http_response_bytes(body=b"This is the final destination.\n"), | |
| "/second": tests.http_response_bytes( | |
| status="302 Found", headers={"location": "/final"}, body=b"second redirect" | |
| ), | |
| "": tests.http_response_bytes( | |
| status="302 Found", headers={"location": "/second"}, body=b"redirect body" | |
| ), | |
| } | |
| with tests.server_route(routes, request_count=7) as uri: | |
| second_url = urllib.parse.urljoin(uri, "/second") | |
| final_url = urllib.parse.urljoin(uri, "/final") | |
| response1, content1 = http.request(second_url, "GET") | |
| response2, content2 = http.request(second_url, "GET") | |
| response3, content3 = http.request(uri, "GET") | |
| assert response1.status == 200 | |
| assert response1["content-location"] == final_url | |
| assert content1 == b"This is the final destination.\n" | |
| assert response1.previous.status == 302 | |
| assert not response1.previous.fromcache | |
| assert response2.status == 200 | |
| # FIXME: | |
| # assert response2.fromcache | |
| assert response2["content-location"] == final_url | |
| assert content2 == b"This is the final destination.\n" | |
| assert response2.previous.status == 302 | |
| assert not response2.previous.fromcache | |
| assert response2.previous["content-location"] == second_url | |
| assert response3.status == 200 | |
| # FIXME: | |
| # assert response3.fromcache | |
| assert content3 == b"This is the final destination.\n" | |
| assert response3.previous.status == 302 | |
| assert not response3.previous.fromcache | |
| def test_get_302_redirection_limit(): | |
| # Test that we can set a lower redirection limit | |
| # and that we raise an exception when we exceed | |
| # that limit. | |
| http = httplib2.Http() | |
| http.force_exception_to_status_code = False | |
| routes = { | |
| "/second": tests.http_response_bytes( | |
| status="302 Found", headers={"location": "/final"}, body=b"second redirect" | |
| ), | |
| "": tests.http_response_bytes( | |
| status="302 Found", headers={"location": "/second"}, body=b"redirect body" | |
| ), | |
| } | |
| with tests.server_route(routes, request_count=4) as uri: | |
| try: | |
| http.request(uri, "GET", redirections=1) | |
| assert False, "This should not happen" | |
| except httplib2.RedirectLimit: | |
| pass | |
| except Exception: | |
| assert False, "Threw wrong kind of exception " | |
| # Re-run the test with out the exceptions | |
| http.force_exception_to_status_code = True | |
| response, content = http.request(uri, "GET", redirections=1) | |
| assert response.status == 500 | |
| assert response.reason.startswith("Redirected more") | |
| assert response["status"] == "302" | |
| assert content == b"second redirect" | |
| assert response.previous is not None | |
| def test_get_302_no_location(): | |
| # Test that we throw an exception when we get | |
| # a 302 with no Location: header. | |
| http = httplib2.Http() | |
| http.force_exception_to_status_code = False | |
| with tests.server_const_http(status="302 Found", request_count=2) as uri: | |
| try: | |
| http.request(uri, "GET") | |
| assert False, "Should never reach here" | |
| except httplib2.RedirectMissingLocation: | |
| pass | |
| except Exception: | |
| assert False, "Threw wrong kind of exception " | |
| # Re-run the test with out the exceptions | |
| http.force_exception_to_status_code = True | |
| response, content = http.request(uri, "GET") | |
| assert response.status == 500 | |
| assert response.reason.startswith("Redirected but") | |
| assert "302" == response["status"] | |
| assert content == b"" | |
| @pytest.mark.skip( | |
| not os.environ.get("httplib2_test_still_run_skipped") | |
| and os.environ.get("TRAVIS_PYTHON_VERSION") in ("2.7", "pypy"), | |
| reason="FIXME: timeout on Travis py27 and pypy, works elsewhere", | |
| ) | |
| def test_303(): | |
| # Do a follow-up GET on a Location: header | |
| # returned from a POST that gave a 303. | |
| http = httplib2.Http() | |
| routes = { | |
| "/final": tests.make_http_reflect(), | |
| "": tests.make_http_reflect( | |
| status="303 See Other", headers={"location": "/final"} | |
| ), | |
| } | |
| with tests.server_route(routes, request_count=2) as uri: | |
| response, content = http.request(uri, "POST", " ") | |
| assert response.status == 200 | |
| reflected = tests.HttpRequest.from_bytes(content) | |
| assert reflected.uri == "/final" | |
| assert response.previous.status == 303 | |
| # Skip follow-up GET | |
| http = httplib2.Http() | |
| http.follow_redirects = False | |
| with tests.server_route(routes, request_count=1) as uri: | |
| response, content = http.request(uri, "POST", " ") | |
| assert response.status == 303 | |
| # All methods can be used | |
| http = httplib2.Http() | |
| cases = "DELETE GET HEAD POST PUT EVEN_NEW_ONES".split(" ") | |
| with tests.server_route(routes, request_count=len(cases) * 2) as uri: | |
| for method in cases: | |
| response, content = http.request(uri, method, body=b"q q") | |
| assert response.status == 200 | |
| reflected = tests.HttpRequest.from_bytes(content) | |
| assert reflected.method == "GET" | |
| def test_etag_used(): | |
| # Test that we use ETags properly to validate our cache | |
| cache_path = tests.get_cache_path() | |
| http = httplib2.Http(cache=cache_path) | |
| response_kwargs = dict( | |
| add_date=True, | |
| add_etag=True, | |
| body=b"something", | |
| headers={"cache-control": "public,max-age=300"}, | |
| ) | |
| def handler(request): | |
| if request.headers.get("range"): | |
| return tests.http_response_bytes(status=206, **response_kwargs) | |
| return tests.http_response_bytes(**response_kwargs) | |
| with tests.server_request(handler, request_count=2) as uri: | |
| response, _ = http.request(uri, "GET", headers={"accept-encoding": "identity"}) | |
| assert response["etag"] == '"437b930db84b8079c2dd804a71936b5f"' | |
| http.request(uri, "GET", headers={"accept-encoding": "identity"}) | |
| response, _ = http.request( | |
| uri, | |
| "GET", | |
| headers={"accept-encoding": "identity", "cache-control": "must-revalidate"}, | |
| ) | |
| assert response.status == 200 | |
| assert response.fromcache | |
| # TODO: API to read cache item, at least internal to tests | |
| cache_file_name = os.path.join( | |
| cache_path, httplib2.safename(httplib2.urlnorm(uri)[-1]) | |
| ) | |
| with open(cache_file_name, "r") as f: | |
| status_line = f.readline() | |
| assert status_line.startswith("status:") | |
| response, content = http.request( | |
| uri, "HEAD", headers={"accept-encoding": "identity"} | |
| ) | |
| assert response.status == 200 | |
| assert response.fromcache | |
| response, content = http.request( | |
| uri, "GET", headers={"accept-encoding": "identity", "range": "bytes=0-0"} | |
| ) | |
| assert response.status == 206 | |
| assert not response.fromcache | |
| def test_etag_ignore(): | |
| # Test that we can forcibly ignore ETags | |
| http = httplib2.Http(cache=tests.get_cache_path()) | |
| response_kwargs = dict(add_date=True, add_etag=True) | |
| with tests.server_reflect(request_count=3, **response_kwargs) as uri: | |
| response, content = http.request( | |
| uri, "GET", headers={"accept-encoding": "identity"} | |
| ) | |
| assert response.status == 200 | |
| assert response["etag"] != "" | |
| response, content = http.request( | |
| uri, | |
| "GET", | |
| headers={"accept-encoding": "identity", "cache-control": "max-age=0"}, | |
| ) | |
| reflected = tests.HttpRequest.from_bytes(content) | |
| assert reflected.headers.get("if-none-match") | |
| http.ignore_etag = True | |
| response, content = http.request( | |
| uri, | |
| "GET", | |
| headers={"accept-encoding": "identity", "cache-control": "max-age=0"}, | |
| ) | |
| assert not response.fromcache | |
| reflected = tests.HttpRequest.from_bytes(content) | |
| assert not reflected.headers.get("if-none-match") | |
| def test_etag_override(): | |
| # Test that we can forcibly ignore ETags | |
| http = httplib2.Http(cache=tests.get_cache_path()) | |
| response_kwargs = dict(add_date=True, add_etag=True) | |
| with tests.server_reflect(request_count=3, **response_kwargs) as uri: | |
| response, _ = http.request(uri, "GET", headers={"accept-encoding": "identity"}) | |
| assert response.status == 200 | |
| assert response["etag"] != "" | |
| response, content = http.request( | |
| uri, | |
| "GET", | |
| headers={"accept-encoding": "identity", "cache-control": "max-age=0"}, | |
| ) | |
| assert response.status == 200 | |
| reflected = tests.HttpRequest.from_bytes(content) | |
| assert reflected.headers.get("if-none-match") | |
| assert reflected.headers.get("if-none-match") != "fred" | |
| response, content = http.request( | |
| uri, | |
| "GET", | |
| headers={ | |
| "accept-encoding": "identity", | |
| "cache-control": "max-age=0", | |
| "if-none-match": "fred", | |
| }, | |
| ) | |
| assert response.status == 200 | |
| reflected = tests.HttpRequest.from_bytes(content) | |
| assert reflected.headers.get("if-none-match") == "fred" | |
| @pytest.mark.skip(reason="was commented in legacy code") | |
| def test_get_304_end_to_end(): | |
| pass | |
| # Test that end to end headers get overwritten in the cache | |
| # uri = urllib.parse.urljoin(base, "304/end2end.cgi") | |
| # response, content = http.request(uri, 'GET') | |
| # assertNotEqual(response['etag'], "") | |
| # old_date = response['date'] | |
| # time.sleep(2) | |
| # response, content = http.request(uri, 'GET', headers = {'Cache-Control': 'max-age=0'}) | |
| # # The response should be from the cache, but the Date: header should be updated. | |
| # new_date = response['date'] | |
| # assert new_date != old_date | |
| # assert response.status == 200 | |
| # assert response.fromcache == True | |
| def test_get_304_last_modified(): | |
| # Test that we can still handle a 304 | |
| # by only using the last-modified cache validator. | |
| http = httplib2.Http(cache=tests.get_cache_path()) | |
| date = email.utils.formatdate() | |
| def handler(read): | |
| read() | |
| yield tests.http_response_bytes( | |
| status=200, body=b"something", headers={"date": date, "last-modified": date} | |
| ) | |
| request2 = read() | |
| assert request2.headers["if-modified-since"] == date | |
| yield tests.http_response_bytes(status=304) | |
| with tests.server_yield(handler, request_count=2) as uri: | |
| response, content = http.request(uri, "GET") | |
| assert response.get("last-modified") == date | |
| response, content = http.request(uri, "GET") | |
| assert response.status == 200 | |
| assert response.fromcache | |
| def test_get_307(): | |
| # Test that we do follow 307 redirects but | |
| # do not cache the 307 | |
| http = httplib2.Http(cache=tests.get_cache_path(), timeout=1) | |
| r307 = tests.http_response_bytes(status=307, headers={"location": "/final"}) | |
| r200 = tests.http_response_bytes( | |
| status=200, | |
| add_date=True, | |
| body=b"final content\n", | |
| headers={"cache-control": "max-age=300"}, | |
| ) | |
| with tests.server_list_http([r307, r200, r307]) as uri: | |
| response, content = http.request(uri, "GET") | |
| assert response.previous.status == 307 | |
| assert not response.previous.fromcache | |
| assert response.status == 200 | |
| assert not response.fromcache | |
| assert content == b"final content\n" | |
| response, content = http.request(uri, "GET") | |
| assert response.previous.status == 307 | |
| assert not response.previous.fromcache | |
| assert response.status == 200 | |
| assert response.fromcache | |
| assert content == b"final content\n" | |
| def test_get_410(): | |
| # Test that we pass 410's through | |
| http = httplib2.Http() | |
| with tests.server_const_http(status=410) as uri: | |
| response, content = http.request(uri, "GET") | |
| assert response.status == 410 | |
| def test_get_duplicate_headers(): | |
| # Test that duplicate headers get concatenated via ',' | |
| http = httplib2.Http() | |
| response = b"""HTTP/1.0 200 OK\r\n\ | |
| Link: link1\r\n\ | |
| Content-Length: 7\r\n\ | |
| Link: link2\r\n\r\n\ | |
| content""" | |
| with tests.server_const_bytes(response) as uri: | |
| response, content = http.request(uri, "GET") | |
| assert response.status == 200 | |
| assert content == b"content" | |
| assert response["link"], "link1, link2" |