fix(http): raise aiohttp max_field_size for large response headers

GitHub OAuth token responses include ~9KB Content-Security-Policy
headers that exceed aiohttp's 8190 default and fail MCP token exchange
with LineTooLong. Pass a higher max_field_size/max_line_size on both
the shared proxy session and the aiohttp transport session factory.

Fixes #48468
This commit is contained in:
er-s-an 2026-10-27 09:23:23 +08:00
parent cfe0100e1f
commit 4ef4f554ee
5 changed files with 55 additions and 7 deletions

View file

@ -388,6 +388,10 @@ AIOHTTP_CONNECTOR_LIMIT: Final = get_env_int("AIOHTTP_CONNECTOR_LIMIT", 1000)
AIOHTTP_CONNECTOR_LIMIT_PER_HOST: Final = get_env_int("AIOHTTP_CONNECTOR_LIMIT_PER_HOST", 500)
AIOHTTP_KEEPALIVE_TIMEOUT: Final = get_env_int("AIOHTTP_KEEPALIVE_TIMEOUT", 15)
AIOHTTP_TTL_DNS_CACHE: Final = get_env_int("AIOHTTP_TTL_DNS_CACHE", 300)
# Raised above aiohttp's 8190 default so large response headers (e.g. GitHub CSP on OAuth
# token exchange) parse instead of raising LineTooLong. https://github.com/BerriAI/litellm/issues/48468
AIOHTTP_MAX_FIELD_SIZE: Final = get_env_int("AIOHTTP_MAX_FIELD_SIZE", 32768)
AIOHTTP_MAX_LINE_SIZE: Final = get_env_int("AIOHTTP_MAX_LINE_SIZE", 32768)
# TCP keep-alive (SO_KEEPALIVE) — opt-in. Required when running behind NAT/LBs
# whose idle timeout is shorter than provider response timeouts (e.g. AWS NAT
# Gateway: 350s vs OpenAI/Azure: 600s). Without this, the kernel sends nothing

View file

@ -29,6 +29,8 @@ from litellm.constants import (
AIOHTTP_CONNECTOR_LIMIT,
AIOHTTP_CONNECTOR_LIMIT_PER_HOST,
AIOHTTP_KEEPALIVE_TIMEOUT,
AIOHTTP_MAX_FIELD_SIZE,
AIOHTTP_MAX_LINE_SIZE,
AIOHTTP_NEEDS_CLEANUP_CLOSED,
AIOHTTP_SO_KEEPALIVE,
AIOHTTP_TCP_KEEPCNT,
@ -1351,6 +1353,8 @@ class AsyncHTTPHandler:
connector=TCPConnector(**transport_connector_kwargs),
cookie_jar=DummyCookieJar(),
trust_env=trust_env,
max_field_size=AIOHTTP_MAX_FIELD_SIZE,
max_line_size=AIOHTTP_MAX_LINE_SIZE,
)
# Use shared session if provided and valid

View file

@ -68,6 +68,8 @@ from litellm.constants import (
AIOHTTP_CONNECTOR_LIMIT,
AIOHTTP_CONNECTOR_LIMIT_PER_HOST,
AIOHTTP_KEEPALIVE_TIMEOUT,
AIOHTTP_MAX_FIELD_SIZE,
AIOHTTP_MAX_LINE_SIZE,
AIOHTTP_NEEDS_CLEANUP_CLOSED,
AIOHTTP_TTL_DNS_CACHE,
AUDIO_SPEECH_CHUNK_SIZE,
@ -1398,7 +1400,12 @@ async def initialize_shared_aiohttp_session() -> "ClientSession | None":
connector_kwargs["socket_factory"] = socket_factory
connector: Final = TCPConnector(**connector_kwargs)
session: Final = ClientSession(connector=connector, cookie_jar=DummyCookieJar())
session: Final = ClientSession(
connector=connector,
cookie_jar=DummyCookieJar(),
max_field_size=AIOHTTP_MAX_FIELD_SIZE,
max_line_size=AIOHTTP_MAX_LINE_SIZE,
)
verbose_proxy_logger.info(
"SESSION REUSE: Created shared aiohttp session for connection pooling (ID: %s, limit=%s, limit_per_host=%s)",

View file

@ -37,3 +37,24 @@ def test_create_aiohttp_transport_omits_enable_cleanup_closed_when_not_needed(
transport.get_valid_client_session()
assert "enable_cleanup_closed" not in mock_tcp_connector.call_args.kwargs
def test_create_aiohttp_transport_raises_header_size_limits():
from litellm.constants import AIOHTTP_MAX_FIELD_SIZE, AIOHTTP_MAX_LINE_SIZE
from litellm.llms.custom_httpx import http_handler as http_handler_module
connector_mock = MagicMock(name="connector")
session_mock = MagicMock(name="session")
with patch.object(http_handler_module, "TCPConnector", return_value=connector_mock):
with (
patch.object(http_handler_module, "ClientSession", return_value=session_mock) as mock_session,
patch.object(http_handler_module, "DummyCookieJar", return_value=MagicMock(name="cookie_jar")),
):
transport = http_handler_module.AsyncHTTPHandler.create_aiohttp_transport(shared_session=None)
transport.get_valid_client_session()
assert mock_session.call_args.kwargs["max_field_size"] == AIOHTTP_MAX_FIELD_SIZE
assert mock_session.call_args.kwargs["max_line_size"] == AIOHTTP_MAX_LINE_SIZE
assert AIOHTTP_MAX_FIELD_SIZE > 8190
assert AIOHTTP_MAX_LINE_SIZE > 8190

View file

@ -11,9 +11,7 @@ def test_initialize_shared_aiohttp_session_sets_enable_cleanup_closed_when_neede
session_mock = MagicMock(name="session")
monkeypatch.setattr(proxy_server_module, "AIOHTTP_NEEDS_CLEANUP_CLOSED", True)
with patch(
"aiohttp.TCPConnector", return_value=connector_mock
) as mock_tcp_connector:
with patch("aiohttp.TCPConnector", return_value=connector_mock) as mock_tcp_connector:
with patch("aiohttp.ClientSession", return_value=session_mock):
asyncio.run(proxy_server_module._initialize_shared_aiohttp_session())
@ -29,10 +27,24 @@ def test_initialize_shared_aiohttp_session_omits_enable_cleanup_closed_when_not_
session_mock = MagicMock(name="session")
monkeypatch.setattr(proxy_server_module, "AIOHTTP_NEEDS_CLEANUP_CLOSED", False)
with patch(
"aiohttp.TCPConnector", return_value=connector_mock
) as mock_tcp_connector:
with patch("aiohttp.TCPConnector", return_value=connector_mock) as mock_tcp_connector:
with patch("aiohttp.ClientSession", return_value=session_mock):
asyncio.run(proxy_server_module._initialize_shared_aiohttp_session())
assert "enable_cleanup_closed" not in mock_tcp_connector.call_args.kwargs
def test_initialize_shared_aiohttp_session_raises_header_size_limits():
from litellm.constants import AIOHTTP_MAX_FIELD_SIZE, AIOHTTP_MAX_LINE_SIZE
from litellm.proxy import proxy_server as proxy_server_module
connector_mock = MagicMock(name="connector")
session_mock = MagicMock(name="session")
with patch("aiohttp.TCPConnector", return_value=connector_mock):
with patch("aiohttp.ClientSession", return_value=session_mock) as mock_session:
asyncio.run(proxy_server_module._initialize_shared_aiohttp_session())
assert mock_session.call_args.kwargs["max_field_size"] == AIOHTTP_MAX_FIELD_SIZE
assert mock_session.call_args.kwargs["max_line_size"] == AIOHTTP_MAX_LINE_SIZE
assert AIOHTTP_MAX_FIELD_SIZE > 8190