Skip to content

Commit

Permalink
Merge branch 'master' of github.com:KeepSafe/aiohttp
Browse files Browse the repository at this point in the history
  • Loading branch information
asvetlov committed Jul 31, 2016
2 parents 1a2bbfc + bc61b23 commit 1bfc02a
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 3 deletions.
56 changes: 56 additions & 0 deletions docs/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,59 @@ peer::
ws.send_str(answer)
finally:
await redis.unsubscribe('channel:1')


.. _aiohttp_faq_terminating_websockets:

How to programmatically close websocket server-side?
----------------------------------------------------


For example we have an application with two endpoints:


1. ``/echo`` a websocket echo server that authenticates the user somehow
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::

async def echo_handler(request):

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

try:
async for msg in ws:
# handle incoming messages
...

except asyncio.CancelledError:
print('websocket cancelled')
finally:
request.app['handlers'][user_id].remove(asyncio.Task.current_task())
await ws.close()
return ws

async def logout_handler(request):

user_id = authenticate_user(request)

for task in request.app['handlers'][user_id]:
task.cancel()

# return response
...

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)
aiohttp.web.run_app(app, host='localhost', port=8080)
11 changes: 8 additions & 3 deletions docs/web.rst
Original file line number Diff line number Diff line change
Expand Up @@ -536,9 +536,13 @@ with the peer::

return ws

Reading from the *WebSocket* (``await ws.receive()``) **must only** be
done inside the request handler *task*; however, writing
(``ws.send_str(...)``) to the *WebSocket* may be delegated to other tasks.
.. _aiohttp-web-websocket-read-same-task:

Reading from the *WebSocket* (``await ws.receive()``) and closing it (``await ws.close()``)
**must only** be done inside the request handler *task*; however, writing
(``ws.send_str(...)``) to the *WebSocket* and cancelling the handler task
may be delegated to other tasks. See also :ref:`FAQ section <aiohttp_faq_terminating_websockets>`.

*aiohttp.web* creates an implicit :class:`asyncio.Task` for handling every
incoming request.

Expand All @@ -556,6 +560,7 @@ incoming request.

Parallel reads from websocket are forbidden, there is no
possibility to call :meth:`aiohttp.web.WebSocketResponse.receive`
or :meth:`aiohttp.web.WebSocketResponse.close`
from two tasks.

See :ref:`FAQ section <aiohttp_faq_parallel_event_sources>` for
Expand Down

0 comments on commit 1bfc02a

Please sign in to comment.