【问题标题】:Is it possible to access a session (from aiohttp_session) within a middleware?是否可以在中间件中访问会话(来自 aiohttp_session)?
【发布时间】:2019-06-06 20:42:04
【问题描述】:

我正在设置一个aiohttp 服务器,使用aiohttp_session 将数据存储到EncryptedCookieStorage。我用它来存储一个 7 天有效的令牌,以及到期日期和一个刷新令牌。 无论客户端访问哪个端点,我都希望检查令牌(存储在会话中)是否需要一些更新。中间件的选择非常明显。

问题是,当我打电话给await aiohttp_session.get_session(request) 时,我收到了一个很好的RuntimeError,要求我将aiohttp_session 中间件设置为aiohttp.web.Application。我的猜测是,我的自定义中间件在处理会话加载之前被称为 ,因此该会话尚不可访问。我搜索了一些关于中间件的“优先”系统,但没有找到任何东西。

我的服务器设置在 main.py 文件中,例如:

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

middleware.setup() 位于单独的包中,在 __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)

最后,我想要获取会话的中间件是:

@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

这里的执行问题:

# 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")

正如我之前所说,似乎会话不打算在中间件中访问,并且尚未加载。那么如何防止我的自定义中间件在会话加载之前运行呢?或者也许只是自己手动运行aiohttp_session 中间件?有没有可能?

【问题讨论】:

  • 您是否尝试在中间件之前设置会话?

标签: python middleware aiohttp


【解决方案1】:

是的,添加到应用中的中间件组件以正确的顺序可以访问会话中间件设置的会话存储。

aiohttp 文档在其Middlewares section 中介绍了中间件组件的优先顺序:

在内部,通过将中间件链以相反的顺序应用于原始处理程序来构造单个请求处理程序,并由RequestHandler 作为常规处理程序调用 .

接下来,他们用一个例子来说明这意味着什么。综上所述,他们使用了两个中间件组件,分别报告其进入和退出,并按此顺序将它们添加到app.middlewares列表中:

... middlewares=[middleware1, middleware2]

此排序产生以下输出:

Middleware 1 called
Middleware 2 called
Handler function called
Middleware 2 finished
Middleware 1 finished

因此,传入的请求通过不同的中间件按照它们添加到app.middlewares列表的相同顺序

接下来,aiohttp_session 还在API entry for aiohttp_session.setup() 中记录了他们如何添加会话中间件:

函数的快捷方式:

app.middlewares.append(session_middleware(storage))

所以他们的中间件组件被添加到列表的末尾。如上所述,这意味着任何需要访问会话的东西都必须在这个中间件组件之后。

会话中间件所做的只是将存储添加到aiohttp_session.STORAGE_KEY 键下的请求中;这使得会话可用于任何后续的中间件组件。您的中间件组件不需要做任何特殊的事情,除了在会话中间件之后添加并将存储对象添加到请求中。请求对象为designed to share data between components this way

您的代码将所有中间件组件放在会话中间件组件之前:

middleware.setup(app)
# ...
aiohttp_session.setup(app, EncryptedCookieStorage(session_key))

这为您提供了[..., refresh_token_middleware, ..., session_middleware] 的排序,并且您的中间件无法访问任何会话信息。

所以你必须交换订单; 先调用aiohttp_session.setup(),然后才添加自己的组件:

aiohttp_session.setup(app, EncryptedCookieStorage(session_key))
middleware.setup(app)

如果您在访问会话存储时仍然遇到问题,那么这意味着 其中一个中间件组件正在再次删除会话存储信息

您可以在不同位置使用以下中间件工厂来报告存在的会话存储,以帮助您进行调试:

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)

如果您在添加的每个中间件之间插入它,您应该能够确定会话存储是否丢失以及丢失的位置:

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)

这应该告诉你

  • 每个探测之前的中间件组件
  • 如果存在会话存储,则使用 ANSI 绿色和红色以便于发现
  • 如果有任何人完全重置了请求;如果探测计数再次从 0 开始,则不仅会清除会话密钥,还会清除探测计数器!

【讨论】:

    【解决方案2】:

    您自己更改顺序。代码应该是这样的

    def main():
        app = web.Application()
        session_key = base64.urlsafe_b64decode(fernet.Fernet.generate_key())
        aiohttp_session.setup(app, EncryptedCookieStorage(session_key))
        # I have tried swapping the two setup functions
        middleware.setup(app)
    
        web.run_app(app)
    

    如果您查看aiohttp_session.setup 的代码

    https://github.com/aio-libs/aiohttp-session/blob/master/aiohttp_session/init.py

    def setup(app, storage):
        """Setup the library in aiohttp fashion."""
    
        app.middlewares.append(session_middleware(storage))
    

    如您所见,该函数中添加了中间件。在 middleware.setup(app) 之前添加中间件会使会话仍然无法用于请求

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-05-01
      • 2010-09-25
      • 1970-01-01
      • 2012-02-25
      • 2014-08-18
      • 1970-01-01
      • 2023-03-21
      • 1970-01-01
      相关资源
      最近更新 更多