send_timeout并非asyncio原生参数,而是http服务器层对响应发送阶段的超时控制;需通过streamwriter.drain()配合wait_for、aiohttp的write_timeout、uvicorn的--timeout-write或动态速率检测实现慢速客户端熔断。

send_timeout 并不是 asyncio 的原生参数或标准 API,Python 标准库中 没有 send_timeout 这一事件循环级配置项。你提到的“在事件循环层面自动熔断慢速客户端响应”,实际对应的是 HTTP 服务器层对客户端写入(response send)阶段的超时控制,常见于异步 Web 框架(如 aiohttp、Starlette/FastAPI、Quart)或底层网络库(如 asyncio.StreamWriter),而非事件循环本身。
事件循环(asyncio.EventLoop)不直接管理 HTTP 响应发送超时;它只调度协程、处理 I/O 就绪事件。真正的“慢速客户端防御”需在 协议栈上层实现:当服务端已生成响应但客户端读取极慢(如每秒只收几个字节),服务端需主动中断连接,防止连接长期占用、耗尽文件描述符或内存。
以下是可落地的三类实践方式,覆盖从底层到框架的完整链路:
直接控制 StreamWriter 发送超时(最底层、最灵活)
适用于自定义 TCP/HTTP 服务器(如基于 asyncio.start_server 构建):
import asyncio
async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
addr = writer.get_extra_info("peername")
try:
# 设置单次 write 的超时(非全局,需每次写前设)
writer.write(b"HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, World!")
await asyncio.wait_for(writer.drain(), timeout=5.0) # 关键:drain() 等待缓冲区清空
except asyncio.TimeoutError:
print(f"[SLOW CLIENT] {addr} failed to consume response in time")
writer.close()
await writer.wait_closed()
except Exception as e:
print(f"[ERROR] {addr}: {e}")
writer.close()
async def main():
server = await asyncio.start_server(handle_client, "0.0.0.0", 8080)
async with server:
await server.serve_forever()
-
writer.drain()是关键:它等待内核发送缓冲区有空间,若客户端接收太慢,缓冲区会满,drain()就会阻塞。 -
asyncio.wait_for(..., timeout=5.0)对drain()施加硬性超时,超时即断连。 - 此方式可精确控制每个
write+drain组合的响应窗口,适合防御 Slowloris 类攻击。
在 aiohttp 中启用 client_timeout 和 write_timeout
aiohttp.web 提供了明确的传输层超时配置:
from aiohttp import web
async def handle(request):
return web.Response(text="OK", content_type="text/plain")
app = web.Application()
app.router.add_get("/", handle)
# 全局写入超时(从响应开始发送起计时)
web.run_app(
app,
host="0.0.0.0",
port=8080,
# ⚠️ 注意:aiohttp 3.9+ 支持 write_timeout;旧版用 client_timeout(含读+写)
write_timeout=5, # 响应体发送总耗时上限(秒)
keepalive_timeout=10, # Keep-Alive 连接空闲超时
shutdown_timeout=3, # 优雅关闭等待时间
)
-
write_timeout从Response.prepare()开始计时,覆盖整个响应体发送过程。 - 若客户端接收速率低于阈值(如 send() 会反复阻塞,最终触发超时并关闭连接。
- 配合
limit=1024*1024(request body 限制)和client_max_size可形成完整慢速攻击防护。
FastAPI / Starlette:通过底层 ASGI Server 配置生效
FastAPI 自身不暴露 send_timeout,但其运行依赖 ASGI 服务器(如 uvicorn 或 hypercorn)。防御需在服务器启动参数中设置:
# Uvicorn 示例:强制所有响应写入在 6 秒内完成 uvicorn main:app --host 0.0.0.0 --port 8000 \ --timeout-keep-alive 5 \ --timeout-write 6 \ --limit-concurrency 1000 \ --limit-max-requests 10000
-
--timeout-write(Uvicorn ≥ 0.29):即send_timeout的等效项,单位秒,作用于 ASGIsend()调用。 -
--limit-concurrency防止慢连接挤占并发槽位。 - 生产环境强烈建议开启
--proxy-headers并校验X-Forwarded-For,避免攻击者绕过反向代理直连。
补充:动态响应速率检测(进阶防御)
仅靠固定超时不够智能。可结合实时速率判断:
class RateLimitedStreamWriter:
def __init__(self, writer: asyncio.StreamWriter, min_rate_bps=1024):
self.writer = writer
self.min_rate_bps = min_rate_bps
self.start_time = None
self.bytes_sent = 0
async def write_and_drain(self, data: bytes):
if self.start_time is None:
self.start_time = asyncio.get_event_loop().time()
self.writer.write(data)
self.bytes_sent += len(data)
now = asyncio.get_event_loop().time()
elapsed = now - self.start_time
if elapsed > 0 and (self.bytes_sent / elapsed)
- 按字节/秒动态评估客户端消费能力,比静态超时更抗扰动。
- 可嵌入中间件或自定义 Response 类,在框架可控范围内复用。
本质上,“事件循环层面熔断”是误称——真正起效的是 I/O 层(socket send buffer + kernel TCP stack)与 asyncio 协作下的超时调度。只要你在 write → drain() 流程中施加 wait_for,或使用支持 write_timeout 的成熟服务器,就能可靠拦截慢速客户端,无需修改事件循环源码。










