Giter Club home page Giter Club logo

aiohttp_session_ws's Introduction

aiohttp_session_ws

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.