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

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": "正常响应内容"}

原代码失效原因

  1. HTTPException的设计初衷是携带状态码和详情信息,FastAPI会自动将其转换为JSON响应,不会读取你内部的resp属性。
  2. 重写__str__方法返回HTMLResponse对象无意义,FastAPI处理异常时不会调用该方法生成响应。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 20:44:54