{"record":{"id":"ebd2ee51dcda576b","repo":"aio-libs/aiohttp","slug":"no-websocket-upgrade-hdr-headers-get-hdrs-upgrad","errorCode":null,"errorMessage":"No WebSocket UPGRADE hdr: {headers.get(hdrs.UPGRADE)}\\n Can \"Upgrade\" only to \"WebSocket\".","messagePattern":"No WebSocket UPGRADE hdr: (.+?)\\\\n Can \"Upgrade\" only to \"WebSocket\"\\.","errorType":"http","errorClass":"HTTPBadRequest","httpStatus":400,"severity":"error","filePath":"aiohttp/web_ws.py","lineNumber":275,"sourceCode":"\n    async def prepare(self, request: BaseRequest) -> AbstractStreamWriter:\n        # make pre-check to don't hide it by do_handshake() exceptions\n        if self._payload_writer is not None:\n            return self._payload_writer\n\n        protocol, writer = self._pre_start(request)\n        payload_writer = await super().prepare(request)\n        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            ]","sourceCodeStart":257,"sourceCodeEnd":293,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_ws.py#L257-L293","documentation":"During the WebSocket handshake, _handshake() verifies the HTTP UPGRADE header equals 'websocket' (case-insensitive, trimmed). If a non-WebSocket request reaches a WebSocketResponse.prepare() (or the route handler invokes the WS handshake on a regular HTTP request), aiohttp returns HTTP 400 with this message. The handshake is invoked from prepare() via _pre_start, so the error surfaces as an HTTPException that aiohttp renders as a 400 response.","triggerScenarios":"A browser issues a normal GET to a path whose handler unconditionally calls await ws.prepare(request); a reverse proxy strips the Upgrade header; the client used a different upgrade token (e.g. 'h2c' for HTTP/2 upgrade); a curl request without -H 'Upgrade: websocket'.","commonSituations":"Sharing one route between WS and HTTP without checking; proxies/load balancers (nginx) not forwarding Upgrade; clients connecting with the wrong library or a plain HTTP request; WebSocket endpoint hit by a health-check probe.","solutions":["Call ws.can_prepare(request) first and branch: if not ws.can_prepare(request).ok: return web.Response(...).","Ensure proxies forward Upgrade/Connection ('Connection' header passthrough) — e.g. nginx: proxy_set_header Upgrade $http_upgrade;.","Point the client at ws:// or wss:// URLs so it sends the correct Upgrade header."],"exampleFix":"// before\nws = web.WebSocketResponse()\nawait ws.prepare(request)  # 400 if not a WS upgrade\n// after\nws = web.WebSocketResponse()\nif not ws.can_prepare(request).ok:\n    return web.Response(status=400, text='WebSocket connection required')\nawait ws.prepare(request)","handlingStrategy":"validation","validationCode":"async def ws_handler(request):\n    ws = web.WebSocketResponse()\n    if not ws.can_prepare(request).ok:\n        return web.Response(status=400, text='WebSocket upgrade required')\n    await ws.prepare(request)\n    # ... ws loop ...","typeGuard":"def is_websocket_upgrade(request) -> bool:\n    return request.headers.get('Upgrade', '').lower().strip() == 'websocket'","tryCatchPattern":"ws = web.WebSocketResponse()\ntry:\n    await ws.prepare(request)\nexcept web.HTTPBadRequest as e:\n    # client didn't send a valid WS upgrade\n    return web.Response(status=400, text='upgrade required')","preventionTips":["Call ws.can_prepare(request) and branch before ws.prepare().","Ensure reverse proxies forward the Upgrade header.","Point clients at ws:// / wss:// URLs."],"tags":["websocket","handshake","http-upgrade","proxy"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}