FastAPI
Intro
HTTP 请求与响应
一次 HTTP 通信可以分成请求和响应。请求由请求行、请求头、空行和请求体组成;响应由状态行、响应头、空行和响应体组成。
请求的组成
| 部分 | 作用 | HTTP 示例 | DeepSeek 请求中的例子 |
|---|---|---|---|
| 请求行 | 指定 HTTP 方法、路径和协议版本 | GET /api/health HTTP/2 | POST /chat/completions HTTP/2 |
| 请求头 | 描述请求及客户端信息 | Content-Type: application/json | Content-Type: application/json、Authorization: Bearer ${DEEPSEEK_API_KEY} |
| 空行 | 标记请求头结束 | — | curl 在请求头后自动添加分隔空行 |
| 请求体 | 携带要提交的数据;有些请求没有请求体 | JSON、表单数据等 | {"model":"deepseek-flash","messages":[...]} |
响应的组成
| 部分 | 作用 | HTTP 示例 | DeepSeek 响应中的例子 |
|---|---|---|---|
| 状态行 | 返回协议版本、状态码和状态描述 | HTTP/1.1 200 OK | HTTP/2 200 |
| 响应头 | 描述响应内容及服务器信息 | Content-Type: application/json | content-type: application/json、server: openresty |
| 空行 | 标记响应头结束 | — | 响应头与响应体之间的分隔行 |
| 响应体 | 返回实际内容 | JSON、HTML、文本等 | {"id":"...","choices":[...],"usage":{...}} |
常见状态码类别:2xx 表示请求成功,4xx 表示请求有问题,5xx 表示服务端处理失败。
常见请求头
| 请求头 | 含义 | DeepSeek 请求中的例子 |
|---|---|---|
Host | 请求访问的主机 | api.deepseek.com |
User-Agent | 发起请求的客户端或工具 | curl/7.81.0 |
Accept | 客户端可以接收的响应格式 | */* |
Accept-Language | 客户端偏好的语言 | 未发送 |
Content-Type | 请求体的数据格式 | application/json |
Content-Length | 请求体的长度 | 299(原始日志中的值) |
Authorization | 身份凭证,例如 Bearer Token | Bearer ${DEEPSEEK_API_KEY}(实际值应保密) |
Cookie | 随请求发送的 Cookie | 未发送 |
常见响应头
| 响应头 | 含义 | DeepSeek 响应中的例子 |
|---|---|---|
Content-Type | 响应体的数据格式 | application/json |
Content-Length | 响应体的长度 | 日志中未显示 |
Server | 服务端软件信息 | openresty |
Date | 响应生成时间 | Sat, 26 Sep 2026 04:42:10 GMT |
Cache-Control | 缓存策略 | 日志中未显示 |
Set-Cookie | 服务端要求客户端保存的 Cookie | 日志中未显示 |
Location | 重定向目标地址 | 日志中未显示 |
Access-Control-Allow-Origin | 允许跨域访问的来源 | 日志中未显示;该响应还包含 vary: origin, access-control-request-method, access-control-request-headers |
示例:用 curl 请求 DeepSeek API
下面通过 curl 向 DeepSeek 的 Chat Completions 接口发送 JSON 请求。-v 会输出连接、TLS 和 HTTP 交互过程,便于观察请求是如何发出的。
先将 API Key 放进环境变量,避免把密钥直接写入命令或笔记:
export DEEPSEEK_API_KEY="你的 API Key"
发送请求:
curl -v https://api.deepseek.com/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${DEEPSEEK_API_KEY}" \
-d '{
"model": "deepseek-flash",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"}
],
"thinking": {"type": "enabled"},
"reasoning_effort": "high",
"stream": false
}'
请求参数
| 字段 | 含义 |
|---|---|
model | 指定要调用的模型 |
messages | 对话消息列表;system 提供指令,user 提供用户输入 |
thinking | 设置是否启用思考模式 |
reasoning_effort | 指定推理强度 |
stream | 是否以流式方式返回;false 表示等完整结果生成后再返回 |
Content-Type | 声明请求体是 JSON |
Authorization | 使用 Bearer Token 传递 API Key |
curl -v 输出怎么看
| 标记 | 示例 | 含义 |
|---|---|---|
* | * Connected to ... | curl 的过程信息,例如代理连接、TLS 握手、上传进度;这些行不是 HTTP 请求或响应正文 |
> | > POST /chat/completions HTTP/2 | curl 发给服务器的请求行和请求头 |
-d 参数 | "model": "deepseek-flash" 等 JSON 字段 | 这是发送的请求体;curl -v 通常不会逐行打印请求体 |
< | < HTTP/2 200 | 服务器返回的状态行;200 表示请求成功 |
< | < content-type: application/json | 服务器返回的响应头 |
| 无前缀 | {"id":"...","choices":[...]} | 响应体,也就是模型返回的数据 |
原始输出中的 * 行通常按连接顺序描述过程,例如:
| 阶段 | 常见输出 | 说明 |
|---|---|---|
| 代理配置 | * Uses proxy env variable ... | curl 检测到并使用代理环境变量 |
| 建立连接 | * Trying ...、* Connected to ... | 尝试连接代理或目标服务器,并确认连接建立 |
| 建立隧道 | * Establish HTTP proxy tunnel ... | 通过 HTTP 代理建立到 HTTPS 服务的隧道 |
| TLS 握手 | * TLS handshake ...、* SSL connection using ... | 协商加密连接并验证服务器证书 |
| 选择协议 | * ALPN ...、* server accepted to use h2 | 协商使用 HTTP/2 或 HTTP/1.1 |
| 发送请求 | * We are completely uploaded and fine | 请求体已经发送完成 |
| 接收响应 | < HTTP/2 200 | 服务端返回成功状态 |
| 结束连接 | * Connection ... left intact | curl 完成请求并处理连接 |
创建自己的 API
下面用同一个 /api/hello 接口对比 Python 标准库和 FastAPI 的写法。
使用 Python 标准库 http.server
from http.server import BaseHTTPRequestHandler, HTTPServer
import json
hello = {
"A": "Hello",
"B": "World",
}
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/api/hello":
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
body = json.dumps(hello, ensure_ascii=True)
self.wfile.write(body.encode("utf-8"))
else:
self.send_response(404)
self.end_headers()
print("backend starts: http://localhost:8080/api/hello")
HTTPServer(("", 8080), Handler).serve_forever()
这里需要自己判断请求路径、设置状态码和响应头、把数据序列化成 JSON,再将响应内容编码并写入连接。
Python 常见的后端 Web 框架有:
| 框架 | 简介 |
|---|---|
| Flask | 轻量、灵活的 Web 框架 |
| Django | 功能完整的 Web 框架 |
| FastAPI | 基于类型注解构建 API,支持自动生成接口文档 |
FastAPI 官方文档:https://fastapi.tiangolo.com/
使用 FastAPI
from fastapi import FastAPI
app = FastAPI()
hello = {
"A": "Hello",
"B": "World",
}
@app.get("/api/hello")
def get_hello():
return hello
用 Uvicorn 启动应用:
uvicorn main:app --host 127.0.0.1 --port 8080 --reload
main:app 表示从 main.py 中加载名为 app 的 FastAPI 实例。--reload 会在开发时监视代码文件,文件变化后自动重启服务。
FastAPI 自动完成了什么?
| 工作 | 标准库写法 | FastAPI 写法 |
|---|---|---|
| 匹配请求方法和路径 | 在 do_GET 中手动检查 self.path | 根据 @app.get("/api/hello") 匹配 |
| 生成响应状态 | 调用 send_response(200) | 默认返回 200 OK,也可以通过参数指定状态码 |
| 设置响应头 | 手动调用 send_header | 返回字典时自动生成 JSON 响应头,如 Content-Type: application/json |
| 把 Python 数据转为响应内容 | 手动 json.dumps、编码并写入 wfile | 将返回的字典转换为 JSON 响应 |
| 处理错误路径 | 手动返回 404 | 没有匹配的路由时自动返回 404 Not Found |
| 生成接口文档 | 自己编写和维护 | 根据路由和类型信息生成 OpenAPI 文档,可通过 /docs 查看交互式文档 |
FastAPI 还可以根据函数参数的类型和声明解析、校验查询参数、路径参数和请求体;校验失败时会生成相应的错误响应。当前 get_hello() 没有参数,因此这个例子没有展示参数校验。
FastAPI 负责应用层的路由、数据处理和响应构造;Uvicorn 负责运行 ASGI 应用、监听端口并处理 HTTP 与 ASGI 之间的通信。
@app.get("/api/hello") 装饰器的作用
@app.get("/api/hello") 会在应用创建时把 get_hello 函数注册为路由处理函数,并声明它处理 GET /api/hello 请求。收到匹配请求时,FastAPI 调用 get_hello(),再把返回值转换成 HTTP 响应。
可以把它理解成下面这种显式注册的简写:
def get_hello():
return hello
app.add_api_route("/api/hello", get_hello, methods=["GET"])
装饰器不会在定义函数时立刻调用 get_hello();它是在应用启动、导入模块时登记路由,等请求到来后才调用处理函数。类似地,@app.post(...)、@app.put(...) 和 @app.delete(...) 分别用于注册其他 HTTP 方法的路由。
POST:接收 JSON 请求体
POST 常用于向服务端提交数据。请求 JSON 会放在 HTTP 请求体中;测试时需要发送 Content-Type: application/json,告诉服务端请求体格式是 JSON。
用 dict 接收请求体
from fastapi import FastAPI
app = FastAPI()
@app.post("/api/post_hello")
def post_hello(req: dict):
return {
"text": req["text"],
"id": 10086,
}
req: dict 中的类型注解告诉 FastAPI:req 是从请求体解析出的 JSON 对象。FastAPI 会读取 JSON 并将其转成 Python 字典,因此可以用 req["text"] 访问字段。普通 dict 不会声明具体有哪些字段,也不会像 Pydantic 模型那样对字段类型做细致校验。
测试请求:
curl -X POST "http://127.0.0.1:8000/api/post_hello" \
-H "Content-Type: application/json" \
-d '{"text":"Hello", "other":"none"}'
注意 JSON 字符串中的键和值要用英文双引号,字段之间用英文逗号。
用 Pydantic 声明请求模型
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class AnalyzeRequest(BaseModel):
text: str
other: str
@app.post("/api/post_hello")
def post_hello(req: AnalyzeRequest):
return {
"text": req.text,
"id": 10086,
}
Pydantic 模型明确声明请求体需要 text 和 other 两个字符串字段。FastAPI 会根据模型解析并校验请求体;模型也会用于生成 OpenAPI 接口文档,因此 /docs 会展示请求字段和类型。字段缺失或类型不符合要求时,FastAPI 会返回校验错误。
读取原始 Request
如果需要访问请求方法、完整 URL、请求头、查询参数或原始请求体,可以直接接收 Request:
用 Request 时,FastAPI 仍负责路由、接收 HTTP 请求和发送响应,但请求体由你手动读取和解析
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/api/post_hello")
async def post_hello(req: Request):
raw_body = await req.body()
body = await req.json()
return {
"method": req.method,
"url": str(req.url),
"headers": dict(req.headers),
"query_params": dict(req.query_params),
"raw_body": raw_body.decode("utf-8"),
"body": body,
"id": 10086,
}
Request 是 Starlette 提供的请求对象。读取 body 的接口是异步的,所以处理函数使用 async def,并通过 await 读取。与 Pydantic 模型不同,直接使用 Request 时,FastAPI 不会根据请求体自动生成字段 schema 或执行模型校验;需要自己解析和检查数据。
用前面的 curl 命令发送 {"text":"Hello", "other":"cc"},响应结构类似:
{
"method": "POST",
"url": "http://127.0.0.1:8000/api/post_hello",
"headers": {
"host": "127.0.0.1:8000",
"user-agent": "curl/7.81.0",
"accept": "*/*",
"content-type": "application/json",
"content-length": "30"
},
"query_params": {},
"raw_body": "{\"text\":\"Hello\", \"other\":\"cc\"}",
"body": {
"text": "Hello",
"other": "cc"
},
"id": 10086
}
| 写法 | 适用场景 | 自动校验与文档 |
|---|---|---|
req: dict | 快速接收结构简单、字段不固定的 JSON | 知道请求体是对象,但字段定义较宽泛 |
req: AnalyzeRequest | 请求字段明确,希望校验数据并生成清晰文档 | 根据 Pydantic 模型校验,并把字段写入 OpenAPI 文档 |
req: Request | 需要访问原始请求、请求头或自定义解析 | 请求体解析和校验由代码自行负责 |