自定义ASGI中间件引发lifespan协议不兼容及K8s部署异常
问题场景
为处理客户端未等待响应就断开的场景,编写了如下自定义中间件NoResponseReturnMiddleware:
class NoResponseReturnMiddleware(BaseHTTPMiddleware): _ERROR_MESSAGE: str = "No response returned." def __init__(self, app: ASGIApp, logger: Logger): super().__init__(app) self.logger = logger async def __call__(self, scope, receive, send) -> None: request = Request(scope, receive, send) try: await super().__call__(scope, receive, send) except RuntimeError as exc: if await request.is_disconnected() and str(exc) == self._ERROR_MESSAGE: self.logger.debug( "Remote client disconnected", extra={ "url": request.url, "method": request.method, "path": request.url.path, "query": request.url.query, }, ) else: raise exc async def dispatch(self, request, call_next): return await call_next(request)
启动应用时收到提示:
ASGI 'lifespan' protocol appears unsupported
本地运行正常,但部署到Kubernetes后,Pod进入永久重启状态,Kubelet报错:
Warning Unhealthy 5m5s (x3 over 5m25s) kubelet Liveness probe failed: Get "http://11.11.11.11.1:8000/": dial tcp 11.11.11.11.1:8000: connect: connection refused
进一步测试发现,执行命令uvicorn main:app --reload --lifespan on时触发断言错误:
assert scope["type"] in ("http", "websocket") AssertionError
问题根源
自定义中间件的__call__方法未处理ASGI协议中的lifespan类型请求。当启用lifespan协议时,Uvicorn会发送type为lifespan的scope,而代码中直接创建Request对象,Request内部会断言scope类型必须是http或websocket,导致断言失败,服务启动失败,进而Kubernetes存活探针无法连接,触发Pod重启。
修复方案
修改__call__方法,仅在scope类型为http或websocket时执行自定义逻辑,其他类型(如lifespan)直接调用父类方法处理:
async def __call__(self, scope, receive, send) -> None: if scope["type"] in ["http", "websocket"]: request = Request(scope, receive, send) try: await super().__call__(scope, receive, send) except RuntimeError as exc: if await request.is_disconnected() and str(exc) == self._ERROR_MESSAGE: self.logger.debug( "Remote client disconnected", extra={ "url": request.url, "method": request.method, "path": request.url.path, "query": request.url.query, }, ) else: raise exc else: # 处理lifespan等其他ASGI协议类型 await super().__call__(scope, receive, send)
这样可以兼容ASGI的所有协议类型,解决lifespan协议不支持的问题,避免服务启动失败导致的Pod重启。
内容的提问来源于stack exchange,提问作者SmokyICE

