{"record":{"id":"276506429232aa7c","repo":"aio-libs/aiohttp","slug":"no-connection-upgrade-hdr-headers-get-hdrs-conne","errorCode":null,"errorMessage":"No CONNECTION upgrade hdr: {headers.get(hdrs.CONNECTION)}","messagePattern":"No CONNECTION upgrade hdr: (.+?)","errorType":"http","errorClass":"HTTPBadRequest","httpStatus":400,"severity":"error","filePath":"aiohttp/web_ws.py","lineNumber":283,"sourceCode":"        assert payload_writer is not None\n        self._post_start(request, protocol, writer)\n        await payload_writer.drain()\n        return payload_writer\n\n    def _handshake(\n        self, request: BaseRequest\n    ) -> tuple[\"CIMultiDict[str]\", str | None, int, bool]:\n        headers = request.headers\n        if \"websocket\" != headers.get(hdrs.UPGRADE, \"\").lower().strip():\n            raise HTTPBadRequest(\n                text=(\n                    f\"No WebSocket UPGRADE hdr: {headers.get(hdrs.UPGRADE)}\\n Can \"\n                    '\"Upgrade\" only to \"WebSocket\".'\n                )\n            )\n\n        if not request._message.upgrade:\n            raise HTTPBadRequest(\n                text=f\"No CONNECTION upgrade hdr: {headers.get(hdrs.CONNECTION)}\"\n            )\n\n        # find common sub-protocol between client and server\n        protocol: str | None = None\n        if hdrs.SEC_WEBSOCKET_PROTOCOL in headers:\n            req_protocols = [\n                str(proto.strip())\n                for proto in headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(\",\")\n            ]\n\n            for proto in req_protocols:\n                if proto in self._protocols:\n                    protocol = proto\n                    break\n            else:\n                # No overlap found: Return no protocol as per spec\n                ws_logger.warning(","sourceCodeStart":265,"sourceCodeEnd":301,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_ws.py#L265-L301","documentation":"After the Upgrade: websocket header check passes, _handshake() verifies request._message.upgrade is True — i.e. that the connection was actually marked as an upgrade by the HTTP parser (driven by the Connection: upgrade header). If the parser did not set the upgrade flag, the request returns HTTP 400. This catches clients that send Upgrade: websocket but omit or mangle the Connection header.","triggerScenarios":"Client sends 'Upgrade: websocket' but no 'Connection: upgrade'; client sends 'Connection: keep-alive, upgrade' that the parser rejects; a proxy rewrites the Connection header; an old HTTP/1.0 client that doesn't support upgrade.","commonSituations":"Reverse proxies (nginx, HAProxy) that don't forward the Connection header or set it to 'close'; hand-rolled clients that forget the Connection header; HTTP/1.0 intermediaries that strip hop-by-hop headers.","solutions":["Configure the proxy to pass hop-by-hop headers: nginx proxy_set_header Connection $http_connection;.","On the client, ensure both 'Upgrade: websocket' and 'Connection: Upgrade' are sent (the aiohttp client does this automatically for ws_connect).","Guard the handler with ws.can_prepare(request) and return a 400-friendly response instead of crashing."],"exampleFix":"// nginx config — before\nlocation /ws { proxy_pass http://app; }\n// after\nlocation /ws {\n    proxy_pass http://app;\n    proxy_http_version 1.1;\n    proxy_set_header Upgrade $http_upgrade;\n    proxy_set_header Connection $http_connection;\n}","handlingStrategy":"validation","validationCode":"def is_valid_ws_request(request) -> bool:\n    h = request.headers\n    return (\n        h.get('Upgrade', '').lower().strip() == 'websocket'\n        and 'upgrade' in h.get('Connection', '').lower()\n    )\n\nif not is_valid_ws_request(request):\n    return web.Response(status=400, text='invalid WS handshake')","typeGuard":"def connection_header_allows_upgrade(request) -> bool:\n    return 'upgrade' in request.headers.get('Connection', '').lower()","tryCatchPattern":"ws = web.WebSocketResponse()\ntry:\n    await ws.prepare(request)\nexcept web.HTTPBadRequest as e:\n    log.warning('rejected WS handshake: %s', e.text)\n    return  # response already prepared by the exception","preventionTips":["Configure proxies to pass Connection and Upgrade headers through (proxy_http_version 1.1).","Use a real WS client (aiohttp ClientSession.ws_connect, browsers) that sends both headers.","Branch on ws.can_prepare() so a non-WS request gets a normal HTTP response."],"tags":["websocket","handshake","proxy","http-headers"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}