diff --git a/litellm/llms/custom_httpx/http_handler.py b/litellm/llms/custom_httpx/http_handler.py index 37b4af306a..dbeceab5e0 100644 --- a/litellm/llms/custom_httpx/http_handler.py +++ b/litellm/llms/custom_httpx/http_handler.py @@ -87,6 +87,60 @@ def _prepare_request_data_and_content( return request_data, request_content +# Cache for SSL contexts to avoid creating duplicate contexts with the same configuration +# Key: tuple of (cafile, ssl_security_level, ssl_ecdh_curve) +# Value: ssl.SSLContext +_ssl_context_cache: Dict[Tuple[Optional[str], Optional[str], Optional[str]], ssl.SSLContext] = {} + + +def _create_ssl_context( + cafile: Optional[str], + ssl_security_level: Optional[str], + ssl_ecdh_curve: Optional[str], +) -> ssl.SSLContext: + """ + Create an SSL context with the given configuration. + This is separated from get_ssl_configuration to enable caching. + """ + custom_ssl_context = ssl.create_default_context(cafile=cafile) + + # Optimize SSL handshake performance + # Set minimum TLS version to 1.2 for better performance + custom_ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2 + + # Configure cipher suites for optimal performance + if ssl_security_level and isinstance(ssl_security_level, str): + # User provided custom cipher configuration (e.g., via SSL_SECURITY_LEVEL env var) + custom_ssl_context.set_ciphers(ssl_security_level) + else: + # Use optimized cipher list that strongly prefers fast ciphers + # but falls back to widely compatible ones + custom_ssl_context.set_ciphers(DEFAULT_SSL_CIPHERS) + + # Configure ECDH curve for key exchange (e.g., to disable PQC and improve performance) + # Set SSL_ECDH_CURVE env var or litellm.ssl_ecdh_curve to 'X25519' to disable PQC + # Common valid curves: X25519, prime256v1, secp384r1, secp521r1 + if ssl_ecdh_curve and isinstance(ssl_ecdh_curve, str): + try: + custom_ssl_context.set_ecdh_curve(ssl_ecdh_curve) + verbose_logger.debug(f"SSL ECDH curve set to: {ssl_ecdh_curve}") + except AttributeError: + verbose_logger.warning( + f"SSL ECDH curve configuration not supported. " + f"Python version: {sys.version.split()[0]}, OpenSSL version: {ssl.OPENSSL_VERSION}. " + f"Requested curve: {ssl_ecdh_curve}. Continuing with default curves." + ) + except ValueError as e: + # Invalid curve name + verbose_logger.warning( + f"Invalid SSL ECDH curve name: '{ssl_ecdh_curve}'. {e}. " + f"Common valid curves: X25519, prime256v1, secp384r1, secp521r1. " + f"Continuing with default curves (including PQC)." + ) + + return custom_ssl_context + + def get_ssl_configuration( ssl_verify: Optional[VerifyTypes] = None, ) -> Union[bool, str, ssl.SSLContext]: @@ -102,6 +156,9 @@ def get_ssl_configuration( If ssl_security_level is set, it will apply the security level to the SSL context. + SSL contexts are cached to avoid creating duplicate contexts with the same configuration, + which reduces memory allocation and improves performance. + Args: ssl_verify: SSL verification setting. Can be: - None: Use default from environment/litellm settings @@ -128,6 +185,7 @@ def get_ssl_configuration( ssl_verify = ssl_verify_bool ssl_security_level = os.getenv("SSL_SECURITY_LEVEL", litellm.ssl_security_level) + ssl_ecdh_curve = os.getenv("SSL_ECDH_CURVE", litellm.ssl_ecdh_curve) cafile = None if isinstance(ssl_verify, str) and os.path.exists(ssl_verify): @@ -140,45 +198,19 @@ def get_ssl_configuration( cafile = certifi.where() if ssl_verify is not False: - custom_ssl_context = ssl.create_default_context(cafile=cafile) - - # Optimize SSL handshake performance - # Set minimum TLS version to 1.2 for better performance - custom_ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2 - - # Configure cipher suites for optimal performance - if ssl_security_level and isinstance(ssl_security_level, str): - # User provided custom cipher configuration (e.g., via SSL_SECURITY_LEVEL env var) - custom_ssl_context.set_ciphers(ssl_security_level) - else: - # Use optimized cipher list that strongly prefers fast ciphers - # but falls back to widely compatible ones - custom_ssl_context.set_ciphers(DEFAULT_SSL_CIPHERS) - - # Configure ECDH curve for key exchange (e.g., to disable PQC and improve performance) - # Set SSL_ECDH_CURVE env var or litellm.ssl_ecdh_curve to 'X25519' to disable PQC - # Common valid curves: X25519, prime256v1, secp384r1, secp521r1 - ssl_ecdh_curve = os.getenv("SSL_ECDH_CURVE", litellm.ssl_ecdh_curve) - if ssl_ecdh_curve and isinstance(ssl_ecdh_curve, str): - try: - custom_ssl_context.set_ecdh_curve(ssl_ecdh_curve) - verbose_logger.debug(f"SSL ECDH curve set to: {ssl_ecdh_curve}") - except AttributeError: - verbose_logger.warning( - f"SSL ECDH curve configuration not supported. " - f"Python version: {sys.version.split()[0]}, OpenSSL version: {ssl.OPENSSL_VERSION}. " - f"Requested curve: {ssl_ecdh_curve}. Continuing with default curves." - ) - except ValueError as e: - # Invalid curve name - verbose_logger.warning( - f"Invalid SSL ECDH curve name: '{ssl_ecdh_curve}'. {e}. " - f"Common valid curves: X25519, prime256v1, secp384r1, secp521r1. " - f"Continuing with default curves (including PQC)." - ) - - # Use our custom SSL context instead of the original ssl_verify value - return custom_ssl_context + # Create cache key from configuration parameters + cache_key = (cafile, ssl_security_level, ssl_ecdh_curve) + + # Check if we have a cached SSL context for this configuration + if cache_key not in _ssl_context_cache: + _ssl_context_cache[cache_key] = _create_ssl_context( + cafile=cafile, + ssl_security_level=ssl_security_level, + ssl_ecdh_curve=ssl_ecdh_curve, + ) + + # Return the cached SSL context + return _ssl_context_cache[cache_key] return ssl_verify