Skip to main content

session-managed websockets for aiohttp

Project description

https://travis-ci.org/dfee/aiohttp_session_ws.svg?branch=master https://coveralls.io/repos/github/dfee/aiohttp_session_ws/badge.svg?branch=master

Simply put: associate your websockets with a user’s session, and close those connections when you see fit.

For example, let’s say you’re using aiohttp_security and a user chooses to log in or log out. Using aiohttp_session_ws you can disconnect the open websocket subscriptions associated with their session, and force them to re-connect and re-authorize thier websocket subscriptions.

demo/demo.gif

Basic Example

The pieces of code in this example are taken from the demo directory of this repository.

async def handle_root(request):
    return web.Response(text='Hello world', content_type="text/html")

async def handle_reset(request):
    session_ws_id = await get_session_ws_id(request)
    response = web.Response(
        text=f"Reset called on session {session_ws_id}!",
        content_type="text/plain",
    )
    await schedule_close_all_session_ws(request, response)
    await new_session_ws_id(request)
    return response

async def handle_websocket(request):
    async with session_ws(request) as wsr:
        connected_at = datetime.now()
        session_ws_id = await get_session_ws_id(request)
        while True:
            await wsr.send_str(
                f"Websocket associated with session [{session_ws_id}] "
                f"connected for {(datetime.now() - connected_at).seconds}"
            )
            await asyncio.sleep(1)
        return wsr

def make_app():
    app = web.Application(
        middlewares=[
            aiohttp_session.session_middleware(
                aiohttp_session.SimpleCookieStorage()
            ),
            session_ws_middleware,
        ]
    )
    app.router.add_get("/", handle_root)
    app.router.add_get("/reset", handle_reset)
    app.router.add_get("/ws", handle_websocket)

    setup_session_websockets(app, SessionWSRegistry())
    return app

Use the code from the demo folder, which includes a simple template to interact with the websocket in your web-browser.

Narrative API

This package is designed to be straightforward and easy to use. This lightweight documentation doesn’t attempt to replace the need to read the code, so you’re encouraged to go do exactly that.

There are a few moving pieces, but if (and when) you need to do something more complex, you can subclass away.

SessionWSRegistry

This is the core of aiohttp_session_ws.

It’s construction is noteworthy:

SessionWSRegistry(self, *, id_factory, session_key)

id_factory generates a session-wide id that associates the websockets. The default id_factory returns a UUID4, but you can supply your own callable (async callables are supported, too). the function signature of id_factory is:

id_factory(request: aiohttp.web.Request) -> typing.Hashable

So pretty much, return something that can be the key in a dictionary (strings, integers, etc.).

session_key is the name of the key in the session that maps to the session-wide websocket identifier. By default it’s a sensible aiohttp_session_ws_id.

Helpers

You won’t need to interact with SessionWSRegistry directly after you’ve created it, but know that it’s available in your aiohttp.web.Application (access it like this: app['aiohttp_session_ws_registry']).

The friendly global manipulators of this object are:

  • get_session_ws_id(request)

  • new_session_ws_id(request)

  • delete_session_ws_id(request)

  • ensure_session_ws_id(request)

  • schedule_close_all_session_ws(request, response)

These methods are importable directly from aiohttp_session_ws.

Notice that schedule_close_all_session_ws takes a response object. This allows us to end the keep-alive status of the response (via aiohttp.web.Response.force_close). This means that as soon as your user has finished receiveing the response, their outstanding websockets will close.

This also means that if you have users with re-connecting websockets, you should probably follow this pattern:

async def handle_logout(request):
    response = web.HTTPFound('/')
    await schedule_close_all_session_ws(request, response)
    await aiohttp_session.new_session(request)
    await new_session_ws_id(request)
    return response

session_ws

To track the websockets, you’ll use the async context manager session_ws. This context manager upgrades the request, and provides its aiothttp.web.WebSocketResponse counterpart. Use if like this:

async def handle_websocket(request):
    async with session_ws(request) as wsr:
        async for msg in wsr:
            await wsr.send_str(f'Heard: {ws.data}')
        return wsr

That’s it. Pretty simple, right? If you’d like to provide the aiohttp.web.WebSocketResponse with initialization options (for example, the supported websocket protocols), pass those along to session_ws as named arguments.

async def handle_websocket(request):
    async with session_ws(request, protocols=('graphql-ws',)) as wsr:
        async for msg in wsr:
            await wsr.send_str(f'Heard: {ws.data}')
        return wsr

As mentioned in the Notes below, it’s important that your users have a session_ws id prior to attempting a websocket connection (hint: Safari).

Use the session_ws_middleware to automatically add the key to your sessions. It should be inside the call-stack of aiohttp_session.session_middleware:

web.Application(
    middlewares=[
        aiohttp_session.session_middleware(
            aiohttp_session.SimpleCookieStorage()
        ),
        session_ws_middleware,
    ]
)

Finally, to set all of this up, you’ll want to use the setup method (feel encourged to import it as setup_session_ws).

Basic usage looks like this:

web.Application(
    middlewares=[
        aiohttp_session.session_middleware(
            aiohttp_session.SimpleCookieStorage()
        ),
        session_ws_middleware,
    ]
)
setup(app, SessionWSRegistry())  # <------
# etc...
return app

Notes

While session_ws generates an aiohttp_session_ws_id upon connect (if it’s not present), some browsers don’t respect Set-Cookie on a websocket upgrade (e.g. Safari).

Therefore it’s best if you ensure that an aiohttp_session_ws_id is present in the users session prior to attempting a websocket connection (if using aiohttp_session.SimpleCookieStorage or aiohttp_session.EncryptedCookieStorage).

If you’re using something more advanced that stores a reference to the session in the session cookie, and stores the actual value server-side (like aiohttp_session.RedisStorage), then it’s not important when aiohttp_session_ws_id is set on the cookie, but it is still important that the user has a session cookie prior to a connection attempt.

If you want to put the session-ws-id (usually aiohttp_session_ws_id) somewhere else in the session, or derive it from the request, you can. Simply subclass SessionWSRegistry and revise the get_id, set_id, and delete_id methods.

If you have a cluster of webservers, you’ll need to subclass SessionWSRegistry and revise the register and unregister functions so listen on a message broker (for example, using aioredis and its pubsub feature).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aiohttp_session_ws-1.1.1.tar.gz (7.2 kB view details)

Uploaded Source

Built Distribution

aiohttp_session_ws-1.1.1-py3-none-any.whl (13.2 kB view details)

Uploaded Python 3

File details

Details for the file aiohttp_session_ws-1.1.1.tar.gz.

File metadata

  • Download URL: aiohttp_session_ws-1.1.1.tar.gz
  • Upload date:
  • Size: 7.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/0.11.5 CPython/3.7.0 Darwin/17.7.0

File hashes

Hashes for aiohttp_session_ws-1.1.1.tar.gz
Algorithm Hash digest
SHA256 9c697a816205e46fcabca85d32712d2ed6092aad84ef7d3b8ccd12588d6c49a5
MD5 92a512cbb05dd1fd4e6d3b21f99d7a33
BLAKE2b-256 21d4070169c88a7d5b6cd621242c7c255caa59546d0ad904f3527027d3281275

See more details on using hashes here.

File details

Details for the file aiohttp_session_ws-1.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for aiohttp_session_ws-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f3cb6ae9dc1c0c87e6426f922123f62b9c97b02bd727a64d1db8c70d28c0f8e8
MD5 89469fc8228802d5622cdc5b960985df
BLAKE2b-256 a6a370857e7b59fa7fbb85e2f3e2e64aa951d192f5aab597845203376856af24

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page