From 97b74687b213da160d03008ebd8f79ebbf656506 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 2 Dec 2019 11:42:59 +0000 Subject: [PATCH 1/7] Simplify HTTP version config, and switch HTTP/2 off by default --- httpx/__init__.py | 4 -- httpx/api.py | 6 +-- httpx/client.py | 17 +++---- httpx/config.py | 82 +++++-------------------------- httpx/dispatch/connection.py | 10 ++-- httpx/dispatch/connection_pool.py | 7 ++- httpx/dispatch/proxy_http.py | 7 ++- tests/client/test_proxies.py | 2 - tests/dispatch/test_http2.py | 14 +++--- tests/test_concurrency.py | 6 +-- tests/test_config.py | 40 --------------- tests/test_multipart.py | 2 - 12 files changed, 39 insertions(+), 158 deletions(-) diff --git a/httpx/__init__.py b/httpx/__init__.py index bda76ae309..0910545bdf 100644 --- a/httpx/__init__.py +++ b/httpx/__init__.py @@ -12,8 +12,6 @@ from .config import ( USER_AGENT, CertTypes, - HTTPVersionConfig, - HTTPVersionTypes, PoolLimits, SSLConfig, TimeoutConfig, @@ -118,8 +116,6 @@ "StatusCode", "codes", "TimeoutTypes", - "HTTPVersionTypes", - "HTTPVersionConfig", "AuthTypes", "Cookies", "CookieTypes", diff --git a/httpx/api.py b/httpx/api.py index 2ea1be62bb..893c3e73cb 100644 --- a/httpx/api.py +++ b/httpx/api.py @@ -81,11 +81,7 @@ async def request( ``` """ async with Client( - http_versions=["HTTP/1.1"], - cert=cert, - verify=verify, - timeout=timeout, - trust_env=trust_env, + cert=cert, verify=verify, timeout=timeout, trust_env=trust_env, ) as client: return await client.request( method=method, diff --git a/httpx/client.py b/httpx/client.py index 1fe4f6c014..28aa1767ec 100644 --- a/httpx/client.py +++ b/httpx/client.py @@ -12,7 +12,6 @@ DEFAULT_POOL_LIMITS, DEFAULT_TIMEOUT_CONFIG, CertTypes, - HTTPVersionTypes, PoolLimits, TimeoutTypes, VerifyTypes, @@ -79,8 +78,8 @@ class Client: to authenticate the client. Either a path to an SSL certificate file, or two-tuple of (certificate file, key file), or a three-tuple of (certificate file, key file, password). - * **http_versions** - *(optional)* A list of strings of HTTP protocol - versions to use when sending requests. eg. `http_versions=["HTTP/1.1"]` + * **http_2** - *(optional)* A boolean indicating if HTTP/2 support should be + enabled. Defaults to `False`. * **proxies** - *(optional)* A dictionary mapping HTTP protocols to proxy URLs. * **timeout** - *(optional)* The timeout configuration to use when sending @@ -111,7 +110,7 @@ def __init__( cookies: CookieTypes = None, verify: VerifyTypes = True, cert: CertTypes = None, - http_versions: HTTPVersionTypes = None, + http_2: bool = False, proxies: ProxiesTypes = None, timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, pool_limits: PoolLimits = DEFAULT_POOL_LIMITS, @@ -134,7 +133,7 @@ def __init__( verify=verify, cert=cert, timeout=timeout, - http_versions=http_versions, + http_2=http_2, pool_limits=pool_limits, backend=backend, trust_env=trust_env, @@ -167,7 +166,7 @@ def __init__( verify=verify, cert=cert, timeout=timeout, - http_versions=http_versions, + http_2=http_2, pool_limits=pool_limits, backend=backend, trust_env=trust_env, @@ -833,13 +832,13 @@ def _proxies_to_dispatchers( verify: VerifyTypes, cert: typing.Optional[CertTypes], timeout: TimeoutTypes, - http_versions: typing.Optional[HTTPVersionTypes], + http_2: bool, pool_limits: PoolLimits, backend: ConcurrencyBackend, trust_env: bool, ) -> typing.Dict[str, Dispatcher]: def _proxy_from_url(url: URLTypes) -> Dispatcher: - nonlocal verify, cert, timeout, http_versions, pool_limits, backend, trust_env + nonlocal verify, cert, timeout, http_2, pool_limits, backend, trust_env url = URL(url) if url.scheme in ("http", "https"): return HTTPProxy( @@ -850,7 +849,7 @@ def _proxy_from_url(url: URLTypes) -> Dispatcher: pool_limits=pool_limits, backend=backend, trust_env=trust_env, - http_versions=http_versions, + http_2=http_2, ) raise ValueError(f"Unknown proxy for {url!r}") diff --git a/httpx/config.py b/httpx/config.py index a32ae84df9..cace3c20dd 100644 --- a/httpx/config.py +++ b/httpx/config.py @@ -13,15 +13,10 @@ TimeoutTypes = typing.Union[ float, typing.Tuple[float, float, float, float], "TimeoutConfig" ] -HTTPVersionTypes = typing.Union[ - str, typing.List[str], typing.Tuple[str], "HTTPVersionConfig" -] USER_AGENT = f"python-httpx/{__version__}" -HTTP_VERSIONS_TO_ALPN_IDENTIFIERS = {"HTTP/1.1": "http/1.1", "HTTP/2": "h2"} - DEFAULT_CIPHERS = ":".join( [ "ECDHE+AESGCM", @@ -91,43 +86,35 @@ def with_overrides( return self return SSLConfig(cert=cert, verify=verify) - def load_ssl_context( - self, http_versions: "HTTPVersionConfig" = None - ) -> ssl.SSLContext: - http_versions = HTTPVersionConfig() if http_versions is None else http_versions - + def load_ssl_context(self, http_2: bool = False) -> ssl.SSLContext: logger.trace( f"load_ssl_context " f"verify={self.verify!r} " f"cert={self.cert!r} " f"trust_env={self.trust_env!r} " - f"http_versions={http_versions!r}" + f"http_2={http_2!r}" ) if self.ssl_context is None: self.ssl_context = ( - self.load_ssl_context_verify(http_versions=http_versions) + self.load_ssl_context_verify(http_2=http_2) if self.verify - else self.load_ssl_context_no_verify(http_versions=http_versions) + else self.load_ssl_context_no_verify(http_2=http_2) ) assert self.ssl_context is not None return self.ssl_context - def load_ssl_context_no_verify( - self, http_versions: "HTTPVersionConfig" - ) -> ssl.SSLContext: + def load_ssl_context_no_verify(self, http_2: bool = False) -> ssl.SSLContext: """ Return an SSL context for unverified connections. """ - context = self._create_default_ssl_context(http_versions=http_versions) + context = self._create_default_ssl_context(http_2=http_2) context.verify_mode = ssl.CERT_NONE context.check_hostname = False return context - def load_ssl_context_verify( - self, http_versions: "HTTPVersionConfig" - ) -> ssl.SSLContext: + def load_ssl_context_verify(self, http_2: bool = False) -> ssl.SSLContext: """ Return an SSL context for verified connections. """ @@ -146,7 +133,7 @@ def load_ssl_context_verify( "invalid path: {}".format(self.verify) ) - context = self._create_default_ssl_context(http_versions=http_versions) + context = self._create_default_ssl_context(http_2=http_2) context.verify_mode = ssl.CERT_REQUIRED context.check_hostname = True @@ -175,9 +162,7 @@ def load_ssl_context_verify( return context - def _create_default_ssl_context( - self, http_versions: "HTTPVersionConfig" - ) -> ssl.SSLContext: + def _create_default_ssl_context(self, http_2: bool) -> ssl.SSLContext: """ Creates the default SSLContext object that's used for both verified and unverified connections. @@ -191,7 +176,8 @@ def _create_default_ssl_context( context.set_ciphers(DEFAULT_CIPHERS) if ssl.HAS_ALPN: - context.set_alpn_protocols(http_versions.alpn_identifiers) + alpn_idents = ["http/1.1", "h2"] if http_2 else ["http/1.1"] + context.set_alpn_protocols(alpn_idents) if hasattr(context, "keylog_filename"): keylogfile = os.environ.get("SSLKEYLOGFILE") @@ -288,52 +274,6 @@ def __repr__(self) -> str: ) -class HTTPVersionConfig: - """ - Configure which HTTP protocol versions are supported. - """ - - def __init__(self, http_versions: HTTPVersionTypes = None): - if http_versions is None: - http_versions = ["HTTP/1.1", "HTTP/2"] - - if isinstance(http_versions, str): - self.http_versions = {http_versions.upper()} - elif isinstance(http_versions, HTTPVersionConfig): - self.http_versions = http_versions.http_versions - elif isinstance(http_versions, typing.Iterable): - self.http_versions = { - version.upper() if isinstance(version, str) else version - for version in http_versions - } - else: - raise TypeError( - "HTTP version should be a string or list of strings, " - f"but got {type(http_versions)}" - ) - - for version in self.http_versions: - if version not in ("HTTP/1.1", "HTTP/2"): - raise ValueError(f"Unsupported HTTP version {version!r}.") - - if not self.http_versions: - raise ValueError("HTTP versions cannot be an empty list.") - - @property - def alpn_identifiers(self) -> typing.List[str]: - """ - Returns a list of supported ALPN identifiers. (One or more of "http/1.1", "h2"). - """ - return [ - HTTP_VERSIONS_TO_ALPN_IDENTIFIERS[version] for version in self.http_versions - ] - - def __repr__(self) -> str: - class_name = self.__class__.__name__ - value = sorted(list(self.http_versions)) - return f"{class_name}({value!r})" - - class PoolLimits: """ Limits on the number of connections in a connection pool. diff --git a/httpx/dispatch/connection.py b/httpx/dispatch/connection.py index 6694caa2de..0f05b2e8b2 100644 --- a/httpx/dispatch/connection.py +++ b/httpx/dispatch/connection.py @@ -7,8 +7,6 @@ from ..config import ( DEFAULT_TIMEOUT_CONFIG, CertTypes, - HTTPVersionConfig, - HTTPVersionTypes, SSLConfig, TimeoutConfig, TimeoutTypes, @@ -35,7 +33,7 @@ def __init__( cert: CertTypes = None, trust_env: bool = None, timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, - http_versions: HTTPVersionTypes = None, + http_2: bool = False, backend: ConcurrencyBackend = None, release_func: typing.Optional[ReleaseCallback] = None, uds: typing.Optional[str] = None, @@ -43,7 +41,7 @@ def __init__( self.origin = Origin(origin) if isinstance(origin, str) else origin self.ssl = SSLConfig(cert=cert, verify=verify, trust_env=trust_env) self.timeout = TimeoutConfig(timeout) - self.http_versions = HTTPVersionConfig(http_versions) + self.http_2 = http_2 self.backend = AsyncioBackend() if backend is None else backend self.release_func = release_func self.uds = uds @@ -119,9 +117,7 @@ async def get_ssl_context(self, ssl: SSLConfig) -> typing.Optional[ssl.SSLContex return None # Run the SSL loading in a threadpool, since it may make disk accesses. - return await self.backend.run_in_threadpool( - ssl.load_ssl_context, self.http_versions - ) + return await self.backend.run_in_threadpool(ssl.load_ssl_context, self.http_2) async def close(self) -> None: logger.trace("close_connection") diff --git a/httpx/dispatch/connection_pool.py b/httpx/dispatch/connection_pool.py index 32173301b7..fc44c41301 100644 --- a/httpx/dispatch/connection_pool.py +++ b/httpx/dispatch/connection_pool.py @@ -6,7 +6,6 @@ DEFAULT_POOL_LIMITS, DEFAULT_TIMEOUT_CONFIG, CertTypes, - HTTPVersionTypes, PoolLimits, TimeoutConfig, TimeoutTypes, @@ -88,7 +87,7 @@ def __init__( trust_env: bool = None, timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, pool_limits: PoolLimits = DEFAULT_POOL_LIMITS, - http_versions: HTTPVersionTypes = None, + http_2: bool = False, backend: ConcurrencyBackend = None, uds: typing.Optional[str] = None, ): @@ -96,7 +95,7 @@ def __init__( self.cert = cert self.timeout = TimeoutConfig(timeout) self.pool_limits = pool_limits - self.http_versions = http_versions + self.http_2 = http_2 self.is_closed = False self.trust_env = trust_env self.uds = uds @@ -150,7 +149,7 @@ async def acquire_connection( verify=self.verify, cert=self.cert, timeout=self.timeout, - http_versions=self.http_versions, + http_2=self.http_2, backend=self.backend, release_func=self.release_connection, trust_env=self.trust_env, diff --git a/httpx/dispatch/proxy_http.py b/httpx/dispatch/proxy_http.py index 13eb42333e..c51c009e12 100644 --- a/httpx/dispatch/proxy_http.py +++ b/httpx/dispatch/proxy_http.py @@ -8,7 +8,6 @@ DEFAULT_POOL_LIMITS, DEFAULT_TIMEOUT_CONFIG, CertTypes, - HTTPVersionTypes, PoolLimits, SSLConfig, TimeoutTypes, @@ -47,7 +46,7 @@ def __init__( trust_env: bool = None, timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, pool_limits: PoolLimits = DEFAULT_POOL_LIMITS, - http_versions: HTTPVersionTypes = None, + http_2: bool = False, backend: ConcurrencyBackend = None, ): @@ -58,7 +57,7 @@ def __init__( pool_limits=pool_limits, backend=backend, trust_env=trust_env, - http_versions=http_versions, + http_2=http_2, ) self.proxy_url = URL(proxy_url) @@ -134,7 +133,7 @@ async def request_tunnel_proxy_connection(self, origin: Origin) -> HTTPConnectio cert=self.cert, timeout=self.timeout, backend=self.backend, - http_versions=["HTTP/1.1"], # Short-lived 'connection' + http_2=False, # Short-lived 'connection' trust_env=self.trust_env, release_func=self.release_connection, ) diff --git a/tests/client/test_proxies.py b/tests/client/test_proxies.py index fc1e57a5e8..ede15a8fce 100644 --- a/tests/client/test_proxies.py +++ b/tests/client/test_proxies.py @@ -36,7 +36,6 @@ def test_proxies_has_same_properties_as_dispatch(): cert="/path/to/cert", trust_env=False, timeout=30, - http_versions=["HTTP/1.1"], ) pool = client.dispatch proxy = client.proxies["all"] @@ -49,7 +48,6 @@ def test_proxies_has_same_properties_as_dispatch(): "cert", "timeout", "pool_limits", - "http_versions", "backend", ]: assert getattr(pool, prop) == getattr(proxy, prop) diff --git a/tests/dispatch/test_http2.py b/tests/dispatch/test_http2.py index 0c8c9177be..86dcc8ff26 100644 --- a/tests/dispatch/test_http2.py +++ b/tests/dispatch/test_http2.py @@ -27,7 +27,7 @@ def app(request): async def test_http2_get_request(): backend = MockHTTP2Backend(app=app) - async with Client(backend=backend) as client: + async with Client(backend=backend, http_2=True) as client: response = await client.get("http://example.org") assert response.status_code == 200 @@ -38,7 +38,7 @@ async def test_http2_get_request(): async def test_http2_post_request(): backend = MockHTTP2Backend(app=app) - async with Client(backend=backend) as client: + async with Client(backend=backend, http_2=True) as client: response = await client.post("http://example.org", data=b"") assert response.status_code == 200 @@ -54,7 +54,7 @@ async def test_http2_large_post_request(): backend = MockHTTP2Backend(app=app) data = b"a" * 100000 - async with Client(backend=backend) as client: + async with Client(backend=backend, http_2=True) as client: response = await client.post("http://example.org", data=data) assert response.status_code == 200 assert json.loads(response.content) == { @@ -68,7 +68,7 @@ async def test_http2_large_post_request(): async def test_http2_multiple_requests(): backend = MockHTTP2Backend(app=app) - async with Client(backend=backend) as client: + async with Client(backend=backend, http_2=True) as client: response_1 = await client.get("http://example.org/1") response_2 = await client.get("http://example.org/2") response_3 = await client.get("http://example.org/3") @@ -91,7 +91,7 @@ async def test_http2_reconnect(): """ backend = MockHTTP2Backend(app=app) - async with Client(backend=backend) as client: + async with Client(backend=backend, http_2=True) as client: response_1 = await client.get("http://example.org/1") backend.server.close_connection = True response_2 = await client.get("http://example.org/2") @@ -106,7 +106,7 @@ async def test_http2_reconnect(): async def test_http2_settings_in_handshake(backend): backend = MockHTTP2Backend(app=app, backend=backend) - async with Client(backend=backend) as client: + async with Client(backend=backend, http_2=True) as client: await client.get("http://example.org") h2_conn = backend.server.conn @@ -139,7 +139,7 @@ async def test_http2_settings_in_handshake(backend): async def test_http2_live_request(backend): - async with Client(backend=backend) as client: + async with Client(backend=backend, http_2=True) as client: try: resp = await client.get("https://nghttp2.org/httpbin/anything") except Timeout: diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py index da43987f0c..52828dacff 100644 --- a/tests/test_concurrency.py +++ b/tests/test_concurrency.py @@ -1,7 +1,7 @@ import pytest import trio -from httpx import AsyncioBackend, HTTPVersionConfig, SSLConfig, TimeoutConfig +from httpx import AsyncioBackend, SSLConfig, TimeoutConfig from httpx.concurrency.trio import TrioBackend from tests.concurrency import run_concurrently @@ -40,7 +40,7 @@ async def read_response(stream, timeout: float, should_contain: bytes) -> bytes: ], ) async def test_start_tls_on_tcp_socket_stream(https_server, backend, get_cipher): - ctx = SSLConfig().load_ssl_context_no_verify(HTTPVersionConfig()) + ctx = SSLConfig().load_ssl_context_no_verify() timeout = TimeoutConfig(5) stream = await backend.open_tcp_stream( @@ -72,7 +72,7 @@ async def test_start_tls_on_tcp_socket_stream(https_server, backend, get_cipher) ], ) async def test_start_tls_on_uds_socket_stream(https_uds_server, backend, get_cipher): - ctx = SSLConfig().load_ssl_context_no_verify(HTTPVersionConfig()) + ctx = SSLConfig().load_ssl_context_no_verify() timeout = TimeoutConfig(5) stream = await backend.open_uds_stream( diff --git a/tests/test_config.py b/tests/test_config.py index d8d429d811..250c37bab9 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -118,46 +118,6 @@ def test_ssl_repr(): assert repr(ssl) == "SSLConfig(cert=None, verify=False)" -def test_http_versions_repr(): - http_versions = httpx.HTTPVersionConfig() - assert repr(http_versions) == "HTTPVersionConfig(['HTTP/1.1', 'HTTP/2'])" - - -def test_http_versions_from_string(): - http_versions = httpx.HTTPVersionConfig("HTTP/1.1") - assert repr(http_versions) == "HTTPVersionConfig(['HTTP/1.1'])" - - -def test_http_versions_from_list(): - http_versions = httpx.HTTPVersionConfig(["http/1.1"]) - assert repr(http_versions) == "HTTPVersionConfig(['HTTP/1.1'])" - - -def test_http_versions_from_config(): - http_versions = httpx.HTTPVersionConfig(httpx.HTTPVersionConfig("HTTP/1.1")) - assert repr(http_versions) == "HTTPVersionConfig(['HTTP/1.1'])" - - -def test_invalid_http_version(): - with pytest.raises(ValueError): - httpx.HTTPVersionConfig("HTTP/9") - - -def test_invalid_http_version_type(): - with pytest.raises(TypeError): - httpx.HTTPVersionConfig(123) - - -def test_invalid_http_version_list_type(): - with pytest.raises(ValueError): - httpx.HTTPVersionConfig([123]) - - -def test_empty_http_version(): - with pytest.raises(ValueError): - httpx.HTTPVersionConfig([]) - - def test_limits_repr(): limits = httpx.PoolLimits(hard_limit=100) assert repr(limits) == "PoolLimits(soft_limit=None, hard_limit=100)" diff --git a/tests/test_multipart.py b/tests/test_multipart.py index 98f8468d63..c689aed879 100644 --- a/tests/test_multipart.py +++ b/tests/test_multipart.py @@ -10,7 +10,6 @@ CertTypes, Client, Dispatcher, - HTTPVersionTypes, Request, Response, TimeoutTypes, @@ -26,7 +25,6 @@ async def send( verify: VerifyTypes = None, cert: CertTypes = None, timeout: TimeoutTypes = None, - http_versions: HTTPVersionTypes = None, ) -> Response: content = await request.read() return Response(200, content=content) From 16f356e8a74841215ebae01f5c031831fb1633f5 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 2 Dec 2019 12:42:00 +0000 Subject: [PATCH 2/7] HTTP/2 docs --- docs/http2.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 docs/http2.md diff --git a/docs/http2.md b/docs/http2.md new file mode 100644 index 0000000000..ed2b08f7d8 --- /dev/null +++ b/docs/http2.md @@ -0,0 +1,42 @@ +# HTTP/2 + +HTTP/2 is a major new iteration of the HTTP protocol, that provides a far more +efficient transport, with potential performance benefits. HTTP/2 does not change +the core semantics of the request or response, but alters the way that data is +sent to and from the server. + +Rather that the text format that HTTP/1.1 uses, HTTP/2 is a binary format. +The binary format provides full request and response multiplexing, and efficient +compression of HTTP headers. The stream multiplexing means that where HTTP/1.1 +requires one TCP stream for each concurrent request, HTTP/2 allows a single TCP +stream to handle multiple concurrent requests. + +HTTP/2 also provides support for functionality such as response prioritization, +and server push. + +## Enabling HTTP/2 + +The HTTPX client provides provisional HTTP/2 support. + +The HTTP/2 support is not enabled by default, because HTTP/1.1 a mature, +battle-hardened transport layer. With HTTP/2 being newer and significantly more +complex, it should be considered a less robust option at this point in time. + +However, if you're issuing highly concurrent requests you might want to consider +trying out our HTTP/2 support. You can do so by instantiating a client with +HTTP/2 support enabled. + +Simply instantiating a client... + +```python +client = httpx.Client(http_2=True) +... +``` + +Instantiating a client as a context manager, to ensure that all connections +are nicely closed at the end of it's usage... + +```python +async with httpx.Client(http_2=True) as client: + ... +``` From 3bfdfd30132e9bee11f3c5bb81e08cabe8ccd3fa Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 2 Dec 2019 14:31:55 +0000 Subject: [PATCH 3/7] HTTP/2 interlinking in docs --- docs/advanced.md | 16 +--------------- docs/http2.md | 32 +++++++++++++++++++++++++------- docs/index.md | 5 +++-- mkdocs.yml | 1 + 4 files changed, 30 insertions(+), 24 deletions(-) diff --git a/docs/advanced.md b/docs/advanced.md index d087bd255e..9b1502fa2f 100644 --- a/docs/advanced.md +++ b/docs/advanced.md @@ -141,20 +141,6 @@ make modifications before sending the request. ``` -## Specify the version of the HTTP protocol - -One can set the version of the HTTP protocol for the client in case you want to make the requests using a specific version. - -For example: - -```python -async with httpx.Client(http_versions=["HTTP/1.1"]) as h11_client: - h11_response = await h11_client.get("https://myserver.com") - -async with httpx.Client(http_versions=["HTTP/2"]) as h2_client: - h2_response = await h2_client.get("https://myserver.com") -``` - ## .netrc Support HTTPX supports .netrc file. In `trust_env=True` cases, if auth parameter is @@ -367,7 +353,7 @@ More specifically, if a tuple is used as a value, it must have between 2 and 3 e - The first element is an optional file name which can be set to `None`. - The second element may be a file-like object or a string which will be automatically encoded in UTF-8. -- An optional third element can be used to specify the +- An optional third element can be used to specify the [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_Types) of the file being uploaded. If not specified HTTPX will attempt to guess the MIME type based on the file name, with unknown file extensions defaulting to "application/octet-stream". diff --git a/docs/http2.md b/docs/http2.md index ed2b08f7d8..66d835d365 100644 --- a/docs/http2.md +++ b/docs/http2.md @@ -14,29 +14,47 @@ stream to handle multiple concurrent requests. HTTP/2 also provides support for functionality such as response prioritization, and server push. +For a comprehensive guide to HTTP/2 you may want to check out "[HTTP2 Explained](https://http2-explained.haxx.se/content/en/)". + ## Enabling HTTP/2 The HTTPX client provides provisional HTTP/2 support. -The HTTP/2 support is not enabled by default, because HTTP/1.1 a mature, +HTTP/2 support is not enabled by default, because HTTP/1.1 is a mature, battle-hardened transport layer. With HTTP/2 being newer and significantly more -complex, it should be considered a less robust option at this point in time. +complex, our implementation should be considered a less robust option at this +point in time. However, if you're issuing highly concurrent requests you might want to consider trying out our HTTP/2 support. You can do so by instantiating a client with -HTTP/2 support enabled. - -Simply instantiating a client... +HTTP/2 support enabled: ```python client = httpx.Client(http_2=True) ... ``` -Instantiating a client as a context manager, to ensure that all connections -are nicely closed at the end of it's usage... +You can also instantiate a client as a context manager, to ensure that all +HTTP connections are nicely scoped, and will be closed once the context block +is exited. ```python async with httpx.Client(http_2=True) as client: ... ``` + +## Inspecting the HTTP version + +Enabling HTTP/2 support on the client does not *necessarily* mean that your +requests and responses will be transported over HTTP/2, since both the client +*and* the server need to support HTTP/2. If you connect to a server that only +supports HTTP/1.1 the client will use a standard HTTP/1.1 connection instead. + +You can determine which version of the HTTP protocol was used by examining +the `.http_version` property on the response. + +```python +client = httpx.Client(http_2=True) +response = await client.get(...) +print(response.http_version) # "HTTP/1.0", "HTTP/1.1", or "HTTP/2". +``` diff --git a/docs/index.md b/docs/index.md index 846d58a496..c233494c92 100644 --- a/docs/index.md +++ b/docs/index.md @@ -64,7 +64,7 @@ HTTPX is a high performance asynchronous HTTP client, that builds on the well-established usability of `requests`, and gives you: * A broadly requests-compatible API. -* HTTP/2 and HTTP/1.1 support. +* HTTP/1.1 and [HTTP/2 support](http2.md). * Ability to [make requests directly to ASGI applications](advanced.md#calling-into-python-web-apps). * Strict timeouts everywhere. * Fully type annotated. @@ -92,7 +92,8 @@ Plus all the standard features of `requests`... For a run-through of all the basics, head over to the [QuickStart](quickstart.md). -For more advanced topics, see the [Advanced Usage](advanced.md) section. +For more advanced topics, see the [Advanced Usage](advanced.md) section, +or the [HTTP/2](http2.md) section. The [Developer Interface](api.md) provides a comprehensive API reference. diff --git a/mkdocs.yml b/mkdocs.yml index 0ddd8c85b6..a2a733c935 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -12,6 +12,7 @@ nav: - Introduction: 'index.md' - QuickStart: 'quickstart.md' - Advanced Usage: 'advanced.md' + - HTTP/2 Support: 'http2.md' - Environment Variables: 'environment_variables.md' - Requests Compatibility: 'compatibility.md' - Developer Interface: 'api.md' From df2afa9081956d3ac55fd9815a0fb81943653e4c Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 2 Dec 2019 16:00:43 +0000 Subject: [PATCH 4/7] Add concurrency auto-detection --- httpx/client.py | 14 +++----- httpx/concurrency/auto.py | 59 +++++++++++++++++++++++++++++++ httpx/concurrency/base.py | 22 ++++++++++++ httpx/dispatch/asgi.py | 4 --- httpx/dispatch/connection.py | 13 +++---- httpx/dispatch/connection_pool.py | 16 ++++++--- httpx/dispatch/http11.py | 4 +-- httpx/dispatch/http2.py | 5 +-- httpx/dispatch/proxy_http.py | 7 ++-- tests/client/test_proxies.py | 1 - 10 files changed, 109 insertions(+), 36 deletions(-) create mode 100644 httpx/concurrency/auto.py diff --git a/httpx/client.py b/httpx/client.py index 28aa1767ec..2593eddf9d 100644 --- a/httpx/client.py +++ b/httpx/client.py @@ -5,7 +5,6 @@ import hstspreload from .auth import BasicAuth -from .concurrency.asyncio import AsyncioBackend from .concurrency.base import ConcurrencyBackend from .config import ( DEFAULT_MAX_REDIRECTS, @@ -95,7 +94,8 @@ class Client: * **app** - *(optional)* An ASGI application to send requests to, rather than sending actual network requests. * **backend** - *(optional)* A concurrency backend to use when issuing - async requests. + async requests. Either 'auto', 'asyncio', 'trio', or a `ConcurrencyBackend` + instance. Defaults to 'auto', for autodetection. * **trust_env** - *(optional)* Enables or disables usage of environment variables for configuration. * **uds** - *(optional)* A path to a Unix domain socket to connect through. @@ -118,15 +118,12 @@ def __init__( base_url: URLTypes = None, dispatch: Dispatcher = None, app: typing.Callable = None, - backend: ConcurrencyBackend = None, + backend: typing.Union[str, ConcurrencyBackend] = "auto", trust_env: bool = True, uds: str = None, ): - if backend is None: - backend = AsyncioBackend() - if app is not None: - dispatch = ASGIDispatch(app=app, backend=backend) + dispatch = ASGIDispatch(app=app) if dispatch is None: dispatch = ConnectionPool( @@ -155,7 +152,6 @@ def __init__( self.max_redirects = max_redirects self.trust_env = trust_env self.dispatch = dispatch - self.concurrency_backend = backend self.netrc = NetRCInfo() if proxies is None and trust_env: @@ -834,7 +830,7 @@ def _proxies_to_dispatchers( timeout: TimeoutTypes, http_2: bool, pool_limits: PoolLimits, - backend: ConcurrencyBackend, + backend: typing.Union[str, ConcurrencyBackend], trust_env: bool, ) -> typing.Dict[str, Dispatcher]: def _proxy_from_url(url: URLTypes) -> Dispatcher: diff --git a/httpx/concurrency/auto.py b/httpx/concurrency/auto.py new file mode 100644 index 0000000000..9b3518deaf --- /dev/null +++ b/httpx/concurrency/auto.py @@ -0,0 +1,59 @@ +import ssl +import typing + +import sniffio + +from ..config import PoolLimits, TimeoutConfig +from .base import ( + BaseBackgroundManager, + BaseEvent, + BasePoolSemaphore, + BaseSocketStream, + ConcurrencyBackend, + lookup_backend, +) + + +class AutoBackend(ConcurrencyBackend): + @property + def backend(self) -> ConcurrencyBackend: + if not hasattr(self, "_backend_implementation"): + backend = sniffio.current_async_library() + if backend not in ("asyncio", "trio"): + raise RuntimeError(f"Unsupported concurrency backend {backend!r}") + self._backend_implementation = lookup_backend(backend) + return self._backend_implementation + + async def open_tcp_stream( + self, + hostname: str, + port: int, + ssl_context: typing.Optional[ssl.SSLContext], + timeout: TimeoutConfig, + ) -> BaseSocketStream: + return await self.backend.open_tcp_stream(hostname, port, ssl_context, timeout) + + async def open_uds_stream( + self, + path: str, + hostname: typing.Optional[str], + ssl_context: typing.Optional[ssl.SSLContext], + timeout: TimeoutConfig, + ) -> BaseSocketStream: + return await self.backend.open_uds_stream(path, hostname, ssl_context, timeout) + + def get_semaphore(self, limits: PoolLimits) -> BasePoolSemaphore: + return self.backend.get_semaphore(limits) + + async def run_in_threadpool( + self, func: typing.Callable, *args: typing.Any, **kwargs: typing.Any + ) -> typing.Any: + return await self.backend.run_in_threadpool(func, *args, **kwargs) + + def create_event(self) -> BaseEvent: + return self.backend.create_event() + + def background_manager( + self, coroutine: typing.Callable, *args: typing.Any + ) -> BaseBackgroundManager: + return self.backend.background_manager(coroutine, *args) diff --git a/httpx/concurrency/base.py b/httpx/concurrency/base.py index 6bbeb071ed..33aca070f1 100644 --- a/httpx/concurrency/base.py +++ b/httpx/concurrency/base.py @@ -5,6 +5,28 @@ from ..config import PoolLimits, TimeoutConfig +def lookup_backend( + backend: typing.Union[str, "ConcurrencyBackend"] = "auto" +) -> "ConcurrencyBackend": + if not isinstance(backend, str): + return backend + + if backend == "auto": + from .auto import AutoBackend + + return AutoBackend() + elif backend == "asyncio": + from .asyncio import AsyncioBackend + + return AsyncioBackend() + elif backend == "trio": + from .trio import TrioBackend + + return TrioBackend() + + raise RuntimeError(f"Unknown or unsupported concurrency backend {backend!r}") + + class TimeoutFlag: """ A timeout flag holds a state of either read-timeout or write-timeout mode. diff --git a/httpx/dispatch/asgi.py b/httpx/dispatch/asgi.py index ee75debb5f..a1e8325595 100644 --- a/httpx/dispatch/asgi.py +++ b/httpx/dispatch/asgi.py @@ -1,7 +1,5 @@ import typing -from ..concurrency.asyncio import AsyncioBackend -from ..concurrency.base import ConcurrencyBackend from ..config import CertTypes, TimeoutTypes, VerifyTypes from ..models import Request, Response from .base import Dispatcher @@ -49,13 +47,11 @@ def __init__( raise_app_exceptions: bool = True, root_path: str = "", client: typing.Tuple[str, int] = ("127.0.0.1", 123), - backend: ConcurrencyBackend = None, ) -> None: self.app = app self.raise_app_exceptions = raise_app_exceptions self.root_path = root_path self.client = client - self.backend = AsyncioBackend() if backend is None else backend async def send( self, diff --git a/httpx/dispatch/connection.py b/httpx/dispatch/connection.py index 0f05b2e8b2..e030770d36 100644 --- a/httpx/dispatch/connection.py +++ b/httpx/dispatch/connection.py @@ -2,8 +2,7 @@ import ssl import typing -from ..concurrency.asyncio import AsyncioBackend -from ..concurrency.base import ConcurrencyBackend +from ..concurrency.base import ConcurrencyBackend, lookup_backend from ..config import ( DEFAULT_TIMEOUT_CONFIG, CertTypes, @@ -34,7 +33,7 @@ def __init__( trust_env: bool = None, timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, http_2: bool = False, - backend: ConcurrencyBackend = None, + backend: typing.Union[str, ConcurrencyBackend] = "auto", release_func: typing.Optional[ReleaseCallback] = None, uds: typing.Optional[str] = None, ): @@ -42,7 +41,7 @@ def __init__( self.ssl = SSLConfig(cert=cert, verify=verify, trust_env=trust_env) self.timeout = TimeoutConfig(timeout) self.http_2 = http_2 - self.backend = AsyncioBackend() if backend is None else backend + self.backend = lookup_backend(backend) self.release_func = release_func self.uds = uds self.h11_connection = None # type: typing.Optional[HTTP11Connection] @@ -104,13 +103,11 @@ async def connect( if http_version == "HTTP/2": self.h2_connection = HTTP2Connection( - stream, self.backend, on_release=on_release + stream, backend=self.backend, on_release=on_release ) else: assert http_version == "HTTP/1.1" - self.h11_connection = HTTP11Connection( - stream, self.backend, on_release=on_release - ) + self.h11_connection = HTTP11Connection(stream, on_release=on_release) async def get_ssl_context(self, ssl: SSLConfig) -> typing.Optional[ssl.SSLContext]: if not self.origin.is_ssl: diff --git a/httpx/dispatch/connection_pool.py b/httpx/dispatch/connection_pool.py index fc44c41301..3069568b0b 100644 --- a/httpx/dispatch/connection_pool.py +++ b/httpx/dispatch/connection_pool.py @@ -1,7 +1,6 @@ import typing -from ..concurrency.asyncio import AsyncioBackend -from ..concurrency.base import ConcurrencyBackend +from ..concurrency.base import BasePoolSemaphore, ConcurrencyBackend, lookup_backend from ..config import ( DEFAULT_POOL_LIMITS, DEFAULT_TIMEOUT_CONFIG, @@ -88,7 +87,7 @@ def __init__( timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, pool_limits: PoolLimits = DEFAULT_POOL_LIMITS, http_2: bool = False, - backend: ConcurrencyBackend = None, + backend: typing.Union[str, ConcurrencyBackend] = "auto", uds: typing.Optional[str] = None, ): self.verify = verify @@ -103,8 +102,15 @@ def __init__( self.keepalive_connections = ConnectionStore() self.active_connections = ConnectionStore() - self.backend = AsyncioBackend() if backend is None else backend - self.max_connections = self.backend.get_semaphore(pool_limits) + self.backend = lookup_backend(backend) + + @property + def max_connections(self) -> BasePoolSemaphore: + # We do this lazily, to make sure backend autodetection always + # runs within an async context. + if not hasattr(self, "_max_connections"): + self._max_connections = self.backend.get_semaphore(self.pool_limits) + return self._max_connections @property def num_connections(self) -> int: diff --git a/httpx/dispatch/http11.py b/httpx/dispatch/http11.py index 8202781b6c..e442629501 100644 --- a/httpx/dispatch/http11.py +++ b/httpx/dispatch/http11.py @@ -2,7 +2,7 @@ import h11 -from ..concurrency.base import BaseSocketStream, ConcurrencyBackend, TimeoutFlag +from ..concurrency.base import BaseSocketStream, TimeoutFlag from ..config import TimeoutConfig, TimeoutTypes from ..exceptions import ConnectionClosed, ProtocolError from ..models import Request, Response @@ -33,11 +33,9 @@ class HTTP11Connection: def __init__( self, stream: BaseSocketStream, - backend: ConcurrencyBackend, on_release: typing.Optional[OnReleaseCallback] = None, ): self.stream = stream - self.backend = backend self.on_release = on_release self.h11_state = h11.Connection(our_role=h11.CLIENT) self.timeout_flag = TimeoutFlag() diff --git a/httpx/dispatch/http2.py b/httpx/dispatch/http2.py index 9947155a0c..7bfd519cb9 100644 --- a/httpx/dispatch/http2.py +++ b/httpx/dispatch/http2.py @@ -10,6 +10,7 @@ BaseSocketStream, ConcurrencyBackend, TimeoutFlag, + lookup_backend, ) from ..config import TimeoutConfig, TimeoutTypes from ..exceptions import ProtocolError @@ -25,11 +26,11 @@ class HTTP2Connection: def __init__( self, stream: BaseSocketStream, - backend: ConcurrencyBackend, + backend: typing.Union[str, ConcurrencyBackend] = "auto", on_release: typing.Callable = None, ): self.stream = stream - self.backend = backend + self.backend = lookup_backend(backend) self.on_release = on_release self.h2_state = h2.connection.H2Connection() self.events = {} # type: typing.Dict[int, typing.List[h2.events.Event]] diff --git a/httpx/dispatch/proxy_http.py b/httpx/dispatch/proxy_http.py index c51c009e12..effbd79ee2 100644 --- a/httpx/dispatch/proxy_http.py +++ b/httpx/dispatch/proxy_http.py @@ -1,4 +1,5 @@ import enum +import typing from base64 import b64encode import h11 @@ -47,7 +48,7 @@ def __init__( timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, pool_limits: PoolLimits = DEFAULT_POOL_LIMITS, http_2: bool = False, - backend: ConcurrencyBackend = None, + backend: typing.Union[str, ConcurrencyBackend] = "auto", ): super(HTTPProxy, self).__init__( @@ -207,9 +208,7 @@ async def tunnel_start_tls( ) else: assert http_version == "HTTP/1.1" - connection.h11_connection = HTTP11Connection( - stream, self.backend, on_release=on_release - ) + connection.h11_connection = HTTP11Connection(stream, on_release=on_release) def should_forward_origin(self, origin: Origin) -> bool: """Determines if the given origin should diff --git a/tests/client/test_proxies.py b/tests/client/test_proxies.py index ede15a8fce..c0c1ae0e83 100644 --- a/tests/client/test_proxies.py +++ b/tests/client/test_proxies.py @@ -48,7 +48,6 @@ def test_proxies_has_same_properties_as_dispatch(): "cert", "timeout", "pool_limits", - "backend", ]: assert getattr(pool, prop) == getattr(proxy, prop) From 08a5bb82c3223a76a356e901e8310561739b21fd Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 2 Dec 2019 16:04:28 +0000 Subject: [PATCH 5/7] Add sniffio --- README.md | 1 + setup.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 66393ff909..431a959498 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ The httpx project relies on these excellent libraries: * `hstspreload` - determines whether IDNA-encoded host should be only accessed via HTTPS. * `idna` - Internationalized domain name support. * `rfc3986` - URL parsing & normalization. +* `sniffio` - Async library autodetection. * `brotlipy` - Decoding for "brotli" compressed responses. *(Optional)* A huge amount of credit is due to `requests` for the API layout that diff --git a/setup.py b/setup.py index bb16e9631b..e58fd8ce68 100644 --- a/setup.py +++ b/setup.py @@ -52,12 +52,13 @@ def get_packages(package): zip_safe=False, install_requires=[ "certifi", + "hstspreload", "chardet==3.*", "h11==0.8.*", "h2==3.*", - "hstspreload>=2019.8.27", "idna==2.*", "rfc3986==1.*", + "sniffio==1.*", ], classifiers=[ "Development Status :: 3 - Alpha", From 2e243fc7c0e131c243b692b96611da4224cf707a Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 2 Dec 2019 17:46:14 +0000 Subject: [PATCH 6/7] Rename HTTP2 switch on client --- httpx/client.py | 14 +++++++------- httpx/dispatch/connection.py | 4 ++-- httpx/dispatch/connection_pool.py | 6 +++--- httpx/dispatch/proxy_http.py | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/httpx/client.py b/httpx/client.py index 2593eddf9d..b845724fd6 100644 --- a/httpx/client.py +++ b/httpx/client.py @@ -77,7 +77,7 @@ class Client: to authenticate the client. Either a path to an SSL certificate file, or two-tuple of (certificate file, key file), or a three-tuple of (certificate file, key file, password). - * **http_2** - *(optional)* A boolean indicating if HTTP/2 support should be + * **http2** - *(optional)* A boolean indicating if HTTP/2 support should be enabled. Defaults to `False`. * **proxies** - *(optional)* A dictionary mapping HTTP protocols to proxy URLs. @@ -110,7 +110,7 @@ def __init__( cookies: CookieTypes = None, verify: VerifyTypes = True, cert: CertTypes = None, - http_2: bool = False, + http2: bool = False, proxies: ProxiesTypes = None, timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, pool_limits: PoolLimits = DEFAULT_POOL_LIMITS, @@ -130,7 +130,7 @@ def __init__( verify=verify, cert=cert, timeout=timeout, - http_2=http_2, + http2=http2, pool_limits=pool_limits, backend=backend, trust_env=trust_env, @@ -162,7 +162,7 @@ def __init__( verify=verify, cert=cert, timeout=timeout, - http_2=http_2, + http2=http2, pool_limits=pool_limits, backend=backend, trust_env=trust_env, @@ -828,13 +828,13 @@ def _proxies_to_dispatchers( verify: VerifyTypes, cert: typing.Optional[CertTypes], timeout: TimeoutTypes, - http_2: bool, + http2: bool, pool_limits: PoolLimits, backend: typing.Union[str, ConcurrencyBackend], trust_env: bool, ) -> typing.Dict[str, Dispatcher]: def _proxy_from_url(url: URLTypes) -> Dispatcher: - nonlocal verify, cert, timeout, http_2, pool_limits, backend, trust_env + nonlocal verify, cert, timeout, http2, pool_limits, backend, trust_env url = URL(url) if url.scheme in ("http", "https"): return HTTPProxy( @@ -845,7 +845,7 @@ def _proxy_from_url(url: URLTypes) -> Dispatcher: pool_limits=pool_limits, backend=backend, trust_env=trust_env, - http_2=http_2, + http2=http2, ) raise ValueError(f"Unknown proxy for {url!r}") diff --git a/httpx/dispatch/connection.py b/httpx/dispatch/connection.py index e030770d36..c125132410 100644 --- a/httpx/dispatch/connection.py +++ b/httpx/dispatch/connection.py @@ -32,7 +32,7 @@ def __init__( cert: CertTypes = None, trust_env: bool = None, timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, - http_2: bool = False, + http2: bool = False, backend: typing.Union[str, ConcurrencyBackend] = "auto", release_func: typing.Optional[ReleaseCallback] = None, uds: typing.Optional[str] = None, @@ -40,7 +40,7 @@ def __init__( self.origin = Origin(origin) if isinstance(origin, str) else origin self.ssl = SSLConfig(cert=cert, verify=verify, trust_env=trust_env) self.timeout = TimeoutConfig(timeout) - self.http_2 = http_2 + self.http2 = http2 self.backend = lookup_backend(backend) self.release_func = release_func self.uds = uds diff --git a/httpx/dispatch/connection_pool.py b/httpx/dispatch/connection_pool.py index 3069568b0b..3505477a45 100644 --- a/httpx/dispatch/connection_pool.py +++ b/httpx/dispatch/connection_pool.py @@ -86,7 +86,7 @@ def __init__( trust_env: bool = None, timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, pool_limits: PoolLimits = DEFAULT_POOL_LIMITS, - http_2: bool = False, + http2: bool = False, backend: typing.Union[str, ConcurrencyBackend] = "auto", uds: typing.Optional[str] = None, ): @@ -94,7 +94,7 @@ def __init__( self.cert = cert self.timeout = TimeoutConfig(timeout) self.pool_limits = pool_limits - self.http_2 = http_2 + self.http2 = http2 self.is_closed = False self.trust_env = trust_env self.uds = uds @@ -155,7 +155,7 @@ async def acquire_connection( verify=self.verify, cert=self.cert, timeout=self.timeout, - http_2=self.http_2, + http2=self.http2, backend=self.backend, release_func=self.release_connection, trust_env=self.trust_env, diff --git a/httpx/dispatch/proxy_http.py b/httpx/dispatch/proxy_http.py index effbd79ee2..9e1da38d19 100644 --- a/httpx/dispatch/proxy_http.py +++ b/httpx/dispatch/proxy_http.py @@ -47,7 +47,7 @@ def __init__( trust_env: bool = None, timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, pool_limits: PoolLimits = DEFAULT_POOL_LIMITS, - http_2: bool = False, + http2: bool = False, backend: typing.Union[str, ConcurrencyBackend] = "auto", ): @@ -58,7 +58,7 @@ def __init__( pool_limits=pool_limits, backend=backend, trust_env=trust_env, - http_2=http_2, + http2=http2, ) self.proxy_url = URL(proxy_url) From 4294c2567820dad833fe85b54c8f36b281fbb12d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 2 Dec 2019 19:25:35 +0000 Subject: [PATCH 7/7] http_2 -> http2 --- httpx/config.py | 20 ++++++++++---------- httpx/dispatch/connection.py | 2 +- httpx/dispatch/proxy_http.py | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/httpx/config.py b/httpx/config.py index cace3c20dd..e997b9f47e 100644 --- a/httpx/config.py +++ b/httpx/config.py @@ -86,35 +86,35 @@ def with_overrides( return self return SSLConfig(cert=cert, verify=verify) - def load_ssl_context(self, http_2: bool = False) -> ssl.SSLContext: + def load_ssl_context(self, http2: bool = False) -> ssl.SSLContext: logger.trace( f"load_ssl_context " f"verify={self.verify!r} " f"cert={self.cert!r} " f"trust_env={self.trust_env!r} " - f"http_2={http_2!r}" + f"http2={http2!r}" ) if self.ssl_context is None: self.ssl_context = ( - self.load_ssl_context_verify(http_2=http_2) + self.load_ssl_context_verify(http2=http2) if self.verify - else self.load_ssl_context_no_verify(http_2=http_2) + else self.load_ssl_context_no_verify(http2=http2) ) assert self.ssl_context is not None return self.ssl_context - def load_ssl_context_no_verify(self, http_2: bool = False) -> ssl.SSLContext: + def load_ssl_context_no_verify(self, http2: bool = False) -> ssl.SSLContext: """ Return an SSL context for unverified connections. """ - context = self._create_default_ssl_context(http_2=http_2) + context = self._create_default_ssl_context(http2=http2) context.verify_mode = ssl.CERT_NONE context.check_hostname = False return context - def load_ssl_context_verify(self, http_2: bool = False) -> ssl.SSLContext: + def load_ssl_context_verify(self, http2: bool = False) -> ssl.SSLContext: """ Return an SSL context for verified connections. """ @@ -133,7 +133,7 @@ def load_ssl_context_verify(self, http_2: bool = False) -> ssl.SSLContext: "invalid path: {}".format(self.verify) ) - context = self._create_default_ssl_context(http_2=http_2) + context = self._create_default_ssl_context(http2=http2) context.verify_mode = ssl.CERT_REQUIRED context.check_hostname = True @@ -162,7 +162,7 @@ def load_ssl_context_verify(self, http_2: bool = False) -> ssl.SSLContext: return context - def _create_default_ssl_context(self, http_2: bool) -> ssl.SSLContext: + def _create_default_ssl_context(self, http2: bool) -> ssl.SSLContext: """ Creates the default SSLContext object that's used for both verified and unverified connections. @@ -176,7 +176,7 @@ def _create_default_ssl_context(self, http_2: bool) -> ssl.SSLContext: context.set_ciphers(DEFAULT_CIPHERS) if ssl.HAS_ALPN: - alpn_idents = ["http/1.1", "h2"] if http_2 else ["http/1.1"] + alpn_idents = ["http/1.1", "h2"] if http2 else ["http/1.1"] context.set_alpn_protocols(alpn_idents) if hasattr(context, "keylog_filename"): diff --git a/httpx/dispatch/connection.py b/httpx/dispatch/connection.py index c125132410..e6f39a662e 100644 --- a/httpx/dispatch/connection.py +++ b/httpx/dispatch/connection.py @@ -114,7 +114,7 @@ async def get_ssl_context(self, ssl: SSLConfig) -> typing.Optional[ssl.SSLContex return None # Run the SSL loading in a threadpool, since it may make disk accesses. - return await self.backend.run_in_threadpool(ssl.load_ssl_context, self.http_2) + return await self.backend.run_in_threadpool(ssl.load_ssl_context, self.http2) async def close(self) -> None: logger.trace("close_connection") diff --git a/httpx/dispatch/proxy_http.py b/httpx/dispatch/proxy_http.py index 9e1da38d19..d146c57daa 100644 --- a/httpx/dispatch/proxy_http.py +++ b/httpx/dispatch/proxy_http.py @@ -134,7 +134,7 @@ async def request_tunnel_proxy_connection(self, origin: Origin) -> HTTPConnectio cert=self.cert, timeout=self.timeout, backend=self.backend, - http_2=False, # Short-lived 'connection' + http2=False, # Short-lived 'connection' trust_env=self.trust_env, release_func=self.release_connection, )