aiohttp icon indicating copy to clipboard operation
aiohttp copied to clipboard

Cannot properly close ws connection in one-sided way.

Open truskovskiyk opened this issue 6 years ago • 2 comments

Long story short

Cannot properly close ws connection from client side. Full description as code bellow

Expected behaviour

Good patter to close ws connection from the client side in one-sided way.

Actual behaviour

Need to use protected attribute to close ws connection

Steps to reproduce

server.py

import asyncio
import aiohttp.web


async def websocket_handler(request):
    print('Websocket connection starting')
    ws = aiohttp.web.WebSocketResponse()
    await ws.prepare(request)
    print('Websocket connection ready')
    await ws.prepare(request)
    while True:
        if request.transport.is_closing():
            break
        await ws.send_json({'mesasge': 'test'})
        await asyncio.sleep(1)

    return ws


def main():
    loop = asyncio.get_event_loop()
    app = aiohttp.web.Application(loop=loop)
    app.router.add_route('GET', '/ws', websocket_handler)
    aiohttp.web.run_app(app, host='0.0.0.0', port=8080)


if __name__ == '__main__':
    main()

client.py

import asyncio
import aiohttp

URL = f'http://0.0.0.0:8080/ws'


async def main():
    session = aiohttp.ClientSession()
    count = 0
    max_query = 2
    async with session.ws_connect(URL) as ws:
        while True:
            msg = await ws.receive_json()
            print(f"get from the server {msg}")
            count += 1
            if count > max_query:
                proto = ws._writer.protocol
                proto.transport.close()
                break


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

python server.py
python client.py

Your environment

macOS Mojave 10.14 python3.6.1 aiohttp==3.5.0a1

truskovskiyk avatar Dec 09 '18 12:12 truskovskiyk

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/523 (Not all connections are closed (pending: 0)), https://github.com/aio-libs/aiohttp/issues/253 (Connection not closed when request is cancelled), and https://github.com/aio-libs/aiohttp/issues/15 (No way to close a response on a timeout).

aio-libs-bot avatar Dec 09 '18 12:12 aio-libs-bot

You need to run a separate task to listen the WebSocket and handle the closing handshake.

server.py:

import asyncio
import aiohttp.web


async def websocket_handler(request):
    print('Websocket connection starting')
    ws = aiohttp.web.WebSocketResponse(heartbeat=1)
    await ws.prepare(request)
    print('Websocket connection ready')

    async def _ws_listener() -> None:
        async for msg in ws:  # handles ping-pong and closing internally
            # What to do with unexpected messages?
            # It depends on you.
            pass

    reader = asyncio.create_task(_ws_listener())
    while not ws.closed:
        await ws.send_json({'mesasge': 'test'})
        await asyncio.sleep(1)
    await reader

    return ws


def main():
    app = aiohttp.web.Application()
    app.router.add_route('GET', '/ws', websocket_handler)
    aiohttp.web.run_app(app, host='0.0.0.0', port=8080)


if __name__ == '__main__':
    main()

client.py:

import asyncio
import aiohttp

URL = f'http://0.0.0.0:8080/ws'


async def main():
  session = aiohttp.ClientSession()
  async with session:
    count = 0
    max_query = 2
    async with session.ws_connect(URL) as ws:
        while True:
            msg = await ws.receive_json()
            print(f"get from the server {msg}")
            count += 1
            if count > max_query:
                break


if __name__ == '__main__':
    asyncio.run(main())

serhiy-storchaka avatar Jul 20 '22 09:07 serhiy-storchaka