Skip to content

Commit

Permalink
Fix ws close faq (aio-libs#2208)
Browse files Browse the repository at this point in the history
* Update server-side websocket close FAQ

Due to improvements done in aio-libs#754, websocket `close()`
can now be called from a separate task.

* Minor doc fix for websocket receive

* Fix web module usage and use .gather

* Use set() instead of dict in FAQ
  • Loading branch information
mpaolini authored and asvetlov committed Aug 24, 2017
1 parent 4ffbd4c commit d951059
Show file tree
Hide file tree
Showing 2 changed files with 17 additions and 24 deletions.
3 changes: 1 addition & 2 deletions docs/client_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1243,8 +1243,7 @@ manually.

It process *ping-pong game* and performs *closing handshake* internally.

:return: :class:`~aiohttp.WSMessage`, `tp` is a type from
:class:`~aiohttp.WSMsgType` enumeration.
:return: :class:`~aiohttp.WSMessage`

.. coroutinemethod:: receive_str()

Expand Down
38 changes: 16 additions & 22 deletions docs/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -162,50 +162,44 @@ 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()
user_id = authenticate_user(request)
await ws.prepare(request)
request.app['websockets'][user_id].add(asyncio.Task.current_task())

request.app['websockets'][user_id].add(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]

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

return web.Response(text='OK')

# return response
...

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


How to make request from a specific IP address?
Expand Down

0 comments on commit d951059

Please sign in to comment.