I'm setting up an aiohttp
server using aiohttp_session
to store data into an EncryptedCookieStorage
. I use it to store a 7-days valid token, along with the expiration date and a refresh token.
I want, no matter which endpoint the client is accessing, to check if the token (stored in the session) needs some refreshment. The choice of a middleware was pretty obvious.
The problem is, when I call await aiohttp_session.get_session(request)
, I'm getting a nice RuntimeError
asking me to setup the aiohttp_session
middleware to the aiohttp.web.Application
. My guess is that my custom middleware was called before the one handling the session loading, thus the session is not accessible yet. I've searched for some "priority" system regarding middlewares, but haven't found anything.
My server is set up in a main.py
file like:
def main():
app = web.Application()
middleware.setup(app)
session_key = base64.urlsafe_b64decode(fernet.Fernet.generate_key())
aiohttp_session.setup(app, EncryptedCookieStorage(session_key))
# I have tried swapping the two setup functions
web.run_app(app)
if __name__ == '__main__':
main()
Where the middleware.setup()
is in a separate package, in the __init__.py
:
# For each python file in the package, add it's middleware function to the app middlewares
def setup(app):
for filename in listdir('middleware'):
if filename[-2:] == 'py' and filename[:2] != '__':
module = __import__('rpdashboard.middleware.' + filename[:-3], fromlist=['middleware'])
app.middlewares.append(module.middleware)
And finally, the middleware I want to get the session in is:
@web.middleware
async def refresh_token_middleware(request, handler):
session = await get_session(request)
if session.get('token'):
pass # To be implemented ...
return await handler(request)
middleware = refresh_token_middleware
The execution issues here:
# From aiohttp_session
async def get_session(request):
session = request.get(SESSION_KEY)
if session is None:
storage = request.get(STORAGE_KEY)
if storage is None:
# This is raised
raise RuntimeError(
"Install aiohttp_session middleware "
"in your aiohttp.web.Application")
As I was saying earlier, it seems like the session is not meant to be accessed in a middleware, and isn't loaded yet. So how would I prevent my custom middleware to run before the session loading one? Or maybe simply run manually the aiohttp_session
middleware myself? Is it even possible?
Yes, middleware components added to the app in the right order can access the session storage set by the session middleware.
The aiohttp
documentation covers the priority order for middleware components in their Middlewares section:
Internally, a single request handler is constructed by applying the middleware chain to the original handler in reverse order, and is called by the
RequestHandler
as a regular handler.
Further down, they use an example to demonstrate what this means. In summary, they use two middleware components that report their entry and exit, and add them to the app.middlewares
list in this order:
... middlewares=[middleware1, middleware2]
This ordering produces the following output:
Middleware 1 called Middleware 2 called Handler function called Middleware 2 finished Middleware 1 finished
So an incoming request is passed along the different middleware in the same order they are added to the app.middlewares
list.
Next, aiohttp_session
also documents how they add their session middleware, in the API entry for aiohttp_session.setup()
:
The function is shortcut for:
app.middlewares.append(session_middleware(storage))
So their middleware component is added to the end of the list. Per above that means that anything that requires access to the session must come after this middleware component.
All that the session middleware does is add the storage to the request under the aiohttp_session.STORAGE_KEY
key; this makes the sessions available to any further middleware components that follow it. Your middleware components do not need to do anything special other than be added after the session middleware and leave the storage object added to the request in place. The request object is designed to share data between components this way.
Your code puts all your middleware components before the session middleware component:
middleware.setup(app)
# ...
aiohttp_session.setup(app, EncryptedCookieStorage(session_key))
This gives you an ordering of [..., refresh_token_middleware, ..., session_middleware]
and your middleware can’t access any session information.
So you have to swap the order; call aiohttp_session.setup()
first, and only then add your own components:
aiohttp_session.setup(app, EncryptedCookieStorage(session_key))
middleware.setup(app)
If you still have issues accessing the session storage then that means one of the intervening middleware components is removing the session storage information again.
You could use the following middleware factory at various locations to report on the session storage being present to help you debug this:
from aiohttp import web
from aiohttp_session import STORAGE_KEY
COUNTER_KEY = "__debug_session_storage_counter__"
_label = {
False: "\x1b[31;1mMISSING\x1b[0m",
True: "\x1b[32;1mPRESENT\x1b[0m",
}
def debug_session_storage(app):
pre = nxt = ""
if app.middlewares:
previous = app.middlewares[-1]
name = getattr(previous, "__qualname__", repr(previous))
pre = f" {name} ->"
nxt = f" {name} <-"
@web.middleware
async def middleware(request, handler):
counter = request.get(COUNTER_KEY, -1) + 1
request[COUNTER_KEY] = counter
found = STORAGE_KEY in request
indent = " " * counter
print(f"{indent}-{pre} probe#{counter} - storage: {_label[found]}")
try:
return await handler(request)
finally:
print(f"{indent}-{nxt} probe#{counter} - done")
app.middlewares.append(middleware)
If you insert this between every piece of middleware you add you should be able to figure out if and where the session storage is being lost:
def setup(app):
# start with a probe
debug_session_storage(app)
for filename in listdir('middleware'):
if filename[-2:] == 'py' and filename[:2] != '__':
module = __import__('rpdashboard.middleware.' + filename[:-3], fromlist=['middleware'])
app.middlewares.append(module.middleware)
# Add debug probe after every component
debug_session_storage(app)
This should tell you