Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize Import Time by Deferring SSL Context Creation #100

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 3 additions & 17 deletions src/requests/adapters.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,3 @@
"""
requests.adapters
~~~~~~~~~~~~~~~~~

This module contains the transport adapters that Requests uses to define
and maintain connections.
"""

import os.path
import socket # noqa: F401
import typing
import warnings

from urllib3.exceptions import ClosedPoolError, ConnectTimeoutError
from urllib3.exceptions import HTTPError as _HTTPError
from urllib3.exceptions import InvalidHeader as _InvalidHeader
Expand All @@ -27,7 +14,6 @@
from urllib3.util import Timeout as TimeoutSauce
from urllib3.util import parse_url
from urllib3.util.retry import Retry
from urllib3.util.ssl_ import create_urllib3_context

from .auth import _basic_auth_str
from .compat import basestring, urlparse
Expand All @@ -47,10 +33,10 @@
from .models import Response
from .structures import CaseInsensitiveDict
from .utils import (
DEFAULT_CA_BUNDLE_PATH,
extract_zipped_paths,
get_auth_from_url,
get_encoding_from_headers,
get_ssl_context,
prepend_scheme_if_needed,
select_proxy,
urldefragauth,
Expand Down Expand Up @@ -117,14 +103,14 @@ def _urllib3_request_context(
pool_kwargs["ca_certs"] = verify
else:
pool_kwargs["ca_cert_dir"] = verify
pool_kwargs["cert_reqs"] = cert_reqs
not has_poolmanager_ssl_context
if client_cert is not None:
if isinstance(client_cert, tuple) and len(client_cert) == 2:
pool_kwargs["cert_file"] = client_cert[0]
pool_kwargs["key_file"] = client_cert[1]
else:
# According to our docs, we allow users to specify just the client
# cert path
pool_kwargs["ssl_context"] = get_ssl_context()
pool_kwargs["cert_file"] = client_cert
host_params = {
"scheme": scheme,
Expand Down
45 changes: 43 additions & 2 deletions src/requests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
import warnings
import zipfile
from collections import OrderedDict
from typing import Optional

from urllib3.util import make_headers, parse_url
from urllib3.util import make_headers, parse_url, create_urllib3_context

from . import certs
from .__version__ import __version__
Expand Down Expand Up @@ -62,10 +63,12 @@
NETRC_FILES = (".netrc", "_netrc")

DEFAULT_CA_BUNDLE_PATH = certs.where()

DEFAULT_CA_BUNDLE_PATH = certs.where()
DEFAULT_PORTS = {"http": 80, "https": 443}

# Ensure that ', ' is used to preserve previous delimiter behavior.
_SSL_CONTEXT: Optional["ssl.SSLContext"] = None

DEFAULT_ACCEPT_ENCODING = ", ".join(
re.split(r",\s*", make_headers(accept_encoding=True)["accept-encoding"])
)
Expand Down Expand Up @@ -124,6 +127,44 @@ def proxy_bypass(host): # noqa
return proxy_bypass_registry(host)


def get_ssl_context() -> "ssl.SSLContext | None":
"""
Returns a custom ``SSLContext`` for Requests.

This function should only be called once per interpreter instance because it can be expensive to call.

:rtype: ssl.SSLContext | None
:returns: The custom ``SSLContext`` for Requests if one could be created, otherwise ``None``.
"""
global _SSL_CONTEXT

if _SSL_CONTEXT is not None:
return _SSL_CONTEXT

try:
# Import ssl here so if it fails we only error on first use of SSLContext creation.
# This allows users to disable SSL verification without third-party dependencies by setting verify=False.
from urllib3.util.ssl_ import create_urllib3_context # type: ignore[import]

_SSL_CONTEXT = create_urllib3_context()

# In some cases, the user may have already loaded a custom CA bundle path into their default SSL context.
# If this is true we want to skip over our default CA load because it may produce a warning or error.
context_has_custom_ca_path = (
DEFAULT_CA_BUNDLE_PATH not in _SSL_CONTEXT.get_ca_certs([]) # type: ignore[attr-defined]
)

if not context_has_custom_ca_path:
_SSL_CONTEXT.load_verify_locations(
extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH)
)

except ImportError:
pass

return _SSL_CONTEXT


def dict_to_sequence(d):
"""Returns an internal sequence dictionary update."""

Expand Down
Loading