• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

20_错误处理

武飞扬头像
空巢青年_rui
帮助1

20_错误处理

在某些情况下,我们需要向使用我们API的客户端提示此错误信息。

客户端可能是一个带前端的浏览器、其他用户使用的代码或者一台物联网设备等。

需要向客户端返回错误提示的场景主要有:

  • 客户端没有执行操作的权限;
  • 客户端没有访问资源的权限;
  • 客户端尝试访问的项目不存在;
  • 等等。

遇到这些情况,我们通常需要返回一个 400 范围(400-499)内的 HTTP 状态码(status code)

这和 200 HTTP 状态码(200-299)的使用类似。200状态码意味着在某妆程度上请求中有一个“成功”。

不过 400 范围内的状态码则表示有一个来自客户端的错误。

就像大家所熟知的 404 NotFound 错误,就是因为客户端访问了一个不存在的URL导致的。


1. 使用 HTTPException :

要向客户端返回HTTP错误响应,可以使用 HTTPException

1.1 导入 HTTPException:

from fastapi import FastAPI, HTTPException

1.2 在代码中抛出 HTTPException:

HTTPException 就是额外包含了与API相关数据的常规Python异常

因为是Python异常,所以不能return, 只能raise

这也意味着,如果我们正处在路径操作函数中调用的一个工具方法里,并且我们在工具方法中抛出了一个HTTPException,那么路径操作函数中的剩余代码将不会再被执行,而是立即终止该请求并将 HTTPException中的HTTP错误返回给客户端。

在介绍依赖项与安全的章节中,您可以了解更多用 raise 异常代替 return 值的优势。

在下面的例子中,当客户端通过ID请求一个不存在的项目时,我们抛出一个 404 状态码的异常:

from fastapi import FastAPI, HTTPException

app = FastAPI()

items = {"foo": "The Foo Wrestlers"}


@app.get("/items/{item_id}")
async def read_item(item_id: str):
    if item_id not in items:
        raise HTTPException(status_code=404, detail="Item not found")  # 抛出异常
    return {"item": items[item_id]}

1.3 响应结果:

如果客户端请求 item_idfoo的项目:http://example.com/items/foo,则客户端会接收到 200 HTTP状态码,以及JSON响应内容:

{
  "item": "The Foo Wrestlers"
}

但是,如果客户端请求一个不存在的item_idbarhttp://example.com/items/bar。那么客户端将收到一个404 HTTP状态码(not found错误)和如下JSON相应内容:

{
  "detail": "Item not found"
}

提示:

触发 HTTPException 时,可以用参数 detail 传递任何能转换为 JSON 的值,不仅限于 str

还支持传递 dictlist 等数据结构。

FastAPI 能自动处理这些数据,并将之转换为 JSON。


2. 添加自定义响应 headers:

在某些情况下,为HTTP错误添加自定义headers是有用的,例如,出于安全等方面的考虑。

一般情况下可能不需要在代码中直接使用响应headers,但是对于一些高级的场景,如果需要还是可以自己添加自定义headers:

from fastapi import FastAPI, HTTPException

app = FastAPI()

items = {"foo": "The Foo Wrestlers"}


@app.get("/items-header/{item_id}")
async def read_item_header(item_id: str):
    if item_id not in items:
        raise HTTPException(
            status_code=404,
            detail="Item not found",
            headers={"X-Error": "There goes my error"},  # 添加自定义headers
        )
    return {"item": items[item_id]}
学新通

3. 安装自定义异常处理器:

我们可以通过 Starlette的异常工具 添加自定义异常处理器。

假设我们有一个自定义异常 UnicornException可能会在我们的代码或我们是用的库中被raise.

现在我们想要使用FastAPI全局处理这个异常,那么我们可以通过 @app.exception_handler() 添加一个自定义异常处理器:

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse


class UnicornException(Exception):
    """定义一个要处理的异常类"""
    def __init__(self, name: str):
        self.name = name


app = FastAPI()


@app.exception_handler(UnicornException)  # 添加自定义异常处理器
async def unicorn_exception_handler(request: Request, exc: UnicornException):
    return JSONResponse(
        status_code=418,
        content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."},
    )


@app.get("/unicorns/{name}")
async def read_unicorn(name: str):
    if name == "yolo":
        raise UnicornException(name=name)  # 抛出对应异常
    return {"unicorn_name": name}
学新通

当我们请求/unicorn/yolo时,路径操作将会抛出一个UnicornException。但是这个异常将会被unicorn_exception_handler所处理。

所以,我们会得到一个清晰的错误,其HTTP状态码是418,JSON内容是:

{"message": "Oops! yolo did something. There goes a rainbow..."}

技术细节:

from starlette.requests import Requestfrom starlette.responses import JSONResponse 也可以用于导入 RequestJSONResponse

FastAPI 提供了与 starlette.responses 相同的 fastapi.responses 作为快捷方式,但大部分响应操作都可以直接从 Starlette 导入。同理,Request 也是如此。


4. 覆盖默认的异常处理器:

FastAPI提供了一些默认的异常处理器。这些处理器负责在引发 HTTPException 和请求具有无效数据时返回默认的 JSON响应。

我们可以使用自己的异常处理程序覆盖这些异常处理器。

4.1 覆盖请求验证异常:

当请求中包含无效数据时,FastAPI内部会抛出一个RequestValidationError。同时还包含了一个默认的异常处理器。

要重写覆盖它,导入RequestValidationError 并将其与@app.exception_handler(RequestValidationError)一起来装饰异常处理器函数。

异常处理器将接收一个 Request 和这个异常:

from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError  # 导入异常
from fastapi.responses import PlainTextResponse
from starlette.exceptions import HTTPException as StarletteHTTPException

app = FastAPI()


@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request, exc):
    return PlainTextResponse(str(exc.detail), status_code=exc.status_code)


@app.exception_handler(RequestValidationError)  # 自定义异常处理器
async def validation_exception_handler(request, exc):
    return PlainTextResponse(str(exc), status_code=400)


@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id == 3:
        raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
    return {"item_id": item_id}
学新通

现在,如果我们访问/items/foo,可以看到默认的JSON错误将会被替换为文本格式的错误信息:

(原默认错误信息)

{
    "detail": [
        {
            "loc": [
                "path",
                "item_id"
            ],
            "msg": "value is not a valid integer",
            "type": "type_error.integer"
        }
    ]
}

文本格式的错误信息:

(替换后的文本错误信息)

1 validation error
path -> item_id
  value is not a valid integer (type=type_error.integer)
RequestValidationError vs ValidationError:

警告:这些是技术细节,如果现在对你来说不重要,可以跳过。

RequestValidationError是一个Pydantic中ValidationError的子类。

FastAPI使用它是因为,如果在response_model中使用了Pydantic模型,同时你的数据中有错误,name你将会在日志中看到错误。

但是客户端/用户不会看到它。相反,客户端会收到一个HTTP状态码为500Internal Server Error错误。

确实应该这样,因为如果在响应或代码中的任何地方(而不是客户端请求中)存在Pydantic ValidationError,那么它实际上是一个我们代码中的bug。

当我们修复它时,客户端/用户不应该访问有关错误的内部信息,因为这可能会暴露安全漏洞。


4.2 覆盖 HTTPException 错误处理器:

同样地,我们也可以覆盖HTTPException处理器。

例如,我们想要为错误返回一个纯文本响应而不是JSON:

from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
from fastapi.responses import PlainTextResponse  # 导入纯文本响应类
from starlette.exceptions import HTTPException as StarletteHTTPException  # 导入HTTPException

app = FastAPI()


@app.exception_handler(StarletteHTTPException)  # 定义HTTPException的异常处理器
async def http_exception_handler(request, exc):
    return PlainTextResponse(str(exc.detail), status_code=exc.status_code)  # 返回纯文本响应


@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
    return PlainTextResponse(str(exc), status_code=400)


@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id == 3:
        raise HTTPException(status_code=418, detail="Nope! I don't like 3.")  # 抛出一个HTTPException
    return {"item_id": item_id}
学新通

技术细节:

还可以使用 from starlette.responses import PlainTextResponse

FastAPI 提供了与 starlette.responses 相同的 fastapi.responses 作为快捷方式,但大部分响应都可以直接从 Starlette 导入。


4.3 使用 RequestValidationError 中的请求体:

RequestValidationError中包含其接收到的无效数据请求的 body

我们可以在开发应用时使用它来记录请求体日志并调试,或者将其返回给用户等。

from fastapi import FastAPI, Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError  # 导入
from fastapi.responses import JSONResponse
from pydantic import BaseModel

app = FastAPI()


@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    return JSONResponse(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}),  # 使用exc.body数据
    )


class Item(BaseModel):
    title: str
    size: int


@app.post("/items/")
async def create_item(item: Item):
    return item
学新通

现在,尝试发送一个无效的项目请求,例如:其中size的数据类型错误

{
  "title": "towel",
  "size": "XL"
}

我们将会收到一个响应告诉我们收到的请求体中包含无效的数据:

{
  "detail": [
    {
      "loc": [
        "body",
        "size"
      ],
      "msg": "value is not a valid integer",
      "type": "type_error.integer"
    }
  ],
  "body": {
    "title": "towel",
    "size": "XL"
  }
}
学新通
FastAPI的 HTTPException vs Starlette的 HTTPException:

FastAPI有自己的HTTPException, 并且FastAPI的HTTPException错误类是继承自Starlette的HTTPException 错误类的。

唯一的区别就是,FastAPI的HTTPException 允许我们在响应中添加自定义headers。

在OAuth 2.0 或其他安全工具内部会需要/使用这些headers。

所以,我们可以在代码中继续像平常一样抛出FastPAI的HTTPException

⭐ 但是,当我们要注册异常处理器时,则应该使用Starlette的HTTPException 来注册。

这样,如果 Starlette 的内部代码、 Starlette 扩展或插件的任何部分引发了 Starlette HTTPException 异常,我们的处理程序将能够捕获并处理它。

在本例中,为了能够在代码中同时包含两种 HTTPException,Starlette 的异常被重命名为 StarletteHTTPException:

from starlette.exceptions import HTTPException as StarletteHTTPException

4.4 复用(re-use) FastAPI 的异常处理器:

如果想要将异常和FastAPI中相同的默认异常处理器一起使用,可以从 fastapi.exception_handlers中导入并重用默认异常处理器。

即,我们可以在自定义异常处理器中自己先对异常做想要执行的处理,然后再将异常交给默认异常处理器进行处理。

from fastapi import FastAPI, HTTPException
from fastapi.exception_handlers import (
    http_exception_handler,
    request_validation_exception_handler,
)  # 导入默认异常处理器
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException

app = FastAPI()


@app.exception_handler(StarletteHTTPException)
async def custom_http_exception_handler(request, exc):
    print(f"OMG! An HTTP error!: {repr(exc)}")  # 自定义异常处理部分
    return await http_exception_handler(request, exc)  # 默认异常处理器处理部分


@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
    print(f"OMG! The client sent invalid data!: {exc}")  # 自定义异常处理部分
    return await request_validation_exception_handler(request, exc)  # 默认异常处理器处理部分


@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id == 3:
        raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
    return {"item_id": item_id}
学新通

在本例中,我们只是用一条非常夸张的消息打印错误,但我们知道,我们可以先使用异常,然后重新使用默认的异常处理器。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhhaciki
系列文章
更多 icon
同类精品
更多 icon
继续加载