FastAPI抛出HTTPException时返回HTML响应的问题排查
问题:FastAPI中抛出异常时返回HTML响应失败
希望在FastAPI中遇到连接错误时,返回带有自动刷新逻辑的HTML响应,让前端面板自动重载。我编写了以下代码:
from fastapi import HTTPException from fastapi.responses import HTMLResponse, JSONResponse class ReloadException(HTTPException): """ 返回HTMLResponse让浏览器稍后自动刷新 """ def __init__(self, status_code:int=500, detail:str=None): self.status_code = status_code self.detail = detail self.resp = HTMLResponse( status_code=self.status_code, content=f"<html><head><meta http-equiv=\"refresh\" content=\"60\"></head><body>error:{self.detail}</body></html>", headers={'Content-Type': 'text/html'} ) def __str__(self): return self.resp # 连接测试 try: r = await s.get(url, headers={'PRIVATE-TOKEN': config.get_config()['gitlabtoken']}) r.raise_for_status() except httpx.HTTPError as exc: raise ReloadException(status_code=500, detail=f"{exc.request.url} - {exc}") if r and r.status_code!=200: raise HTTPException(status_code=r.status_code, detail=f"{r.text}")
运行时触发Starlette报错:
File "site-packages\starlette\responses.py", line 49, in render return content.encode(self.charset) # type: ignore ^^^^^^^^^^^^^^ AttributeError: 'dict' object has no attribute 'encode'
更新:调整内容为字符串后,接口仍返回JSON响应:
curl.exe -v "http://localhost:8000/lastjobs" * Trying [::1]:8000... * Trying 127.0.0.1:8000... * Connected to localhost (127.0.0.1) port 8000 > GET /lastjobs HTTP/1.1 > Host: localhost:8000 > User-Agent: curl/8.4.0 > Accept: */* > < HTTP/1.1 500 Internal Server Error < date: Wed, 03 Apr 2024 13:20:43 GMT < server: uvicorn < content-length: 100 < content-type: application/json < {"detail":"https://xxxxxxx/api/v4/projects/2309 - [Errno 11001] getaddrinfo failed"}
解决方案
核心问题是:自定义的ReloadException继承自HTTPException,但FastAPI对HTTPException的默认处理逻辑是返回JSON响应,不会识别你内部定义的HTMLResponse对象。以下两种方式可以解决问题:
方式一:全局异常处理器(推荐)
注册全局异常处理器,直接针对目标异常返回HTML响应:
from fastapi import FastAPI, Request from fastapi.responses import HTMLResponse import httpx app = FastAPI() # 针对httpx.HTTPError注册异常处理器 @app.exception_handler(httpx.HTTPError) async def reload_handler(request: Request, exc: httpx.HTTPError): err_detail = f"{exc.request.url} - {exc}" return HTMLResponse( status_code=500, content=f"<html><head><meta http-equiv='refresh' content='60'></head><body>error:{err_detail}</body></html>", headers={'Content-Type': 'text/html'} ) # 业务路由 @app.get("/lastjobs") async def get_lastjobs(): target_url = "你的目标接口地址" async with httpx.AsyncClient() as client: try: resp = await client.get( target_url, headers={'PRIVATE-TOKEN': config.get_config()['gitlabtoken']} ) resp.raise_for_status() except httpx.HTTPError as exc: raise exc # 触发自定义异常处理器 if resp.status_code != 200: return HTMLResponse( status_code=resp.status_code, content=f"<html><body>请求错误:{resp.text}</body></html>" ) # 正常业务逻辑返回 return {"data": "正常响应内容"}
方式二:直接返回HTMLResponse
捕获异常后不抛出,直接返回HTML响应:
from fastapi import FastAPI from fastapi.responses import HTMLResponse import httpx app = FastAPI() @app.get("/lastjobs") async def get_lastjobs(): target_url = "你的目标接口地址" async with httpx.AsyncClient() as client: try: resp = await client.get( target_url, headers={'PRIVATE-TOKEN': config.get_config()['gitlabtoken']} ) resp.raise_for_status() except httpx.HTTPError as exc: err_detail = f"{exc.request.url} - {exc}" return HTMLResponse( status_code=500, content=f"<html><head><meta http-equiv='refresh' content='60'></head><body>error:{err_detail}</body></html>", headers={'Content-Type': 'text/html'} ) if resp.status_code != 200: return HTMLResponse( status_code=resp.status_code, content=f"<html><body>请求错误:{resp.text}</body></html>" ) # 正常业务逻辑返回 return {"data": "正常响应内容"}
原代码失效原因
HTTPException的设计初衷是携带状态码和详情信息,FastAPI会自动将其转换为JSON响应,不会读取你内部的resp属性。- 重写
__str__方法返回HTMLResponse对象无意义,FastAPI处理异常时不会调用该方法生成响应。
内容的提问来源于stack exchange,提问作者MortenB
相关产品推荐
相关产品推荐

