aio-libs / aiohttp

Asynchronous HTTP client/server framework for asyncio and Python
https://docs.aiohttp.org
Other
15.13k stars 2.02k forks source link

ClientWebSocketResponse fails with TypeError if server closed the connection #3645

Open dangusev opened 5 years ago

dangusev commented 5 years ago

Long story short

Hi,

I have an application serving websockets and I test it using the aiohttp websockets as a client. When I call .close() on the server-side, the client-side fails with TypeError: Received message 8:4404 is not str. I want to determine how to handle these situations correctly, e.g. when client receives WSMsgType.CLOSE message instead of e.g WSMsgType.TEXT?

Expected behaviour

The ideal case would be to have an exception from e.g ClientError with error_code and message within.

Please, tell me if this way is appropriate or not and I'll try to make a PR. It seems not to break backward incompatibility if the exception will be raised within receive_str|bytes|json methods, the receive code could stay the same. Thanks!

Actual behaviour

Websocket client fails with TypeError: Received message **** is not str It happens because ClientWebSocketResponse.receive() return msg in case of CLOSE https://github.com/aio-libs/aiohttp/blob/master/aiohttp/client_ws.py#L255 and then ClientWebSocketResponse.receive_str() just raises the TypeError https://github.com/aio-libs/aiohttp/blob/master/aiohttp/client_ws.py#L272

Currently I have to write my own wrapper around receive and handle websocket's closing manually.

Steps to reproduce

1) Run websocket server with endpoint that closes the connection write after accepting it. I use Sanic, but behaviour with aiohttp server should be the same. 2) Connect to websocket using ws = await ClientSession.ws_connect() 3) Call await ws.receive_json() or ws.receive_str() 4) See the TypeError in logs

Your environment

python 3.7, aiohttp 3.5.4, OS X 10.14.3

aio-libs-bot commented 5 years ago

GitMate.io thinks the contributor most likely able to help you is @asvetlov.

Possibly related issues are https://github.com/aio-libs/aiohttp/issues/1814 (Close websocket connection when pong not received), https://github.com/aio-libs/aiohttp/issues/3052 (SSL with closed connections), https://github.com/aio-libs/aiohttp/issues/370 (Server closes connection before sending data), https://github.com/aio-libs/aiohttp/issues/2595 (Server handler halts after client connection closed), and https://github.com/aio-libs/aiohttp/issues/1768 (websocket connection is closing.).

wenleix commented 1 year ago

I ran into similar issue. This makes receive_json / receive_str less useful in applications if

As a result, I also end up often needs to write my own version of receive_json/str, e.g.

async def my_receive_str(response) -> Optional[str]:
    msg = await response.receive()  
    if msg == aiohttp.http.WS_CLOSED_MESSAGE or msg == aiohttp.http.WS_CLOSING_MESSAGE: 
        await response.close()
        return None # or throw exceptions other than TypeError

    if msg.type != aiohttp.WSMsgType.TEXT:  
        raise TypeError(f"Received message {msg.type}:{msg.data!r} is not str")

   return cast(str, msg.data)

Throw an different type of error (like WSConnectionClosedException) is one way. Another thought is to return None upon closed connection.