| import httplib2 | |
| import mock | |
| import os | |
| import pickle | |
| import pytest | |
| import socket | |
| import sys | |
| import tests | |
| import time | |
| from six.moves import urllib | |
| @pytest.mark.skipif( | |
| sys.version_info <= (3,), | |
| reason=( | |
| "TODO: httplib2._convert_byte_str was defined only in python3 code " "version" | |
| ), | |
| ) | |
| def test_convert_byte_str(): | |
| with tests.assert_raises(TypeError): | |
| httplib2._convert_byte_str(4) | |
| assert httplib2._convert_byte_str(b"Hello") == "Hello" | |
| assert httplib2._convert_byte_str("World") == "World" | |
| def test_reflect(): | |
| http = httplib2.Http() | |
| with tests.server_reflect() as uri: | |
| response, content = http.request(uri + "?query", "METHOD") | |
| assert response.status == 200 | |
| host = urllib.parse.urlparse(uri).netloc | |
| assert content.startswith( | |
| """\ | |
| METHOD /?query HTTP/1.1\r\n\ | |
| Host: {host}\r\n""".format( | |
| host=host | |
| ).encode() | |
| ), content | |
| def test_pickle_http(): | |
| http = httplib2.Http(cache=tests.get_cache_path()) | |
| new_http = pickle.loads(pickle.dumps(http)) | |
| assert tuple(sorted(new_http.__dict__)) == tuple(sorted(http.__dict__)) | |
| assert new_http.credentials.credentials == http.credentials.credentials | |
| assert new_http.certificates.credentials == http.certificates.credentials | |
| assert new_http.cache.cache == http.cache.cache | |
| for key in new_http.__dict__: | |
| if key not in ("cache", "certificates", "credentials"): | |
| assert getattr(new_http, key) == getattr(http, key) | |
| def test_pickle_http_with_connection(): | |
| http = httplib2.Http() | |
| http.request("http://random-domain:81/", connection_type=tests.MockHTTPConnection) | |
| new_http = pickle.loads(pickle.dumps(http)) | |
| assert tuple(http.connections) == ("http:random-domain:81",) | |
| assert new_http.connections == {} | |
| def test_pickle_custom_request_http(): | |
| http = httplib2.Http() | |
| http.request = lambda: None | |
| http.request.dummy_attr = "dummy_value" | |
| new_http = pickle.loads(pickle.dumps(http)) | |
| assert getattr(new_http.request, "dummy_attr", None) is None | |
| @pytest.mark.xfail( | |
| sys.version_info >= (3,), | |
| reason=( | |
| "FIXME: for unknown reason global timeout test fails in Python3 " | |
| "with response 200" | |
| ), | |
| ) | |
| def test_timeout_global(): | |
| def handler(request): | |
| time.sleep(0.5) | |
| return tests.http_response_bytes() | |
| try: | |
| socket.setdefaulttimeout(0.1) | |
| except Exception: | |
| pytest.skip("cannot set global socket timeout") | |
| try: | |
| http = httplib2.Http() | |
| http.force_exception_to_status_code = True | |
| with tests.server_request(handler) as uri: | |
| response, content = http.request(uri) | |
| assert response.status == 408 | |
| assert response.reason.startswith("Request Timeout") | |
| finally: | |
| socket.setdefaulttimeout(None) | |
| def test_timeout_individual(): | |
| def handler(request): | |
| time.sleep(0.5) | |
| return tests.http_response_bytes() | |
| http = httplib2.Http(timeout=0.1) | |
| http.force_exception_to_status_code = True | |
| with tests.server_request(handler) as uri: | |
| response, content = http.request(uri) | |
| assert response.status == 408 | |
| assert response.reason.startswith("Request Timeout") | |
| def test_timeout_subsequent(): | |
| class Handler(object): | |
| number = 0 | |
| @classmethod | |
| def handle(cls, request): | |
| # request.number is always 1 because of | |
| # the new socket connection each time | |
| cls.number += 1 | |
| if cls.number % 2 != 0: | |
| time.sleep(0.6) | |
| return tests.http_response_bytes(status=500) | |
| return tests.http_response_bytes(status=200) | |
| http = httplib2.Http(timeout=0.5) | |
| http.force_exception_to_status_code = True | |
| with tests.server_request(Handler.handle, request_count=2) as uri: | |
| response, _ = http.request(uri) | |
| assert response.status == 408 | |
| assert response.reason.startswith("Request Timeout") | |
| response, _ = http.request(uri) | |
| assert response.status == 200 | |
| def test_timeout_https(): | |
| c = httplib2.HTTPSConnectionWithTimeout("localhost", 80, timeout=47) | |
| assert 47 == c.timeout | |
| # @pytest.mark.xfail( | |
| # sys.version_info >= (3,), | |
| # reason='[py3] last request should open new connection, but client does not realize socket was closed by server', | |
| # ) | |
| def test_connection_close(): | |
| http = httplib2.Http() | |
| g = [] | |
| def handler(request): | |
| g.append(request.number) | |
| return tests.http_response_bytes(proto="HTTP/1.1") | |
| with tests.server_request(handler, request_count=3) as uri: | |
| http.request(uri, "GET") # conn1 req1 | |
| for c in http.connections.values(): | |
| assert c.sock is not None | |
| http.request(uri, "GET", headers={"connection": "close"}) | |
| time.sleep(0.7) | |
| http.request(uri, "GET") # conn2 req1 | |
| assert g == [1, 2, 1] | |
| def test_get_end2end_headers(): | |
| # one end to end header | |
| response = {"content-type": "application/atom+xml", "te": "deflate"} | |
| end2end = httplib2._get_end2end_headers(response) | |
| assert "content-type" in end2end | |
| assert "te" not in end2end | |
| assert "connection" not in end2end | |
| # one end to end header that gets eliminated | |
| response = { | |
| "connection": "content-type", | |
| "content-type": "application/atom+xml", | |
| "te": "deflate", | |
| } | |
| end2end = httplib2._get_end2end_headers(response) | |
| assert "content-type" not in end2end | |
| assert "te" not in end2end | |
| assert "connection" not in end2end | |
| # Degenerate case of no headers | |
| response = {} | |
| end2end = httplib2._get_end2end_headers(response) | |
| assert len(end2end) == 0 | |
| # Degenerate case of connection referrring to a header not passed in | |
| response = {"connection": "content-type"} | |
| end2end = httplib2._get_end2end_headers(response) | |
| assert len(end2end) == 0 | |
| @pytest.mark.xfail( | |
| os.environ.get("TRAVIS_PYTHON_VERSION") in ("2.7", "pypy"), | |
| reason="FIXME: fail on Travis py27 and pypy, works elsewhere", | |
| ) | |
| @pytest.mark.parametrize("scheme", ("http", "https")) | |
| def test_ipv6(scheme): | |
| # Even if IPv6 isn't installed on a machine it should just raise socket.error | |
| uri = "{scheme}://[::1]:1/".format(scheme=scheme) | |
| try: | |
| httplib2.Http(timeout=0.1).request(uri) | |
| except socket.gaierror: | |
| assert False, "should get the address family right for IPv6" | |
| except socket.error: | |
| pass | |
| @pytest.mark.parametrize( | |
| "conn_type", | |
| (httplib2.HTTPConnectionWithTimeout, httplib2.HTTPSConnectionWithTimeout), | |
| ) | |
| def test_connection_proxy_info_attribute_error(conn_type): | |
| # HTTPConnectionWithTimeout did not initialize its .proxy_info attribute | |
| # https://github.com/httplib2/httplib2/pull/97 | |
| # Thanks to Joseph Ryan https://github.com/germanjoey | |
| conn = conn_type("no-such-hostname.", 80) | |
| # TODO: replace mock with dummy local server | |
| with tests.assert_raises(socket.gaierror): | |
| with mock.patch("socket.socket.connect", side_effect=socket.gaierror): | |
| conn.request("GET", "/") | |
| def test_http_443_forced_https(): | |
| http = httplib2.Http() | |
| http.force_exception_to_status_code = True | |
| uri = "http://localhost:443/" | |
| # sorry, using internal structure of Http to check chosen scheme | |
| with mock.patch("httplib2.Http._request") as m: | |
| http.request(uri) | |
| assert len(m.call_args) > 0, "expected Http._request() call" | |
| conn = m.call_args[0][0] | |
| assert isinstance(conn, httplib2.HTTPConnectionWithTimeout) | |
| def test_close(): | |
| http = httplib2.Http() | |
| assert len(http.connections) == 0 | |
| with tests.server_const_http() as uri: | |
| http.request(uri) | |
| assert len(http.connections) == 1 | |
| http.close() | |
| assert len(http.connections) == 0 | |
| def test_connect_exception_type(): | |
| # This autoformatting PR actually changed the behavior of error handling: | |
| # https://github.com/httplib2/httplib2/pull/105/files#diff-c6669c781a2dee1b2d2671cab4e21c66L985 | |
| # potentially changing the type of the error raised by connect() | |
| # https://github.com/httplib2/httplib2/pull/150 | |
| http = httplib2.Http() | |
| with mock.patch("httplib2.socket.socket.connect", side_effect=socket.timeout("foo")): | |
| with tests.assert_raises(socket.timeout): | |
| http.request(tests.DUMMY_URL) |