【问题标题】:How to use an aiohttp ClientSession with Sanic?如何在 Sanic 中使用 aiohttp ClientSession?
【发布时间】:2019-07-10 17:55:08
【问题描述】:

我试图了解将 aiohttp 与 Sanic 一起使用的正确方法是什么。

从 aiohttp documentation,我发现以下内容:

不要为每个请求创建会话。您很可能需要每个应用程序一个会话来完全执行所有请求。 更复杂的情况可能需要每个站点一个会话,例如一个用于 Github,另一个用于 Facebook API。无论如何,为每个请求创建一个会话是一个非常糟糕的主意。 会话内部包含一个连接池。连接重用和保持活动(默认情况下都打开)可以提高整体性能。

当我去 Sanic 文档时,我发现了一个这样的例子:

这是一个例子:

from sanic import Sanic
from sanic.response import json

import asyncio
import aiohttp

app = Sanic(__name__)

sem = None

@app.route("/")
async def test(request):
    """
    Download and serve example JSON
    """
    url = "https://api.github.com/repos/channelcat/sanic"

    async with aiohttp.ClientSession() as session:
         async with sem, session.get(url) as response:
         return await response.json()

app.run(host="0.0.0.0", port=8000, workers=2)

这不是管理 aiohttp 会话的正确方法...

那么正确的方法是什么?
我应该在应用程序中初始化会话并将会话注入所有层中的所有方法吗?

我发现的唯一问题是 this 但这无济于事,因为我需要创建自己的类来使用会话,而不是 sanic。
在 Sanic 文档中还发现了 this,它说您不应该在事件循环之外创建会话。

我有点困惑:( 正确的方法是什么?

【问题讨论】:

  • 嘿@Tomer,我想知道,你觉得任何答案有帮助吗?
  • @johnMoutafis 谢谢!我们实际上做了类似的事情,只是做了一些小的改动。首先,自 2.0 版以来,ClientSession 中的传递循环已被弃用,因此我们不这样做。此外,我们没有使用“全局”定义全局会话,而是将其放在应用程序上。另外不要忘记在应用关闭时您需要关闭 ClientSession。
  • @johnMoutafis,如果你同意我的观点,如果你能改变你的答案,我会很高兴,这样我就可以点击“接受答案”:)
  • 我做了一些研究,因为你告诉我的很有趣,我更新了我的答案:D
  • @johnMoutafis 谢谢:) 同时更新描述。 (你不再使用全局)

标签: python python-3.x asynchronous aiohttp sanic


【解决方案1】:

为了使用单个aiohttp.ClientSession,我们只需将会话实例化一次并在应用程序的其余部分使用该特定实例。

为了实现这一点,我们可以使用before_server_start listener,这将允许我们在应用提供第一个字节之前创建实例。

from sanic import Sanic 
from sanic.response import json

import aiohttp

app = Sanic(__name__)

@app.listener('before_server_start')
def init(app, loop):
    app.aiohttp_session = aiohttp.ClientSession(loop=loop)

@app.listener('after_server_stop')
def finish(app, loop):
    loop.run_until_complete(app.aiohttp_session.close())
    loop.close()

@app.route("/")
async def test(request):
    """
    Download and serve example JSON
    """
    url = "https://api.github.com/repos/channelcat/sanic"

    async with app.aiohttp_session.get(url) as response:
        return await response.json()


app.run(host="0.0.0.0", port=8000, workers=2)

代码分解:

  • 我们正在创建一个aiohttp.ClientSession,将Sanic 应用程序在开始时创建的循环作为参数传递,在此过程中避免this pitfall
  • 我们将该会话存储在 Sanic app
  • 最后,我们正在使用此会话来提出我们的请求。

【讨论】:

  • 这个设置最终导致我的一些客户请求崩溃。有时,如果您有两个协同程序正在运行,第一个 __aenter__s 并等待响应。然后,第二个 __aenter__s 但在第一个完成之前完成其请求并 __aexit__s 打开会话。从而给我“会话已关闭”错误。我不是 100% 确定 ClientSession 的异步 ctx 管理器是如何实现的,或者特别是 ClientSession.exit 是如何实现的,但是用每个请求实例化一个新的客户端会话解决了我的问题。我认为如果 aiohttp 团队对这个问题有任何意见会很好。
  • 如何将应用对象传播到另一个文件/模块?假设我有多个带有“@app.route”集的文件。我在 main 中创建了应用程序,下一步该做什么?
  • @likern init 的实现方式,意味着aiohttp_session 存储在app 中,您可以从那里使用它。
  • @JohnMoutafis 我明白这一点。但是,如果我有另一个(类似的)带有路由的文件,如何将 app 对象本身添加到该文件中?
  • @JohnMoutafis 在所有示例和文档中,我只看到一个文件的用法。但是当你有很多路由时,我不明白如何在 Sanic 中使用多个文件。
【解决方案2】:

这基本上就是我正在做的事情。

我创建了一个模块 (interactions.py),它具有例如这样的功能:

async def get(url, headers=None, **kwargs):
    async with aiohttp.ClientSession() as session:
        log.debug(f'Fetching {url}')
        async with session.get(url, headers=headers, ssl=ssl) as response:
            try:
                return await response.json()
            except Exception as e:
                log.error(f'Unable to complete interaction: {e}')
                return await response.text()

然后我就await

results = await interactions.get(url)

我不确定为什么这不是“正确的方式”。一旦我的请求完成,会话(至少满足我的需要)可以关闭。

【讨论】:

  • 感谢您的回答:) 这不是正确的方法,因为这是他们在文档中所说的。他们说您不需要每次需要执行请求时都打开客户端会话(具有连接池),而只需一次。
  • 几乎我所做的只是略有不同。一旦我对这种设置进行了更改,就会将响应和 response.json() 作为元组返回。所以响应,response_json = await req.get()。这允许您检查响应状态、标头等。我还添加了超时和异步超时。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多