请求体现的是后端的数据服务能力,而响应体现的是后端向前端的数据展示能力。
一,一个完整的web响应应该包含哪些东西
一个完整的 Web 响应通常包含以下几个主要部分:
1. 状态行- HTTP 版本- 状态码- 状态消息例如:`HTTP/1.1 200 OK`2. 响应头
常见的响应头包括:a. 通用头部:- Date: 响应生成的日期和时间- Connection: 连接状态(如 keep-alive 或 close)b. 响应特定头部:- Server: 服务器软件名称和版本- Content-Type: 响应体的 MIME 类型- Content-Length: 响应体的长度(以字节为单位)- Content-Encoding: 响应体的编码方式(如 gzip)c. 实体头部:- Last-Modified: 资源的最后修改日期- ETag: 资源的唯一标识符- Expires: 资源的过期时间- Cache-Control: 缓存控制指令d. 安全相关头部:- Set-Cookie: 设置 HTTP cookie- X-XSS-Protection: 控制浏览器的 XSS 筛选器- X-Frame-Options: 控制页面是否可以被嵌入框架- Content-Security-Policy: 内容安全策略- Strict-Transport-Security: 强制使用 HTTPSe. 跨域相关头部:- Access-Control-Allow-Origin: 指定允许跨域请求的源- Access-Control-Allow-Methods: 允许的 HTTP 方法- Access-Control-Allow-Headers: 允许的请求头3. 空行用于分隔头部和响应体4. 响应体- 包含请求的资源或处理结果- 格式取决于 Content-Type(如 HTML、JSON、XML、图片等)
示例:
HTTP/1.1 200 OK
Date: Mon, 23 May 2023 12:28:53 GMT
Server: Apache/2.4.41 (Ubuntu)
Content-Type: application/json; charset=utf-8
Content-Length: 234
Cache-Control: max-age=3600
ETag: "686897696a7c876b7e"
X-Frame-Options: DENY
Strict-Transport-Security: max-age=31536000; includeSubDomains{"id": 12345,"name": "Example Product","description": "This is an example product description.","price": 99.99,"inStock": true
}
注意事项:
- 并非所有响应都需要包含响应体(如 204 No Content)。
- 响应头的具体内容会根据请求和应用需求而变化。
- 在实际应用中,应当根据安全需求和性能考虑来选择合适的响应头。
- 对于流式响应或大文件传输,可能会使用分块传输编码(Transfer-Encoding: chunked)。
- 在设计 API 时,应考虑响应的一致性,包括错误处理和状态码的使用。
二,响应
(一)简单响应
就像前面我们定义的路由处理函数一样,可以通过返回一个字典来返回一个简单的响应内容:
import uvicorn
from fastapi import FastAPI, Headerapp = FastAPI()@app.get("/test/")
def test():response = {"message": "Hello, World!"}print(type(response)) # -> <class 'dict'>return responseif '__main__' == __name__:uvicorn.run(app, host='127.0.0.1', port=8088)
通过打印的内容我们可以看到,返回的是一个 Python 字典,来看看 FastAPI 是怎么处理它的:
具体到不同的客户端情况:
1,使用浏览器直接访问:
- 你会看到 JSON 格式的文本
2,使用 JavaScript/Ajax:
fetch('http://127.0.0.1:8088/test/').then(response => response.json()).then(data => console.log(typeof data, data));
// 输出:object {message: "Hello, World!"}
- 接收到的是 JavaScript 对象
3,使用 Python requests 库:
import requests
response = requests.get('http://127.0.0.1:8088/test/')
data = response.json()
print(type(data), data)
# 输出:<class 'dict'> {'message': 'Hello, World!'}
- 解析后得到的是 Python 字典
4,使用 curl 命令行工具:
curl http://127.0.0.1:8088/test/
# 输出:{"message": "Hello, World!"}
- 接收到的是 JSON 格式的字符串
(二)响应数据模型
我们可以定义请求体的数据模型来接收请求体的数据,同样也可以定义响应数据模型来规范响应数据。
- 响应数据模型在 API 文档页中为 JSON 格式。
只需要在任意的路由处理函数中使用 response_model
参数来声明用于响应的模型。
FastAPI 将使用此 response_model 来:
- 将输出数据转换为其声明的类型。
- 校验数据。
- 在 OpenAPI 的路径操作中为响应添加一个 JSON Schema。
- 并在自动生成文档系统中使用。
1,定义与使用响应数据模型
首先同样使用 pydantic 定义响应数据模型:
import uvicornfrom typing import Optional
from fastapi import FastAPI
from pydantic import BaseModelapp = FastAPI()# 1,定义请求提数据模型
class User(BaseModel):first_name: strlast_name: str# 2,定义响应数据模型
class UserExtra(BaseModel):first_name: strlast_name: strfull_name: str | None = None# 处理请求
@app.post("/create-user/", response_model=UserExtra)
async def create_user(user: User) -> Any:# 4,处理请求数据full_name = user.first_name + ' ' + user.last_name# 5,返回数据return {"first_name": user.first_name,"last_name": user.last_name,"full_name": full_name}if '__main__' == __name__:uvicorn.run(app, host='127.0.0.1', port=8088)
之所以要将响应模型放在参数中声明,而不是放在函数返回值中使用,是因为路由处理函数可能不会真正返回响应模型(可能是一个 dict、数据库对象或其他模型),这是就可以使用 response_model
来执行字段约束和序列化。
查看 API:
当路由处理函数的返回值无法被 response_model
处理成满足响应模型的数据的时候,就会报错:
import uvicornfrom typing import Optional
from fastapi import FastAPI
from pydantic import BaseModelapp = FastAPI()# 1,定义请求提数据模型
class User(BaseModel):first_name: strlast_name: str# 2,定义响应数据模型
class UserExtra(BaseModel):first_name: strlast_name: strfull_name: str | None = None# 处理请求
@app.post("/create-user/", response_model=UserExtra)
async def create_user(user: User):# 4,处理请求数据full_name = user.first_name + ' ' + user.last_name# 5,返回数据return {"full_name": full_name}if '__main__' == __name__:uvicorn.run(app, host='127.0.0.1', port=8088)
当路由处理函数的返回值“太丰富”时,response_model
还会自动过滤掉无关内容:
import uvicornfrom typing import Optional
from fastapi import FastAPI
from pydantic import BaseModelapp = FastAPI()# 1,定义请求提数据模型
class User(BaseModel):first_name: strlast_name: str# 2,定义响应数据模型
class UserExtra(BaseModel):first_name: strlast_name: strfull_name: str | None = None# 处理请求
@app.post("/create-user/", response_model=UserExtra)
async def create_user(user: User):# 4,处理请求数据full_name = user.first_name + ' ' + user.last_name# 5,返回数据return {"first_name": user.first_name,"last_name": user.last_name,"full_name": full_name,"extra": "extra"}if '__main__' == __name__:uvicorn.run(app, host='127.0.0.1', port=8088)
2,对响应数据模型的控制
如果我们在定义响应数据模型时使用了默认值,通常情况下,会在请求响应中补上这些字段并使用其默认值。
如果组要排除这些字段的出现,可以使用 response_model_exclude_unset
参数:
from typing import Any
import uvicornfrom fastapi import FastAPI
from pydantic import BaseModelapp = FastAPI()# 1,响应数据模型
class Item(BaseModel):name: strdescription: str | None = Noneprice: floattax: float | None = 1111tags: list[str] = ['tag1', 'tag2']@app.get("/items/", response_model=list[Item], response_model_exclude_unset=True)
async def read_items() -> Any:return [{"name": "Portal Gun", "price": 42.0},{"name": "Plumbus", "price": 32.0, "tax": 3.2},]if __name__ == '__main__':uvicorn.run(app)
这些控制字段来自 pydantic,还有:
response_model_exclude_unset=True
:排除未设置的字段;response_model_exclude_defaults=True
:排除默认值的字段;response_model_exclude_none=True
:排除值为 None 的字段;response_model_exclude={}
:排除指定的字段,{}
是一个set
;response_model_include={}
:包含指定的字段,{}
是一个set
。
fastapi 官方更建议使用多个响应数据模型而不是疯狂使用上面这些个参数:因为即使使用
response_model_include
或response_model_exclude
来省略某些属性,在应用程序的 OpenAPI 定义(和文档)中生成的 JSON Schema 仍将是完整的模型。
3,业务数据模型
在实际场景中,获得请求数据后会经过一系列业务处理后才能生成响应数据,这个过程中往往还需要使用到其他数据模型。
例如,在处理用户注册业务的路由处理函数中:
- 接受请求数据的请求数据模型包含用户名和密码
- 业务模型包含用户名、密码的哈希值
- 响应数据模型包含用户名
通常来说,我们应该将各个数据模型独立出来,形成请求数据模型、业务数据模型和响应数据模型。
from datetime import datetimeimport uvicornfrom typing import Any, Optional
from fastapi import FastAPI
from pydantic import BaseModelapp = FastAPI()class UserIn(BaseModel):"""请求数据模型"""username: str # 必填password: str # 必填# 以下字段非必填email: Optional[str] = Nonephone: Optional[str] = Noneaddress: Optional[str] = Noneclass UserDb(BaseModel):"""数据库业务模型"""username: strpassword_hash: stremail: Optional[str] = Nonephone: Optional[str] = Noneaddress: Optional[str] = Noneupdate_time: datetime = datetime.now()class UserOut(BaseModel):"""响应数据模型"""username: stremail: Optional[str] = Nonephone: Optional[str] = Noneaddress: Optional[str] = Noneupdate_time: datetimedef get_password_hash(password: str) -> str:"""假设这是一个加密函数"""return password + 'hash'def save_user_login(user: UserIn) -> UserDb:"""假设这是一个保存用户登录的函数"""password_md5 = get_password_hash(user.password)user_db = UserDb(**user.model_dump(), password_hash=password_md5)print(user_db)return user_db@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):"""创建用户"""user_saved = save_user_login(user)return user_savedif '__main__' == __name__:uvicorn.run(app, host='127.0.0.1', port=8088)
4,简化数据模型
在上面的代码中,我们定义了三个数据模型,它们有许多相似的字段,我们可以使用继承来简化这些数据模型。
import uvicornfrom datetime import datetime
from typing import Optionalfrom fastapi import FastAPI
from pydantic import BaseModel, Fieldapp = FastAPI()class UserBase(BaseModel):username: stremail: Optional[str] = Nonephone: Optional[str] = Noneaddress: Optional[str] = Noneclass UserIn(UserBase):password: strclass UserDb(UserBase):password_hash: strupdate_time: datetime = Field(default_factory=datetime.now)class UserOut(UserBase):update_time: datetime# 其余的函数和路由保持不变
def get_password_hash(password: str) -> str:return password + 'hash'def save_user_login(user: UserIn) -> UserDb:password_hash = get_password_hash(user.password)return UserDb(**user.model_dump(exclude={'password'}), password_hash=password_hash)@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):return save_user_login(user)if '__main__' == __name__:uvicorn.run(app, host='127.0.0.1', port=8088)
5,使用多个响应模型
在以下情况下,我们可能需要在响应中返回多个数据模型:
- 错误处理:当API可能返回成功响应或错误响应时。
- 条件响应:基于某些条件(如查询参数)返回不同的响应模型。
- 版本控制:API的不同版本可能返回不同的响应结构。
- 多态响应:根据请求的资源类型返回不同的响应模型。
这时可以使用 Union 类型将不同的响应模型组合在一起。
from typing import Unionimport uvicorn
from fastapi import FastAPI
from pydantic import BaseModelapp = FastAPI()class UserProfile(BaseModel):username: stremail: stris_active: boolclass ErrorMessage(BaseModel):error_code: intmessage: str@app.get("/user/{user_id}", response_model=Union[UserProfile, ErrorMessage])
async def get_user(user_id: int):if user_id == 1:return UserProfile(username="john_doe", email="john@example.com", is_active=True)else:return ErrorMessage(error_code=404, message="User not found")# 另一个例子:根据查询参数返回不同的响应
class BasicUserInfo(BaseModel):username: stremail: strclass DetailedUserInfo(BaseModel):username: stremail: strphone: straddress: str@app.get("/user_info", response_model=Union[BasicUserInfo, DetailedUserInfo])
async def get_user_info(user_id: int, detailed: bool = False):if detailed:return DetailedUserInfo(username="jane_doe", email="jane@example.com",phone="123-456-7890", address="123 Main St")else:return BasicUserInfo(username="jane_doe", email="jane@example.com")if '__main__' == __name__:uvicorn.run(app, host='127.0.0.1', port=8088)
在上面的示例中,我展示了两种使用 Union 类型的情况:
- 错误处理:get_user 函数可能返回 UserProfile 或 ErrorMessage。
- 条件响应:get_user_info 函数根据 detailed 参数返回 BasicUserInfo 或 DetailedUserInfo。
使用 Union
类型的好处包括:
- 类型安全:它明确定义了可能的响应类型,有助于静态类型检查。
- 文档清晰:FastAPI 会自动生成包含所有可能响应的 API 文档。
- 灵活性:允许在不同情况下返回不同的响应结构,而不需要创建一个包含所有可能字段的大型模型。
需要注意的是,当使用 Union
类型时,客户端需要能够处理不同的响应结构。
查看 API:
(三)内置的响应类
在路由处理函数中,我们可以直接返回基础数据类型、用 Union
构造的泛型,或者使用 Pydantic 定义的响应数据模型。
fastapi 会将这些数据转换为 JSON 格式的响应数据,然后通过响应类返回给客户端。
除此之外,FastAPI还提供了一些内置的响应类,用来返回更多类型的响应。
1,纯文本响应——Plain Text Response
fastapi 使用 PlainTextResponse
类来返回纯文本响应,不会对纯文本的内容进行校验与转换。
import uvicornfrom fastapi import FastAPI
from fastapi.responses import PlainTextResponseapp = FastAPI()@app.get("/", response_class=PlainTextResponse)
async def read_root():return "Hello, world!"if __name__ == '__main__':uvicorn.run(app)
2,HTML 响应——HTML Response
fastapi 使用 HTMLResponse
类来返回 HTML 响应,不会对 HTML 的内容进行校验与转换。
import uvicornfrom fastapi import FastAPI
from fastapi.responses import HTMLResponseapp = FastAPI()html_content = """
<html><head><title>FastAPI</title></head><body><h1>Hello, FastAPI!</h1></body>
</html>
"""@app.get("/type1", response_class=HTMLResponse)
async def read_root():"""响应数据的 media type 为 text/html"""return html_content@app.get("/type2")
async def read_root():"""响应数据的 media type 为 application/json"""return HTMLResponse(content=html_content)if __name__ == '__main__':uvicorn.run(app)
3,重定向响应——Redirect Response
fastapi 使用 RedirectResponse
类来返回重定向响应,跳转到指定的 URL。
import uvicornfrom fastapi import FastAPI
from fastapi.responses import RedirectResponseapp = FastAPI()@app.get("/")
def read_root():return RedirectResponse(url='/docs')@app.get("/github")
def read_github():return RedirectResponse(url='https://github.com/')if __name__ == '__main__':uvicorn.run(app)
4,JSON响应——JSON Response
默认情况下,fastapi 使用 json_encoder()
将模型数据转为 JSON 格式,然后使用 JSONResponse
类返回这些数据。
import uvicornfrom typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponseapp = FastAPI()class UserModel(BaseModel):name: strage: intaddress: strdesignation: Optional[str] = None@app.post("/user")
async def update_user(user: UserModel):json_data = jsonable_encoder(user)return JSONResponse(content=json_data)if __name__ == '__main__':uvicorn.run(app)
5,通用响应——Response
当返回的数据不是 JSON 格式时,可以使用 Response
类来返回通用响应。
import uvicornfrom fastapi import FastAPI, Responseapp = FastAPI()@app.get("/item/{item_id}")
async def get_xml_data(item_id: int):data = """<Document><Name>John Doe</Name><Age>25</Age><City>San Francisco</City></Document>"""return Response(content=data, media_type="application/xml")if __name__ == '__main__':uvicorn.run(app)
对于通用响应,我们在实例化 Response 类时需要指定一些额外的参数:
- content:响应数据的内容;
- status_code:响应状态码,默认为 200;
- media_type:响应数据的 media type;自动生成一个 Content-Type 头信息;
- headers:响应头信息;自动包含一个 Content-Length 头信息,用于指定响应数据的长度。
5,流式响应——StreamingResponse
字节流响应的数据是二进制格式的,用于传输音频、视频、图像等二进制数据。
fastapi 使用 StreamingResponse
类来返回流式响应。
import uvicornfrom fastapi import FastAPI
from fastapi.responses import StreamingResponseapp = FastAPI()@app.get("/get_video")
async def get_file():file_path = "1-1 本周整体内容介绍和学习方法.mp4"file_like = open(file_path, mode="rb")return StreamingResponse(file_like, media_type="video/mp4")if __name__ == '__main__':uvicorn.run(app)
在浏览器访问 http://127.0.0.1:8000/get_video 就可以浏览视频内容了。
6,文件响应——FileResponse
fastapi 使用 FileResponse
类来处理异步文件响应,用于传输文件。
相比于 StreamingResponse
,FileResponse
更适合传输文件,因为它可以接受更多的参数:
- path:文件路径;
- filename:文件名;将自动添加 Content-Disposition 头信息;
- media_type:文件的 media type;将自动添加 Content-Type 头信息;
- headers:响应头信息;
两者最大的区别是,FileResponse
是异步读取文件的,不会因为一次性读出文件太大而导致内存溢出。
import uvicornfrom fastapi import FastAPI
from fastapi.responses import FileResponseapp = FastAPI()@app.get("/get_file")
async def get_file():file_path = "教程.md"return FileResponse(file_path, media_type="application/octet-stream", filename="教程.md")if __name__ == '__main__':uvicorn.run(app)
三,响应状态码
(一)HTTP 状态码
1xx: 信息响应
状态码 | 含义 | 描述 |
---|---|---|
100 | Continue(继续) | 服务器已收到请求的初始部分,客户端应继续请求。 |
101 | Switching Protocols(切换协议) | 服务器理解并同意客户端的协议切换请求。 |
102 | Processing(处理中) | 服务器已收到并正在处理请求,但无响应可用。 |
103 | Early Hints(预先提示) | 用于与Link头一起返回一些响应头,主要用于预加载。 |
2xx: 成功响应
状态码 | 含义 | 描述 |
---|---|---|
200 | OK(成功) | 请求成功。GET: 资源已被提取并在消息正文中传输。 |
201 | Created(已创建) | 请求成功且服务器创建了新的资源。 |
202 | Accepted(已接受) | 服务器已接受请求,但尚未处理。 |
203 | Non-Authoritative Information(非授权信息) | 服务器已成功处理请求,但返回的信息可能来自另一来源。 |
204 | No Content(无内容) | 服务器成功处理请求,但不需要返回任何实体内容。 |
205 | Reset Content(重置内容) | 服务器成功处理请求,但需要重置文档视图。 |
206 | Partial Content(部分内容) | 服务器成功处理了部分GET请求。 |
3xx: 重定向消息
状态码 | 含义 | 描述 |
---|---|---|
300 | Multiple Choices(多种选择) | 针对请求,服务器可执行多种操作。 |
301 | Moved Permanently(永久移动) | 请求的资源已永久移动到新位置。 |
302 | Found(临时移动) | 请求的资源临时从不同的URI响应请求。 |
303 | See Other(查看其他位置) | 对应当前请求的响应可以在另一个URI上被找到。 |
304 | Not Modified(未修改) | 资源未被修改,可使用缓存版本。 |
307 | Temporary Redirect(临时重定向) | 请求应该被重定向到另一个URI,但将来的请求仍应使用原始URI。 |
308 | Permanent Redirect(永久重定向) | 请求和所有将来的请求应该被重定向到给定的URI。 |
4xx: 客户端错误响应
状态码 | 含义 | 描述 |
---|---|---|
400 | Bad Request(错误请求) | 服务器无法理解请求的语法。 |
401 | Unauthorized(未授权) | 请求要求身份验证。 |
402 | Payment Required(需要付款) | 保留以供将来使用。 |
403 | Forbidden(禁止) | 服务器理解请求但拒绝执行。 |
404 | Not Found(未找到) | 服务器找不到请求的资源。 |
405 | Method Not Allowed(方法不允许) | 请求方法不被允许。 |
406 | Not Acceptable(不可接受) | 服务器无法根据客户端请求的内容特性完成请求。 |
407 | Proxy Authentication Required(需要代理授权) | 请求要求代理的身份认证。 |
408 | Request Timeout(请求超时) | 服务器等候请求时发生超时。 |
409 | Conflict(冲突) | 由于和被请求的资源的当前状态之间存在冲突,请求无法完成。 |
410 | Gone(已删除) | 被请求的资源在服务器上已经不再可用。 |
411 | Length Required(需要有效长度) | 服务器拒绝在没有定义Content-Length头的情况下接受请求。 |
412 | Precondition Failed(前提条件失败) | 服务器在验证在请求的头字段中给出先决条件时,没能满足其中的一个或多个。 |
413 | Payload Too Large(负载过大) | 请求实体过大,超出服务器的处理能力。 |
414 | URI Too Long(URI过长) | 请求的URI过长,服务器无法处理。 |
415 | Unsupported Media Type(不支持的媒体类型) | 请求的格式不受请求页面的支持。 |
416 | Range Not Satisfiable(范围不符合要求) | 页面无法提供请求的范围。 |
417 | Expectation Failed(未满足期望) | 服务器无法满足Expect的请求头信息。 |
429 | Too Many Requests(请求过多) | 用户在给定的时间内发送了太多的请求。 |
5xx: 服务器错误响应
状态码 | 含义 | 描述 |
---|---|---|
500 | Internal Server Error(服务器内部错误) | 服务器遇到了不知道如何处理的情况。 |
501 | Not Implemented(尚未实施) | 服务器不具备完成请求的功能。 |
502 | Bad Gateway(错误网关) | 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。 |
503 | Service Unavailable(服务不可用) | 由于超载或系统维护,服务器暂时的无法处理客户端的请求。 |
504 | Gateway Timeout(网关超时) | 充当网关或代理的服务器,未及时从上游服务器收到请求。 |
505 | HTTP Version Not Supported(HTTP版本不受支持) | 服务器不支持请求中所用的HTTP协议版本。 |
506 | Variant Also Negotiates(变元协商) | 服务器存在内部配置错误。 |
507 | Insufficient Storage(存储空间不足) | 服务器无法存储完成请求所必须的内容。 |
508 | Loop Detected(检测到循环) | 服务器在处理请求时检测到无限循环。 |
510 | Not Extended(未扩展) | 获取资源所需要的策略并没有被满足。 |
511 | Network Authentication Required(要求网络认证) | 客户端需要进行身份验证才能获得网络访问权限。 |
HTTP response status codes
(二)fastapi 中的状态码
在fastapi中,有多种方式指定返回不同的响应码。
1,使用response参数
在create_item 函数中,我们接受一个response: Response
参数,并直接设 response.status_code。
class Item(BaseModel):name: strprice: float@app.post("/items/")
async def create_item(item: Item, response: Response):if item.price < 0:response.status_code = status.HTTP_400_BAD_REQUESTreturn {"error": "Price cannot be negative"}response.status_code = status.HTTP_201_CREATEDreturn {"message": "Item created successfully", "item": item}
这种方法允许在函数的任何位置设置状态码,同时仍然返回您想要的内容。
2,抛出HTTPException
在read_item函数中,我们使用raise HTTPException来设置状态码和错误详情。
@app.get("/items/{item_id}")
async def read_item(item_id: int):if item_id == 0:raise HTTPException(status_code=404, detail="Item not found")return {"item_id": item_id, "name": "Sample Item"}
这种方法特别适合处理错误情况,因为它会自动停止函数的执行并返回错误响应。
3,返回Response对象
在update_item函数中,我们直接返回一个Response对象,其中包含内容和状态码。
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):if item_id == 0:return Response(content="Item not found", status_code=status.HTTP_404_NOT_FOUND)if item.price > 1000:return Response(content="Price too high", status_code=status.HTTP_400_BAD_REQUEST)return Response(content=f"Item {item_id} updated", status_code=status.HTTP_200_OK)
这种方法给了开发者对响应的完全控制,包括内容和状态码。
4,使用装饰器设置默认状态码
在delete_item函数中,我们使用@app.delete(…, status_code=status.HTTP_204_NO_CONTENT)来设置默认状态码。
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item(item_id: int, response: Response):if item_id == 0:response.status_code = status.HTTP_404_NOT_FOUNDreturn {"error": "Item not found"}# 如果成功删除,不需要返回内容,状态码会是204return None
如果需要更改状态码,我们仍然可以使用response参数来实现。
5,一些注意事项
- 使用status模块(如status.HTTP_400_BAD_REQUEST)可以提高代码的可读性。
- 对于错误处理,HTTPException通常是最清晰和最符合FastAPI风格的选择。
- 当您需要完全控制响应时,返回Response对象是一个好方法。
- 当设置了一个非200的状态码时,通常应该返回一些解释性的内容。
四,处理异常
在处理请求出错的情况下,需要通过响应来向客户端返回错误提示。
(一)使用 HTTPException
FastAPI的HTTPException
是一个用于在API中引发HTTP错误的异常类。它允许你在代码中明确地抛出HTTP错误,以便向客户端返回适当的错误响应。
from fastapi import FastAPI, HTTPExceptionapp = FastAPI()@app.get("/items/{item_id}")
async def read_item(item_id: int):if item_id not in some_items_db:raise HTTPException(status_code=404, detail="Item not found")return {"item": some_items_db[item_id]}
HTTPException 是额外包含了和 API 有关数据的常规 Python 异常。因为是 Python 异常,所以不能 return,只能 raise。
如在调用路径操作函数里的工具函数时,触发了 HTTPException,FastAPI
就不再继续执行路径操作函数中的后续代码,而是立即终止请求,并把HTTPException 的 HTTP 错误发送至客户端。
主要参数:
- status_code: HTTP状态码(如404, 400, 500等)
- detail: 错误的详细描述
- headers: 可选的额外响应头
(二)全局异常
代码中通常要处理大量异常,如果所有异常都 raise HTTPException
,那么代码将更加复杂,因此 fastapi 提供了全局异常处理器来自定义不同类型的异常,实现逻辑代码与异常处理代码的分离。
import uvicorn
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponseapp = FastAPI()# 自定义异常类
class CustomException(Exception):def __init__(self, name: str):self.name = name# 为HTTPException定义异常处理器
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):return JSONResponse(status_code=exc.status_code,content={"message": f"HTTP error occurred: {exc.detail}"})# 为CustomException定义异常处理器
@app.exception_handler(CustomException)
async def custom_exception_handler(request: Request, exc: CustomException):return JSONResponse(status_code=418,content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."})# 路由处理函数,可能会抛出HTTPException
@app.get("/items/{item_id}")
async def read_item(item_id: int):if item_id == 3:raise HTTPException(status_code=404, detail="Item not found")return {"item_id": item_id}# 路由处理函数,可能会抛出CustomException
@app.get("/unicorns/{name}")
async def read_unicorn(name: str):if name == "yolo":raise CustomException(name=name)return {"unicorn_name": name}# 全局异常处理器
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):return JSONResponse(status_code=500,content={"message": f"An unexpected error occurred: {str(exc)}"})if '__main__' == __name__:uvicorn.run(app, host='127.0.0.1', port=8088)
解释一下这个代码示例的主要部分:
-
异常处理器装饰器:
@app.exception_handler(ExceptionType) 用来定义一个异常处理器。ExceptionType 可以是任何异常类,如 HTTPException, CustomException, 或者甚至是基础的 Exception 类。 -
异常处理器函数:
这些函数接收两个参数: request (当前的请求对象) 和 exc (捕获到的异常实例)。它们返回一个响应对象,通常是 JSONResponse。 -
HTTPException 处理器:
这个处理器捕获所有的 HTTPException,并返回一个包含状态码和错误消息的 JSON 响应。 -
自定义异常处理器:
我们定义了一个 CustomException 类和相应的处理器。这展示了如何处理应用特定的异常。 -
路由处理函数:
示例中包含了两个路由处理函数,分别可能抛出 HTTPException 和 CustomException。 -
全局异常处理器:
最后,我们定义了一个处理所有未被其他处理器捕获的异常的全局处理器。