Skip to content

Instantly share code, notes, and snippets.

@playpauseandstop
Last active August 3, 2021 18:22
Show Gist options
  • Save playpauseandstop/9252d2b29862174c474ad37adb13634e to your computer and use it in GitHub Desktop.
Save playpauseandstop/9252d2b29862174c474ad37adb13634e to your computer and use it in GitHub Desktop.
Setup aiohttp web app with Session Middleware to use Redis Storage and run under Gunicorn.

aiohttp-session-redis-demo

Setup aiohttp web app with Session Middleware to use Redis Storage and run under Gunicorn.

Problem

For using Redis Storage in aiohttp_session you need to create Redis connection pool, which obviously requires you to have awaitable call, so you need to put middleware setup of your web.Application inside the coroutine, like:

async def create_app():
    app = web.Application()

    pool = await create_pool(('127.0.0.1', 6379), db=0)
    aiohttp_session.setup(app, RedisStorage(pool))

    return app

This requires call create_app coroutine inside of asyncio event loop as:

loop = asyncio.get_event_loop()
app = loop.run_until_complete(create_app())

But when you serve aiohttp app with Gunicorn, you might don't want to have this additional loop (and it may not work at all).

Solution

In case if written above is the problem for you, you can easily fix it, by wrapping original session_middleware into your own session middleware factory. And as a middleware factory is a coroutine by design, you're able to make a Redis connection pool inside your factory for free.

def create_app():
    return web.Application(middlewares=[
        session_middleware_factory,
    ])

async def session_middleware_factory(app, handler):
    pool = await create_pool(('127.0.0.1', 6379), db=0)
    return await session_middleware(RedisStorage(pool))(app, handler)

app = create_app()

Whole code for the solution available in app.py.

Usage

$ make run

Note

This requires bootstrapper to be installed in the system with Python 3.5+

Questions, comments

I'm @playpausenstop on Twitter and link to my personal site and more contacts available in GitHub profile.

Cheers!

from types import MappingProxyType
from urllib.parse import urlparse
from aiohttp import web
from aiohttp_session import get_session, session_middleware
from aiohttp_session.redis_storage import RedisStorage
from aioredis import create_pool
def create_app(**options):
"""Create aiohttp app with custom middleware."""
app = web.Application(middlewares=[
session_middleware_factory,
])
app.router.add_route('GET', '/', view)
app.settings = MappingProxyType(dict(
REDIS_URL='redis://127.0.0.1:6379/0',
**options
))
return app
async def session_middleware_factory(app, handler):
"""Wrapper to Session Middleware factory.
Create Redis pool and setup session storage inside of coroutine.
"""
# Parse Redis URL
parsed = urlparse(app.settings['REDIS_URL'])
# Create Redis connection pool
pool = await create_pool(
(parsed.hostname, parsed.port),
db=int(parsed.path.lstrip('/')),
)
# Do the trick, by passing app & handler back to original session
# middleware factory. Do not forget to await on results here as original
# session middleware factory is also awaitable.
return await session_middleware(RedisStorage(pool))(app, handler)
async def view(request):
"""View function to work with session."""
session = await get_session(request)
session['counter'] = (session.get('counter') or 0) + 1
return web.json_response({
'counter': session['counter'],
})
#: Global application to serve with Gunicorn
app = create_app()
.PHONY: clean distclean install run
ENV ?= env
VENV = $(shell python -c "import sys; print(int(hasattr(sys, 'real_prefix')));")
ifeq ($(VENV),1)
GUNICORN = gunicorn
else
GUNICORN = $(ENV)/bin/gunicorn
endif
GUNICORN_WORKERS ?= $(shell python -c "import multiprocessing; print(multiprocessing.cpu_count() * 2 + 1);")
SERVER_HOST ?= 0.0.0.0
SERVER_PORT ?= 8211
clean:
find . -name "__pycache__" -type d -exec rm -rf {} 2> /dev/null +
distclean: clean
rm -rf $(ENV)/
install: .install
.install: requirements.txt
bootstrapper -e $(ENV)/
touch $@
run: .install
$(GUNICORN) -b $(SERVER_HOST):$(SERVER_PORT) -k aiohttp.worker.GunicornWebWorker -w $(GUNICORN_WORKERS) -t 30 --graceful-timeout=30 --reload $(GUNICORN_ARGS) app:app
aiohttp==0.21.6
aiohttp_session==0.5.0
aioredis==0.2.7
chardet==2.3.0
gunicorn==19.6.0
hiredis==0.2.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment