From 98998c207d7c33bafd4e16306d9afc5ba4a7e7eb Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 11 Sep 2026 02:22:00 +0000 Subject: [PATCH] fix(http_handler): isolate ssl context for http2 clients Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/llms/custom_httpx/http_handler.py | 12 +++++++----- .../llms/custom_httpx/test_http_handler.py | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/litellm/llms/custom_httpx/http_handler.py b/litellm/llms/custom_httpx/http_handler.py index 64520cd824f..18dc8b71e12 100644 --- a/litellm/llms/custom_httpx/http_handler.py +++ b/litellm/llms/custom_httpx/http_handler.py @@ -235,9 +235,9 @@ def _prepare_request_data_and_content( # Cache for SSL contexts to avoid creating duplicate contexts with the same configuration -# Key: tuple of (cafile, ssl_security_level, ssl_ecdh_curve) +# Key: tuple of (cafile, ssl_security_level, ssl_ecdh_curve, http2) # Value: ssl.SSLContext -_ssl_context_cache: Final[dict[tuple[str | None, str | None, str | None], ssl.SSLContext]] = {} +_ssl_context_cache: Final[dict[tuple[str | None, str | None, str | None, bool], ssl.SSLContext]] = {} def _create_ssl_context( @@ -330,6 +330,7 @@ def get_ssl_verify( def get_ssl_configuration( ssl_verify: VerifyTypes | None = None, + http2: bool = False, ) -> bool | str | ssl.SSLContext: """ Unified SSL configuration function that handles ssl_context and ssl_verify logic. @@ -345,6 +346,7 @@ def get_ssl_configuration( SSL contexts are cached to avoid creating duplicate contexts with the same configuration, which reduces memory allocation and improves performance. + http2 clients get their own cached context because httpcore sets ALPN protocols on it. Args: ssl_verify: SSL verification setting. Can be: @@ -378,7 +380,7 @@ def get_ssl_configuration( if ssl_verify is not False: # Create cache key from configuration parameters - cache_key: Final = (cafile, ssl_security_level, ssl_ecdh_curve) + cache_key: Final = (cafile, ssl_security_level, ssl_ecdh_curve, http2) # Check if we have a cached SSL context for this configuration if cache_key not in _ssl_context_cache: @@ -609,7 +611,7 @@ class AsyncHTTPHandler: http2: bool = False, ) -> httpx.AsyncClient: # Get unified SSL configuration - ssl_config: Final = get_ssl_configuration(ssl_verify) + ssl_config: Final = get_ssl_configuration(ssl_verify, http2=http2) # An SSL certificate used by the requested host to authenticate the client. # /path/to/client.pem @@ -1273,7 +1275,7 @@ class HTTPHandler: def create_client(self) -> httpx.Client: # Get unified SSL configuration - ssl_config: Final = get_ssl_configuration(self.ssl_verify) + ssl_config: Final = get_ssl_configuration(self.ssl_verify, http2=self.http2) # An SSL certificate used by the requested host to authenticate the client. # /path/to/client.pem diff --git a/tests/test_litellm/llms/custom_httpx/test_http_handler.py b/tests/test_litellm/llms/custom_httpx/test_http_handler.py index e50dfbc75aa..ebd84cf2d5a 100644 --- a/tests/test_litellm/llms/custom_httpx/test_http_handler.py +++ b/tests/test_litellm/llms/custom_httpx/test_http_handler.py @@ -306,6 +306,23 @@ def test_get_ssl_configuration(): assert result == mock_ssl_context +def test_get_ssl_configuration_http2_uses_separate_context(): + from litellm.llms.custom_httpx.http_handler import _ssl_context_cache + + _ssl_context_cache.clear() + + default_context = get_ssl_configuration() + http2_context = get_ssl_configuration(http2=True) + + assert default_context is not http2_context + assert get_ssl_configuration() is default_context + assert get_ssl_configuration(http2=True) is http2_context + + custom_context = ssl.create_default_context() + assert get_ssl_configuration(custom_context) is custom_context + assert get_ssl_configuration(custom_context, http2=True) is custom_context + + def test_get_ssl_configuration_integration(): """Integration test that _get_ssl_context() returns a working SSL context""" # Call the static method without mocking