From ce1f04a062aa3118270dfe77465732db7208c380 Mon Sep 17 00:00:00 2001 From: Gustavo Carneiro Date: Fri, 9 Aug 2019 19:01:17 +0100 Subject: [PATCH] Fix an issue with a failed websocket handshake leaving connection hanging #3380 --- CHANGES/3380.bugfix | 1 + aiohttp/_http_parser.pyx | 3 +++ aiohttp/http_parser.py | 7 +++++++ aiohttp/web_protocol.py | 6 ++++++ tests/test_web_websocket_functional.py | 28 +++++++++++++++++++++++++- 5 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 CHANGES/3380.bugfix diff --git a/CHANGES/3380.bugfix b/CHANGES/3380.bugfix new file mode 100644 index 00000000000..4c66ff0394b --- /dev/null +++ b/CHANGES/3380.bugfix @@ -0,0 +1 @@ +Fix failed websocket handshake leaving connection hanging. diff --git a/aiohttp/_http_parser.pyx b/aiohttp/_http_parser.pyx index b0ee4a18d38..1160c4120f6 100644 --- a/aiohttp/_http_parser.pyx +++ b/aiohttp/_http_parser.pyx @@ -533,6 +533,9 @@ cdef class HttpParser: else: return messages, False, b'' + def set_upgraded(self, val): + self._upgraded = val + cdef class HttpRequestParser(HttpParser): diff --git a/aiohttp/http_parser.py b/aiohttp/http_parser.py index b68a6cc24eb..f2881c3bf11 100644 --- a/aiohttp/http_parser.py +++ b/aiohttp/http_parser.py @@ -411,6 +411,13 @@ def parse_headers( return (headers, raw_headers, close_conn, encoding, upgrade, chunked) + def set_upgraded(self, val: bool) -> None: + """Set connection upgraded (to websocket) mode. + + :param bool val: new state. + """ + self._upgraded = val + class HttpRequestParser(HttpParser): """Read request status line. Exception .http_exceptions.BadStatusLine diff --git a/aiohttp/web_protocol.py b/aiohttp/web_protocol.py index 6b8ce04efa5..3aa09d56960 100644 --- a/aiohttp/web_protocol.py +++ b/aiohttp/web_protocol.py @@ -549,6 +549,12 @@ async def finish_response(self, can get exception information. Returns True if the client disconnects prematurely. """ + if self._request_parser is not None: + self._request_parser.set_upgraded(False) + self._upgrade = False + if self._message_tail: + self._request_parser.feed_data(self._message_tail) + self._message_tail = b'' try: prepare_meth = resp.prepare except AttributeError: diff --git a/tests/test_web_websocket_functional.py b/tests/test_web_websocket_functional.py index 8c3c30a9157..e8347722acd 100644 --- a/tests/test_web_websocket_functional.py +++ b/tests/test_web_websocket_functional.py @@ -5,7 +5,7 @@ import pytest import aiohttp -from aiohttp import web +from aiohttp import WSServerHandshakeError, web from aiohttp.http import WSMsgType @@ -827,3 +827,29 @@ async def handler(request): ws = await client.ws_connect('/') with pytest.raises(TypeError): await ws.receive_bytes() + + +async def test_bug3380(loop, aiohttp_client) -> None: + + async def handle_null(request): + return aiohttp.web.json_response({'err': None}) + + async def ws_handler(request): + return web.Response(status=401) + + app = web.Application() + app.router.add_route('GET', '/ws', ws_handler) + app.router.add_route('GET', '/api/null', handle_null) + + client = await aiohttp_client(app) + + resp = await client.get('/api/null') + assert (await resp.json()) == {'err': None} + resp.close() + + with pytest.raises(WSServerHandshakeError): + await client.ws_connect('/ws') + + resp = await client.get('/api/null', timeout=1) + assert (await resp.json()) == {'err': None} + resp.close()