Skip to content

Commit

Permalink
Update server-side websocket close FAQ
Browse files Browse the repository at this point in the history
Due to improvements done in aio-libs#754, websocket `close()`
can now be called from a separate task.
  • Loading branch information
mpaolini committed Aug 16, 2017
1 parent b5f8a42 commit a06398b
Showing 1 changed file with 15 additions and 21 deletions.
36 changes: 15 additions & 21 deletions docs/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -162,49 +162,43 @@ For example we have an application with two endpoints:
2. ``/logout_user`` that when invoked needs to close all open
websockets for that user.

Keep in mind that you can only ``.close()`` a websocket from inside
the handler task, and since the handler task is busy reading from the
websocket, it can't react to other events.

One simple solution is keeping a shared registry of websocket handler
tasks for a user in the :class:`aiohttp.web.Application` instance and
``cancel()`` them in ``/logout_user`` handler::
One simple solution is keeping a shared registry of websocket responses
for a user in the :class:`aiohttp.web.Application` instance and
call :meth:`aiohttp.web.WebSocketResponse.close` on all of them in ``/logout_user`` handler::

async def echo_handler(request):

ws = web.WebSocketResponse()
ws = aiohttp.web.WebSocketResponse()
user_id = authenticate_user(request)
await ws.prepare(request)
request.app['websockets'][user_id].add(asyncio.Task.current_task())

request.app['websockets'][user_id].append(ws)
try:
async for msg in ws:
# handle incoming messages
...

except asyncio.CancelledError:
print('websocket cancelled')
ws.send_str(msg.data)
finally:
request.app['websockets'][user_id].remove(asyncio.Task.current_task())
await ws.close()
request.app['websockets'][user_id].remove(ws)

return ws


async def logout_handler(request):

user_id = authenticate_user(request)

for task in request.app['websockets'][user_id]:
task.cancel()
ws_closers = [ws.close() for ws in request.app['websockets'][user_id] if not ws.closed]

# return response
...
# Watch out, this will keep us from returing the response until all are closed
ws_closers and await asyncio.wait(ws_closers)

return aiohttp.web.Response(text='OK')

def main():
loop = asyncio.get_event_loop()
app = aiohttp.web.Application(loop=loop)
app.router.add_route('GET', '/echo', echo_handler)
app.router.add_route('POST', '/logout', logout_handler)
app['websockets'] = defaultdict(set)
app['websockets'] = defaultdict(deque)
aiohttp.web.run_app(app, host='localhost', port=8080)


Expand Down

0 comments on commit a06398b

Please sign in to comment.