You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Quart端点中调用aiohttp抛出远程主机强制关闭连接错误

Quart + aiohttp 出现 ConnectionResetError 问题解决

问题现象

在Quart端点中使用aiohttp发起GET/POST请求时,即便遵循aiohttp官方教程实现,仍会抛出ConnectionResetError: [WinError 10054] An existing connection was forcibly closed by the remote host错误,完整异常栈如下:

Exception in callback _ProactorBasePipeTransport._call_connection_lost(None)
handle: <Handle _ProactorBasePipeTransport._call_connection_lost(None) created at C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.2800.0_x64__qbz5n2kfra8p0\lib\asyncio\proactor_events.py:109>
source_traceback: Object created at (most recent call last):
  File "C:\Users\devuser\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\aiohttp\client.py", line 560, in _request
    await resp.start(conn)
  File "C:\Users\devuser\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\aiohttp\client_reqrep.py", line 917, in start
    payload.on_eof(self._response_eof)
  File "C:\Users\devuser\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\aiohttp\streams.py", line 167, in on_eof
    callback()
  File "C:\Users\devuser\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\aiohttp\client_reqrep.py", line 952, in _response_eof
    self._connection.release()
  File "C:\Users\devuser\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\aiohttp\connector.py", line 178, in release
    self._connector._release(
  File "C:\Users\devuser\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\aiohttp\connector.py", line 663, in _release
    protocol.close()
  File "C:\Users\devuser\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\aiohttp\client_proto.py", line 63, in close
    transport.close()
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.2800.0_x64__qbz5n2kfra8p0\lib\asyncio\proactor_events.py", line 109, in close
    self._loop.call_soon(self._call_connection_lost, None)
Traceback (most recent call last):
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.2800.0_x64__qbz5n2kfra8p0\lib\asyncio\events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.2800.0_x64__qbz5n2kfra8p0\lib\asyncio\proactor_events.py", line 165, in _call_connection_lost
    self._sock.shutdown(socket.SHUT_RDWR)
ConnectionResetError: [WinError 10054] An existing connection was forcibly closed by the remote host

推测是aiohttp与Quart的交互存在异常,相关代码如下:

异步GET请求方法

async def _call_api_get(self, url : str, json_data : dict = None) -> ApiResponse:
    try:
        async with aiohttp.ClientSession() as session:
            async with session.get(url, json=json_data) as resp:
                body = await resp.json() \
                    if resp.content_type == 'application/json' \
                    else await resp.text()
                api_return = ApiResponse(
                    status_code = resp.status,
                    body = body,
                    content_type = resp.content_type)

    except ConnectionResetError as ex:
        print("ConnectionResetError:", ex)

    except Exception as ex:
        api_return = ApiResponse(exception_msg = ex)

    return api_return

Quart端点方法

async def my_endpoint(self) -> Response:

    auth_url = 'http://www.google.co.uk'

    api_return_body : dict = {"key": "value"}

    api_response = await self._call_api_get(auth_url,
                                            json_data=auth_request)

已参考过同类问题,但仍未解决。

解决方案

1. 复用ClientSession而非每次请求创建

当前代码每次请求都新建ClientSession,频繁建立/销毁连接易触发远端连接关闭策略。建议全局复用一个ClientSession实例:

import aiohttp
from quart import Quart

app = Quart(__name__)
# 应用初始化时创建全局session
app.client_session = aiohttp.ClientSession()

# 应用关闭时销毁session
@app.teardown_appcontext
async def close_session(exception):
    await app.client_session.close()

# 修改请求方法
async def _call_api_get(self, url : str, json_data : dict = None) -> ApiResponse:
    api_return = None
    try:
        async with app.client_session.get(url, json=json_data) as resp:
            body = await resp.json() if resp.content_type == 'application/json' else await resp.text()
            api_return = ApiResponse(
                status_code=resp.status,
                body=body,
                content_type=resp.content_type)
    except ConnectionResetError as ex:
        print("ConnectionResetError:", ex)
        api_return = ApiResponse(exception_msg=str(ex))
    except Exception as ex:
        api_return = ApiResponse(exception_msg=str(ex))
    return api_return

2. 调整aiohttp连接池配置

通过配置连接池参数,避免连接被过早回收:

# 创建session时指定连接池配置
timeout = aiohttp.ClientTimeout(total=60)
connector = aiohttp.TCPConnector(
    limit=100,  # 连接池总大小
    limit_per_host=10,  # 单主机连接数限制
    keepalive_timeout=30  # 连接保持时长
)
app.client_session = aiohttp.ClientSession(timeout=timeout, connector=connector)

3. 切换Windows环境下的asyncio事件循环

Windows默认的ProactorEventLoop可能存在兼容性问题,切换为SelectorEventLoop:

import asyncio
from quart import Quart

asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
app = Quart(__name__)

4. 确保完整读取响应内容

未完整读取响应就释放连接可能触发错误,需确保响应内容读取完成后再结束上下文:

import json

async def _call_api_get(self, url : str, json_data : dict = None) -> ApiResponse:
    api_return = None
    try:
        async with app.client_session.get(url, json=json_data) as resp:
            # 先读取完整响应数据
            raw_data = await resp.read()
            body = json.loads(raw_data) if resp.content_type == 'application/json' else raw_data.decode('utf-8')
            api_return = ApiResponse(
                status_code=resp.status,
                body=body,
                content_type=resp.content_type)
    except ConnectionResetError as ex:
        print("ConnectionResetError:", ex)
        api_return = ApiResponse(exception_msg=str(ex))
    except Exception as ex:
        api_return = ApiResponse(exception_msg=str(ex))
    return api_return

内容的提问来源于stack exchange,提问作者Swatcat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 18:18:07