Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot properly close ws connection in one-sided way. #3443

Closed
truskovskiyk opened this issue Dec 9, 2018 · 3 comments
Closed

Cannot properly close ws connection in one-sided way. #3443

truskovskiyk opened this issue Dec 9, 2018 · 3 comments
Labels

Comments

@truskovskiyk
Copy link

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

@aio-libs-bot
Copy link

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

Possibly related issues are #1814 (Close websocket connection when pong not received), #3052 (SSL with closed connections), #523 (Not all connections are closed (pending: 0)), #253 (Connection not closed when request is cancelled), and #15 (No way to close a response on a timeout).

@serhiy-storchaka
Copy link
Contributor

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())

@Dreamsorcerer
Copy link
Member

I think this is by design and just following the websocket standard:

The underlying TCP connection, in most normal cases, SHOULD be closed first by the server
https://www.rfc-editor.org/rfc/rfc6455#section-7.1.1

i.e. As part of your websocket API, there should be a close command you can send to the server, at which point the server can initiate closing of the connection.

@Dreamsorcerer Dreamsorcerer closed this as not planned Won't fix, can't repro, duplicate, stale Aug 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants