{"record":{"id":"e467c089f5f61da8","repo":"aio-libs/aiohttp","slug":"invalid-upgrade-header","errorCode":null,"errorMessage":"Invalid upgrade header","messagePattern":"Invalid upgrade header","errorType":"http","errorClass":"WSServerHandshakeError","httpStatus":null,"severity":"error","filePath":"aiohttp/client.py","lineNumber":1110,"sourceCode":"            proxy=proxy,\n            ssl=ssl,\n            server_hostname=server_hostname,\n            proxy_headers=proxy_headers,\n        )\n\n        try:\n            # check handshake\n            if resp.status != 101:\n                raise WSServerHandshakeError(\n                    resp.request_info,\n                    resp.history,\n                    message=\"Invalid response status\",\n                    status=resp.status,\n                    headers=resp.headers,\n                )\n\n            if resp.headers.get(hdrs.UPGRADE, \"\").lower() != \"websocket\":\n                raise WSServerHandshakeError(\n                    resp.request_info,\n                    resp.history,\n                    message=\"Invalid upgrade header\",\n                    status=resp.status,\n                    headers=resp.headers,\n                )\n\n            if not resp._upgraded:\n                raise WSServerHandshakeError(\n                    resp.request_info,\n                    resp.history,\n                    message=\"Invalid connection header\",\n                    status=resp.status,\n                    headers=resp.headers,\n                )\n\n            # key calculation\n            r_key = resp.headers.get(hdrs.SEC_WEBSOCKET_ACCEPT, \"\")","sourceCodeStart":1092,"sourceCodeEnd":1128,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/client.py#L1092-L1128","documentation":"Raised as WSServerHandshakeError when the response UPGRADE header (case-insensitive) is not 'websocket'. The server returned 101 but did not confirm the WebSocket upgrade, so the protocol is not actually established.","triggerScenarios":"Server replies 101 with Upgrade: h2c or Upgrade: HTTP/2 or omits/misspells the UPGRADE header. resp.headers.get('Upgrade','').lower() != 'websocket'.","commonSituations":"Reverse proxy or framework that auto-responds 101 for any Upgrade but writes a different token. Custom server returning Upgrade: WebSocket with extra tokens or different casing/value. Bug in a hand-rolled WS server.","solutions":["Inspect resp.headers in the caught WSServerHandshakeError to see what was sent.","Fix the server/proxy to respond with exactly 'Upgrade: websocket'.","If you cannot change the server, do not use ws_connect against it; speak raw HTTP/1.1 or pick a compliant endpoint."],"exampleFix":"// before\n# server returns 'Upgrade: h2c' on a 101\nawait session.ws_connect('wss://x')  # raises Invalid upgrade header\n// after\n# fix the server to send 'Upgrade: websocket'","handlingStrategy":"try-catch","validationCode":"async def preflight_upgrade(session, url):\n    # Verify the server's handshake behavior before relying on it.\n    async with session.get(url) as r:\n        return r.headers.get('Upgrade', '').lower()","typeGuard":"def upgrade_is_websocket(header_value: str) -> bool:\n    return header_value.strip().lower() == 'websocket'","tryCatchPattern":"from aiohttp import WSServerHandshakeError\n\ntry:\n    ws = await session.ws_connect(url)\nexcept WSServerHandshakeError as e:\n    if e.message == 'Invalid upgrade header':\n        # server/proxy is non-RFC-compliant; cannot use ws_connect\n        raise\n    raise","preventionTips":["Ensure the server emits exactly 'Upgrade: websocket' on 101.","Don't let intermediaries rewrite the Upgrade header.","Validate handshakes in staging before production rollout."],"tags":["websocket","handshake","headers"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}