Skip to content

Commit c25cff0

Browse files
committed
transport: cache default ssl context
1 parent c7c13f1 commit c25cff0

File tree

2 files changed

+85
-8
lines changed

2 files changed

+85
-8
lines changed

httpx/_transports/default.py

+46-8
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import contextlib
3030
import typing
31+
from functools import cache
3132
from types import TracebackType
3233

3334
if typing.TYPE_CHECKING:
@@ -70,6 +71,35 @@
7071

7172
HTTPCORE_EXC_MAP: dict[type[Exception], type[httpx.HTTPError]] = {}
7273

74+
_DEFAULT_VERIFY = True
75+
_DEFAULT_CERT = None
76+
_DEFAULT_TRUST_ENV = True
77+
78+
79+
@cache
80+
def _create_default_ssl_context() -> ssl.Context:
81+
return create_ssl_context(
82+
verify=_DEFAULT_VERIFY,
83+
cert=_DEFAULT_CERT,
84+
trust_env=_DEFAULT_TRUST_ENV,
85+
)
86+
87+
88+
@cache
89+
def _create_or_reuse_ssl_context(
90+
verify: ssl.SSLContext | str | bool = _DEFAULT_VERIFY,
91+
cert: CertTypes | None = _DEFAULT_CERT,
92+
trust_env: bool = _DEFAULT_TRUST_ENV,
93+
) -> ssl.Context:
94+
if (verify, cert, trust_env) == (
95+
_DEFAULT_VERIFY,
96+
_DEFAULT_CERT,
97+
_DEFAULT_TRUST_ENV,
98+
):
99+
return _create_default_ssl_context()
100+
else:
101+
return create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)
102+
73103

74104
def _load_httpcore_exceptions() -> dict[type[Exception], type[httpx.HTTPError]]:
75105
import httpcore
@@ -135,9 +165,9 @@ def close(self) -> None:
135165
class HTTPTransport(BaseTransport):
136166
def __init__(
137167
self,
138-
verify: ssl.SSLContext | str | bool = True,
139-
cert: CertTypes | None = None,
140-
trust_env: bool = True,
168+
verify: ssl.SSLContext | str | bool = _DEFAULT_VERIFY,
169+
cert: CertTypes | None = _DEFAULT_CERT,
170+
trust_env: bool = _DEFAULT_TRUST_ENV,
141171
http1: bool = True,
142172
http2: bool = False,
143173
limits: Limits = DEFAULT_LIMITS,
@@ -150,7 +180,11 @@ def __init__(
150180
import httpcore
151181

152182
proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
153-
ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)
183+
ssl_context = _create_or_reuse_ssl_context(
184+
verify=verify,
185+
cert=cert,
186+
trust_env=trust_env,
187+
)
154188

155189
if proxy is None:
156190
self._pool = httpcore.ConnectionPool(
@@ -279,9 +313,9 @@ async def aclose(self) -> None:
279313
class AsyncHTTPTransport(AsyncBaseTransport):
280314
def __init__(
281315
self,
282-
verify: ssl.SSLContext | str | bool = True,
283-
cert: CertTypes | None = None,
284-
trust_env: bool = True,
316+
verify: ssl.SSLContext | str | bool = _DEFAULT_VERIFY,
317+
cert: CertTypes | None = _DEFAULT_CERT,
318+
trust_env: bool = _DEFAULT_TRUST_ENV,
285319
http1: bool = True,
286320
http2: bool = False,
287321
limits: Limits = DEFAULT_LIMITS,
@@ -294,7 +328,11 @@ def __init__(
294328
import httpcore
295329

296330
proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
297-
ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)
331+
ssl_context = _create_or_reuse_ssl_context(
332+
verify=verify,
333+
cert=cert,
334+
trust_env=trust_env,
335+
)
298336

299337
if proxy is None:
300338
self._pool = httpcore.AsyncConnectionPool(

tests/client/test_transports.py

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import pytest
2+
3+
from httpx import AsyncHTTPTransport, HTTPTransport
4+
from httpx._transports.default import _DEFAULT_TRUST_ENV, _DEFAULT_VERIFY
5+
6+
DIFFERENT_DEFAULT = {"verify": not _DEFAULT_VERIFY}
7+
DIFFERENT_CERT_ENV = {"cert": ()}
8+
DIFFERENT_TRUST_ENV = {"trust_env": not _DEFAULT_TRUST_ENV}
9+
10+
11+
@pytest.mark.parametrize("transport", [HTTPTransport, AsyncHTTPTransport])
12+
def test_default_ssl_config_cached(transport):
13+
transport_1 = transport()
14+
transport_2 = transport()
15+
assert transport_1._pool._ssl_context is not None
16+
assert transport_2._pool._ssl_context is not None
17+
18+
assert transport_1._pool._ssl_context is transport_2._pool._ssl_context
19+
20+
21+
@pytest.mark.parametrize("transport", [HTTPTransport, AsyncHTTPTransport])
22+
@pytest.mark.parametrize(
23+
("kwargs_1", "kwargs_2"),
24+
[
25+
({}, DIFFERENT_DEFAULT),
26+
(DIFFERENT_DEFAULT, {}),
27+
({}, DIFFERENT_CERT_ENV),
28+
(DIFFERENT_CERT_ENV, {}),
29+
({}, DIFFERENT_TRUST_ENV),
30+
(DIFFERENT_TRUST_ENV, {}),
31+
],
32+
)
33+
def test_non_default_ssl_config_not_cached(transport, kwargs_1, kwargs_2):
34+
transport_1 = transport(**kwargs_1)
35+
transport_2 = transport(**kwargs_2)
36+
assert transport_1._pool._ssl_context is not None
37+
assert transport_2._pool._ssl_context is not None
38+
39+
assert transport_1._pool._ssl_context is not transport_2._pool._ssl_context

0 commit comments

Comments
 (0)