aiohttp.web.application() 不必显式传 loop 参数;python 3.7+ 中应省略该参数,由 aiohttp 自动获取当前线程运行中的事件循环,手动传入易引发 runtimeerror 或静默失败。

aiohttp.web.Application() 必须显式传入 loop 参数吗?
Python 3.7+ 默认不强制要求传 loop,但显式传入反而容易出错。aiohttp 会自动从当前线程获取运行中的事件循环,手动传一个已关闭或非当前线程的 loop,启动时直接抛 RuntimeError: Event loop is closed 或静默失败。
实操建议:
- 完全不要传
loop参数,让 aiohttp 自己处理 - 若在非主线程里启动服务器(比如测试中用
threading.Thread),必须先调用asyncio.set_event_loop()设置该线程专属 loop - 旧项目升级到 Python 3.11+ 时注意:
asyncio.get_event_loop()已弃用,统一用asyncio.get_running_loop(),但 aiohttp 内部已适配,你不用管
路由 handler 必须是 async def 吗?
必须。哪怕函数体里没 await,也得声明为协程。写成普通函数会导致 500 错误,日志里出现 TypeError: object function is not a coroutine。
常见错误现象:
- 把
def handle(request): return web.Response(text="ok")直接注册进路由 —— 看似能跑,实际返回空响应或 Connection Reset - 用
asyncio.run_in_executor包裹同步逻辑后,忘了在外层再套一层async def
正确写法就一种:
async def handle(request):
return web.Response(text="ok")
为什么本地能跑,部署到 Linux 就报 Address already in use?
默认绑定 localhost(即 127.0.0.1),外部请求根本连不上。这不是端口占用,是监听地址不对。
使用场景差异:
- 开发调试:用
host="localhost"没问题,浏览器 localhost:8080 可访问 - 容器或服务器部署:必须改成
host="0.0.0.0",否则只响应本机内部请求 - 若用 systemd 或 supervisor 管理进程,还要确认是否重复执行了多次
web.run_app()—— 它不会自动去重,第二次必然报地址冲突
如何加个简单中间件记录请求耗时?
aiohttp 中间件不是装饰器,不能用 @middleware 这种写法(那是旧版遗留语法,已移除)。必须定义为接收 app, handler 的异步函数,并通过 app.middlewares.append() 注册。
性能影响很小,但要注意两点:
- 中间件里别做阻塞操作(如直接调
time.sleep()),否则整个 event loop 卡住 - 如果 handler 抛异常,
request.match_info可能为None,需判空再取.get("handler_name") - 日志打太频繁会影响吞吐,建议只在 debug 模式下开启耗时打印
示例中间件:
async def timing_middleware(app, handler):
async def middleware_handler(request):
start = asyncio.get_event_loop().time()
try:
response = await handler(request)
elapsed = asyncio.get_event_loop().time() - start
print(f"{request.method} {request.path} {elapsed:.3f}s")
return response
except Exception:
elapsed = asyncio.get_event_loop().time() - start
print(f"ERROR {request.method} {request.path} {elapsed:.3f}s")
raise
return middleware_handler
<p>app = web.Application()
app.middlewares.append(timing_middleware)</p>
实际部署时最容易被忽略的是:没检查 web.run_app() 是否被调用了两次 —— 一次在脚本顶层,一次又被 import 触发(比如模块被 pytest 扫描、或 IDE 自动 reload)。这种问题不会报错,但服务器会监听两遍,第二次必然失败,且错误日志藏在 stderr 里,不重定向就看不到。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











