网络编程基础概念笔记
1. OSI 模型与网络层次
1.1 什么是 OSI 模型
OSI(Open Systems Interconnection,开放系统互联)模型 是由国际标准化组织(ISO)制定的网络通信参考模型,将网络通信过程分为七个层次,每层负责特定的功能。
1.2 OSI 模型的七层
- 物理层(Physical Layer):
- 传输原始的比特流,涉及硬件设备、信号传输等。
- 数据链路层(Data Link Layer):
- 负责在物理层上建立可靠的数据传输,处理帧的传输和错误检测。
- 网络层(Network Layer):
- 负责数据包的传输、路由选择和 IP 地址管理。
- 传输层(Transport Layer):
- 提供端到端的通信服务,如 TCP 和 UDP 协议。
- 会话层(Session Layer):
- 管理用户会话,控制通信双方的对话和同步。
- 表示层(Presentation Layer):
- 负责数据的格式转换、加密和压缩。
- 应用层(Application Layer):
- 直接与用户应用交互,提供网络服务,如 HTTP、FTP、SMTP 等协议。
1.3 HTTP 在 OSI 模型中的位置
HTTP 属于 应用层(第七层),负责处理应用程序之间的数据通信和交互。它依赖于下层的协议,如 TCP(传输层)、IP(网络层)和数据链路层协议进行数据传输。
1.4 OSI 模型的应用
- 网络设计与故障排除:帮助理解不同协议和设备在通信过程中的作用,便于诊断和解决网络问题。
- 协议开发与标准化:指导新协议的设计,确保不同设备和系统之间的互操作性。
- 教育与培训:作为网络通信概念的教学工具,帮助学习者系统地理解网络层次结构。
1.5 OSI 模型与 TCP/IP 模型的比较
尽管 OSI 模型是理论上的参考模型,实际网络通信更多基于 TCP/IP 模型。TCP/IP 模型简化为四层:
- 网络接口层(对应 OSI 的物理层和数据链路层)
- 互联网层(对应 OSI 的网络层)
- 传输层(对应 OSI 的传输层)
- 应用层(对应 OSI 的会话层、表示层和应用层)
比较表:
OSI 模型层次 | TCP/IP 模型层次 | 主要协议与功能 |
---|---|---|
7. 应用层 | 4. 应用层 | HTTP, FTP, SMTP, DNS, etc. |
6. 表示层 | 数据格式转换、加密、压缩 | |
5. 会话层 | 管理会话和连接 | |
4. 传输层 | 3. 传输层 | TCP, UDP |
3. 网络层 | 2. 互联网层 | IP, ICMP, IGMP |
2. 数据链路层 | 1. 网络接口层 | Ethernet, Wi-Fi, PPP, etc. |
1. 物理层 | 硬件设备、信号传输 |
2. TCP 协议
2.1 什么是 TCP 协议
TCP(Transmission Control Protocol,传输控制协议) 是一种面向连接的、可靠的、基于字节流的传输层协议。它位于 OSI 模型的第四层,负责在网络中的两台计算机之间建立连接,并确保数据的可靠传输。
关键特性:
- 面向连接:在数据传输之前,必须在通信双方之间建立一个可靠的连接。
- 可靠性:通过数据包序列号、确认应答、重传机制等确保数据按序、完整地传输。
- 流量控制:通过滑动窗口机制防止发送方过快发送数据,避免接收方缓冲区溢出。
- 拥塞控制:动态调整数据传输速率,以应对网络拥塞,避免网络过载。
2.2 TCP 与 HTTP 的关系
- 底层传输:HTTP 协议依赖于 TCP 协议进行数据传输。HTTP 报文被封装在 TCP 报文段中,通过 TCP 连接传输。
- 可靠传输:由于 TCP 提供可靠的数据传输,HTTP 数据能够准确、按序地到达目的地。
- 连接管理:HTTP/1.1 引入了持久连接(默认开启),可以在一个 TCP 连接上发送多个 HTTP 请求/响应,减少连接建立和关闭的开销。
2.3 TCP 连接过程
TCP 连接的建立和断开遵循 三次握手(Three-Way Handshake) 和 四次挥手(Four-Way Handshake) 的过程。
建立连接(三次握手):
- SYN:客户端发送一个 SYN(同步)报文段,告诉服务器客户端打算建立连接,并初始化序列号。
- SYN-ACK:服务器回应一个 SYN-ACK 报文段,表示同意建立连接,并回复客户端的序列号,同时自己也初始化序列号。
- ACK:客户端发送一个 ACK 报文段,确认收到服务器的 SYN-ACK,连接建立完成。
断开连接(四次挥手):
- FIN:主动关闭连接的一方发送一个 FIN(终止)报文段,表示不再发送数据。
- ACK:被动关闭连接的一方回应一个 ACK,确认收到 FIN。
- FIN:被动关闭连接的一方发送一个 FIN,表示也不再发送数据。
- ACK:主动关闭连接的一方回应一个 ACK,连接完全断开。
示例图示:
建立连接:
Client -> SYN -> Server
Client <- SYN-ACK <- Server
Client -> ACK -> Server
连接建立
断开连接:
Client -> FIN -> Server
Client <- ACK <- Server
Client <- FIN <- Server
Client -> ACK -> Server
连接断开
3. UDP 协议
3.1 什么是 UDP 协议
UDP(User Datagram Protocol,用户数据报协议) 是一种无连接的传输层协议,与 TCP(Transmission Control Protocol) 相对。UDP 主要用于需要低延迟和高吞吐量的应用,如视频流、在线游戏和 DNS 查询。
3.2 UDP 的特点
- 无连接:发送方和接收方之间不建立连接,数据包(数据报)直接发送。
- 不可靠:不保证数据包的送达、顺序和完整性。数据包可能会丢失、重复或乱序。
- 低开销:由于没有连接建立和维护,UDP 的头部较小(8 字节),开销低。
- 支持广播和多播:可以向多个目标发送数据包。
3.3 UDP 的应用场景
- 实时应用:如视频会议、在线游戏,需要快速传输,容忍一定的数据丢失。
- DNS 查询:快速的请求响应,通常一次性通信。
- 流媒体传输:音视频流需要持续传输,偶尔丢失数据不影响整体体验。
3.4 UDP 与 TCP 的比较
特性 | TCP | UDP |
---|---|---|
连接性 | 面向连接 | 无连接 |
可靠性 | 可靠,保证数据顺序和完整性 | 不可靠,可能丢失、重复或乱序 |
流量控制 | 有流量控制和拥塞控制机制 | 无流量控制和拥塞控制机制 |
速度 | 较慢,因为有额外的控制开销 | 较快,开销低 |
应用场景 | HTTP、FTP、SMTP、数据库连接等 | DNS、视频流、在线游戏、实时通信等 |
4. IP 地址(IPv4 和 IPv6)
4.1 什么是 IP 地址
IP 地址(Internet Protocol Address) 是分配给网络中每个设备的唯一标识符,用于在网络中定位和通信。IP 地址分为 IPv4 和 IPv6 两种版本。
4.2 IPv4
- 地址格式:32 位,通常表示为四个十进制数(0-255),以点分十进制形式表示,如
192.168.1.1
。 - 地址数量:约 43 亿个(2^32)。
- 问题:地址枯竭,无法满足全球互联网设备的增长需求。
- NAT 使用:广泛使用 NAT 解决地址不足问题。
4.3 IPv6
- 地址格式:128 位,通常表示为八组十六进制数,以冒号分隔,如
2001:0db8:85a3:0000:0000:8a2e:0370:7334
。 - 地址数量:约 3.4×10^38 个(2^128),足够应对未来互联网需求。
- 优势:
- 无需 NAT:每个设备可以拥有一个唯一的公网 IP 地址,简化网络配置。
- 更高的安全性:内置了 IPsec 加密支持。
- 更高的性能:改进了路由效率,减少了路由器负载。
- 迁移挑战:需要更新硬件和软件,兼容性问题。
4.4 在 FastAPI 项目中处理 IPv4 与 IPv6
FastAPI 和 Uvicorn 都支持 IPv4 和 IPv6 地址。您可以通过配置绑定地址,指定应用监听的 IP 版本。
示例:绑定到所有 IPv4 和 IPv6 地址
uvicorn app.main:app --host 0.0.0.0 --port 8000
IPv6 示例:
uvicorn app.main:app --host :: --port 8000
配置示例代码
app/main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_root():
return {"message": "Hello World"}
5. NAT(网络地址转换)
5.1 什么是 NAT
NAT(Network Address Translation,网络地址转换) 是一种网络技术,通过修改网络数据包的源或目的 IP 地址,实现多个设备共享一个公共 IP 地址。NAT 主要用于 IPv4 地址资源紧张的情况。
5.2 NAT 的类型
- 静态 NAT
- 一一映射私有 IP 地址到公共 IP 地址,适用于需要固定公共 IP 的服务器。
- 动态 NAT
- 动态分配公共 IP 地址给私有网络中的设备,公共 IP 数量需大于私有 IP。
- PAT(Port Address Translation)或 NAPT(Network Address Port Translation)
- 通过修改端口号,实现多个私有 IP 地址共享一个公共 IP 地址。
- 最常见的 NAT 类型,通常称为“端口转发”或“端口映射”。
5.3 NAT 的优缺点
特性 | 优点 | 缺点 |
---|---|---|
资源节约 | 节约公共 IP 地址资源,适用于 IPv4 | 需要维护 NAT 表,增加设备负载 |
隐私保护 | 隐藏内部网络结构,增强隐私保护 | 影响某些协议和服务,如 VoIP、P2P |
配置复杂性 | 无显著增加 | 需要额外配置,可能导致网络问题 |
支持多设备连接 | 允许多个设备共享一个公共 IP 地址 | 不支持端到端的直接通信,增加延迟和复杂性 |
6. 端口号与知名端口
6.1 什么是端口号
端口号(Port Number) 是用于标识计算机上的特定进程或网络服务的数字。端口号在传输层(TCP/UDP)用于区分不同的服务和应用。
6.2 端口号的范围
- 知名端口(Well-Known Ports)
- 范围:0-1023
- 用途:预定义的服务和协议,如 HTTP(80)、HTTPS(443)、FTP(21)、SSH(22)、SMTP(25)。
- 注册端口(Registered Ports)
- 范围:1024-49151
- 用途:由软件公司和应用程序注册使用,如 MySQL(3306)、PostgreSQL(5432)、Redis(6379)。
- 动态或私有端口(Dynamic or Private Ports)
- 范围:49152-65535
- 用途:临时分配给客户端应用程序,用于会话通信。
6.3 常见的知名端口
服务/协议 | 端口号 | 描述 |
---|---|---|
HTTP | 80 | 超文本传输协议 |
HTTPS | 443 | 安全超文本传输协议 |
FTP | 21 | 文件传输协议 |
SSH | 22 | 安全外壳协议 |
SMTP | 25 | 简单邮件传输协议 |
DNS | 53 | 域名系统 |
MySQL | 3306 | MySQL 数据库服务器 |
PostgreSQL | 5432 | PostgreSQL 数据库服务器 |
Redis | 6379 | Redis 内存数据存储 |
MongoDB | 27017 | MongoDB NoSQL 数据库 |
Telnet | 23 | 远程登录协议 |
6.4 在 FastAPI 中使用不同端口
在开发和生产环境中,可能需要让 FastAPI 应用监听不同的端口,以支持多个服务或实例。
示例:启动 FastAPI 在不同端口
-
启动在端口 8000:
uvicorn app.main:app --host 0.0.0.0 --port 8000
-
启动在端口 8080:
uvicorn app.main:app --host 0.0.0.0 --port 8080
使用 Docker 启动多个实例
通过 Docker Compose,可以轻松管理多个 FastAPI 实例,分别监听不同的端口,并通过反向代理进行负载均衡。
docker-compose.yml
version: '3.8'
services:
app1:
build: .
command: uvicorn app.main:app --host 0.0.0.0 --port 8001
ports:
- "8001:8001"
app2:
build: .
command: uvicorn app.main:app --host 0.0.0.0 --port 8002
ports:
- "8002:8002"
app3:
build: .
command: uvicorn app.main:app --host 0.0.0.0 --port 8003
ports:
- "8003:8003"
nginx:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- app1
- app2
- app3
Nginx 配置
nginx.conf
http {
upstream fastapi_app {
server app1:8001;
server app2:8002;
server app3:8003;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://fastapi_app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
解释:
upstream fastapi_app
:定义一个服务器组,包含多个 FastAPI 实例。proxy_pass http://fastapi_app;
:将请求转发到服务器组,实现负载均衡。proxy_set_header
:设置必要的头信息,确保后端服务器获取正确的请求上下文。
7. DNS(域名系统)
7.1 什么是 DNS
DNS(Domain Name System,域名系统) 是互联网的“电话簿”,将易于记忆的域名(如 www.example.com
)转换为机器可读的 IP 地址(如 93.184.216.34
)。
7.2 DNS 的工作原理
- 查询过程:
- 当用户在浏览器中输入域名时,浏览器首先检查本地缓存是否有该域名的 IP 地址。
- 如果没有,操作系统会向配置的 DNS 服务器发送查询请求。
- DNS 服务器可能会递归查询其他 DNS 服务器,直到找到相应的 IP 地址。
- 最终,IP 地址返回给用户的设备,浏览器使用该 IP 地址建立连接。
- DNS 记录类型:
- A 记录:将域名映射到 IPv4 地址。
- AAAA 记录:将域名映射到 IPv6 地址。
- CNAME 记录:别名记录,将一个域名指向另一个域名。
- MX 记录:邮件交换记录,指定邮件服务器。
- TXT 记录:文本记录,用于各种验证和配置,如 SPF、DKIM。
- NS 记录:指定域名的权威 DNS 服务器。
7.3 DNS 服务器类型
- 递归解析器:
- 接受客户端的 DNS 查询,负责递归查找最终的 IP 地址。
- 权威 DNS 服务器:
- 对特定域名负责,提供最终的 DNS 记录响应。
- 根 DNS 服务器:
- 位于 DNS 层次结构的顶端,指向各个顶级域(如
.com
,.org
)的权威 DNS 服务器。
- 位于 DNS 层次结构的顶端,指向各个顶级域(如
7.4 DNS 缓存
为了提高查询效率和减少网络流量,DNS 使用缓存机制:
- 本地缓存:操作系统和浏览器会缓存 DNS 查询结果。
- DNS 缓存服务器:ISP 或组织内部的 DNS 服务器会缓存查询结果。
7.5 DNS 安全扩展(DNSSEC)
DNSSEC 为 DNS 查询提供了安全性,通过数字签名确保 DNS 响应的完整性和真实性,防止 DNS 欺骗和篡改攻击。
8. HTTP 协议及其特点
8.1 什么是 HTTP 协议
HTTP(HyperText Transfer Protocol,超文本传输协议) 是一种用于分布式、协作式和超媒体信息系统的应用层协议。它是 Web 的基础,用于在客户端(通常是浏览器)和服务器之间传输超文本数据,如 HTML、CSS、JavaScript 以及其他资源。
关键点:
- 无状态协议:每个请求都是独立的,服务器不会保留关于客户端的任何状态信息。
- 基于请求-响应模式:客户端发送请求,服务器返回响应。
- 应用层协议:位于 OSI 模型的第七层,依赖于传输层协议(如 TCP)进行数据传输。
8.2 HTTP 协议的特点
- 简单性:HTTP 协议设计简洁,易于理解和实现。使用明文传输,便于调试和测试。
- 无状态性:每个 HTTP 请求都是独立的,服务器不会记住之前的请求。这使得协议更加简洁,但也带来了需要管理状态的挑战(如使用 Cookie 或 Token)。
- 灵活性:HTTP 支持多种数据格式和传输方式,可以通过扩展方法和头信息实现更多功能。
- 可扩展性:通过头信息(Headers)和方法(Methods)的扩展,可以不断增加新的功能和特性。
- 基于文本:HTTP 报文使用可读的文本格式,易于理解和调试。
- 无连接与持久连接:
- 无连接:默认情况下,每个请求/响应周期完成后连接就关闭了。
- 持久连接(HTTP/1.1 默认):可以在一个连接上发送多个请求/响应,减少握手开销。
8.3 HTTP 方法
HTTP 定义了一系列的方法,用于指定对资源执行的操作。常见的方法包括:
- GET:请求指定的资源。用于获取数据,不应有副作用(即不修改服务器上的资源)。
- POST:向指定资源提交数据,可能会导致服务器状态的改变(如创建新资源)。
- PUT:上传指定资源的最新内容,用于更新资源或创建资源(如果资源不存在)。
- DELETE:删除指定资源。
- PATCH:对资源进行部分修改。
- HEAD:与 GET 类似,但不返回消息体,用于获取报头信息。
- OPTIONS:描述目标资源的通信选项,常用于跨域请求预检。
- TRACE:回显服务器收到的请求,主要用于诊断。
示例:
GET /users/1 HTTP/1.1
Host: example.com
9. HTTP 请求和响应格式
9.1 HTTP 请求格式
一个完整的 HTTP 请求由以下部分组成:
-
请求行(Request Line):包含请求方法、请求 URI 和协议版本。
GET /path/resource HTTP/1.1
-
请求头(Headers):一组键值对,提供关于客户端环境和请求正文的附加信息。
Host: example.com User-Agent: Mozilla/5.0 Accept: text/html,application/xhtml+xml
-
空行:请求头和请求体之间的空行,用于分隔。
-
请求体(Body):包含要发送到服务器的数据,通常用于 POST、PUT 等方法。
{ "name": "Alice", "email": "alice@example.com" }
完整示例:
POST /users HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0
Content-Type: application/json
Content-Length: 51
{
"name": "Alice",
"email": "alice@example.com"
}
9.2 HTTP 响应格式
一个完整的 HTTP 响应由以下部分组成:
-
状态行(Status Line):包含协议版本、状态码和状态消息。
HTTP/1.1 200 OK
-
响应头(Headers):一组键值对,提供关于服务器环境和响应正文的附加信息。
Content-Type: application/json Content-Length: 27
-
空行:响应头和响应体之间的空行,用于分隔。
-
响应体(Body):包含要发送到客户端的数据。
{ "id": 1, "name": "Alice" }
完整示例:
HTTP/1.1 201 Created
Content-Type: application/json
Content-Length: 38
{
"id": 1,
"name": "Alice",
"email": "alice@example.com"
}
9.3 示例解析
请求示例解析:
POST /users HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0
Content-Type: application/json
Content-Length: 51
{
"name": "Alice",
"email": "alice@example.com"
}
- 请求行:
- 方法:
POST
- URI:
/users
- 版本:
HTTP/1.1
- 方法:
- 请求头:
Host
:example.com
(指定目标服务器)User-Agent
:Mozilla/5.0
(客户端软件信息)Content-Type
:application/json
(请求体的数据类型)Content-Length
:51
(请求体的长度)
- 请求体:
- 包含 JSON 格式的用户信息。
响应示例解析:
HTTP/1.1 201 Created
Content-Type: application/json
Content-Length: 38
{
"id": 1,
"name": "Alice",
"email": "alice@example.com"
}
- 状态行:
- 版本:
HTTP/1.1
- 状态码:
201
- 状态消息:
Created
(表示资源已成功创建)
- 版本:
- 响应头:
Content-Type
:application/json
(响应体的数据类型)Content-Length
:38
(响应体的长度)
- 响应体:
- 包含 JSON 格式的用户信息,包括新创建的用户 ID。
10. HTTP 状态码
10.1 什么是 HTTP 状态码
HTTP 状态码(HTTP Status Codes) 是服务器在响应 HTTP 请求时返回的三位数代码,用于指示请求的处理结果和状态。
10.2 状态码分类
- 1xx:信息性状态码
- 表示请求已接收,继续处理。
- 示例:
100 Continue
- 2xx:成功状态码
- 表示请求已成功被服务器接收、理解和接受。
- 示例:
200 OK
:请求成功。201 Created
:请求成功,服务器创建了新的资源。204 No Content
:请求成功,但没有内容返回。
- 3xx:重定向状态码
- 表示需要客户端进一步操作以完成请求。
- 示例:
301 Moved Permanently
:资源已永久移动到新位置。302 Found
:临时重定向。304 Not Modified
:资源未修改,使用缓存。
- 4xx:客户端错误状态码
- 表示请求包含语法错误或无法完成。
- 示例:
400 Bad Request
:请求语法错误。401 Unauthorized
:未授权,需要认证。403 Forbidden
:服务器拒绝请求。404 Not Found
:请求的资源不存在。
- 5xx:服务器错误状态码
- 表示服务器未能完成合法的请求。
- 示例:
500 Internal Server Error
:服务器内部错误。502 Bad Gateway
:无效的网关。503 Service Unavailable
:服务不可用。504 Gateway Timeout
:网关超时。
10.3 常见的 HTTP 状态码
状态码 | 描述 |
---|---|
200 | OK - 请求成功 |
201 | Created - 资源已创建 |
204 | No Content - 无内容 |
301 | Moved Permanently - 永久移动 |
302 | Found - 临时重定向 |
304 | Not Modified - 未修改 |
400 | Bad Request - 请求有误 |
401 | Unauthorized - 未授权 |
403 | Forbidden - 禁止访问 |
404 | Not Found - 未找到 |
500 | Internal Server Error - 服务器内部错误 |
502 | Bad Gateway - 无效的网关 |
503 | Service Unavailable - 服务不可用 |
504 | Gateway Timeout - 网关超时 |
10.4 在 FastAPI 中使用状态码
FastAPI 允许您通过返回不同的状态码来指示请求的处理结果。
示例:自定义状态码
from fastapi import FastAPI, HTTPException, status
app = FastAPI()
@app.post("/users", status_code=status.HTTP_201_CREATED)
async def create_user(user: dict):
if not user.get("name") or not user.get("email"):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Name and email are required")
# 假设用户创建成功
return {"id": 1, "name": user["name"], "email": user["email"]}
解释:
status_code=status.HTTP_201_CREATED
:指定默认的成功状态码为 201。HTTPException
:在遇到错误时,抛出 HTTP 异常,并指定相应的状态码和错误详情。
11. URL 格式
11.1 什么是 URL
URL(Uniform Resource Locator,统一资源定位符) 是一种用于定位和访问互联网上资源的标准化地址。它指定了资源的访问方法、位置和路径。
11.2 URL 的组成部分
一个完整的 URL 通常由以下几个部分组成:
scheme://user:password@host:port/path?query#fragment
组成部分详解:
- Scheme(方案):
- 指定了访问资源所使用的协议。
- 常见的方案包括
http
、https
、ftp
、mailto
等。 - 示例:
http://
,https://
- User Information(用户信息)(可选):
- 包含用户名和密码,用于访问需要认证的资源。
- 格式:
user:password@
- 示例:
user:pass@
- Host(主机):
- 指定了服务器的域名或 IP 地址。
- 示例:
www.example.com
,192.168.1.1
- Port(端口)(可选):
- 指定了服务器上的端口号,默认情况下不同方案有默认端口(如 HTTP 的 80,HTTPS 的 443)。
- 示例:
:8080
- Path(路径):
- 指定了服务器上资源的具体位置。
- 类似于文件系统中的路径。
- 示例:
/users/1
- Query(查询)(可选):
- 提供了资源的参数,以键值对的形式传递。
- 以
?
开头,每个参数之间用&
分隔。 - 示例:
?search=fastapi&limit=10
- Fragment(片段)(可选):
- 指定了资源内的一个片段或位置,通常用于定位文档中的特定部分。
- 以
#
开头。 - 示例:
#section2
完整示例:
https://user:password@www.example.com:443/users/1?search=fastapi&limit=10#details
- Scheme:
https
- User Information:
user:password@
- Host:
www.example.com
- Port:
:443
- Path:
/users/1
- Query:
?search=fastapi&limit=10
- Fragment:
#details
11.3 URL 编码
URL 编码用于在 URL 中传递特殊字符和非 ASCII 字符。它通过将字符转换为 %
后跟两位十六进制数的形式表示。
常见需要编码的字符包括:
- 空格(
%20
或+
- 斜杠(
/
)编码为%2F
- 问号(
?
)编码为%3F
- 井号(
#
)编码为%23
- 等号(
=
)编码为%3D
- 和符号(
&
)编码为%26
示例:
https://www.example.com/search?q=fast api&sort=ascending
编码后:
https://www.example.com/search?q=fast%20api&sort=ascending
Python 示例:
使用 urllib.parse
模块进行 URL 编码。
import urllib.parse
query = "fast api"
encoded_query = urllib.parse.quote(query)
print(encoded_query) # 输出: fast%20api
11.4 示例解析
示例 URL:
https://john:secret@www.example.com:8080/api/v1/users?search=alice&limit=5#profile
解析:
- Scheme:
https
- User Information:
john:secret@
- Host:
www.example.com
- Port:
:8080
- Path:
/api/v1/users
- Query:
?search=alice&limit=5
- Fragment:
#profile
用途:
- 认证:通过
john:secret@
提供用户名和密码。 - 定位资源:通过
/api/v1/users
指定 API 路径。 - 传递参数:通过
search=alice&limit=5
传递查询参数。 - 定位文档片段:通过
#profile
定位文档中的特定部分。
12. MIME 类型
12.1 什么是 MIME 类型
MIME 类型(Multipurpose Internet Mail Extensions Types) 是一种标准,用于指示文件的媒体类型或格式。虽然最初用于电子邮件传输,但现已广泛应用于 HTTP 协议中,帮助浏览器正确处理和显示资源。
12.2 MIME 类型的结构
MIME 类型由两部分组成,使用斜杠(/
)分隔:
类型/子类型
示例:
text/html
:HTML 文档application/json
:JSON 数据image/png
:PNG 图片audio/mpeg
:MPEG 音频video/mp4
:MP4 视频
12.3 常见的 MIME 类型
文件类型 | MIME 类型 |
---|---|
HTML | text/html |
CSS | text/css |
JavaScript | application/javascript |
JSON | application/json |
XML | application/xml |
PNG 图片 | image/png |
JPEG 图片 | image/jpeg |
GIF 图片 | image/gif |
SVG 图片 | image/svg+xml |
MP3 音频 | audio/mpeg |
MP4 视频 | video/mp4 |
PDF 文档 | application/pdf |
ZIP 压缩文件 | application/zip |
Binary 文件 | application/octet-stream |
12.4 在 FastAPI 中使用 MIME 类型
FastAPI 允许您通过响应模型或响应类指定 MIME 类型,确保客户端正确处理响应内容。
示例:返回 JSON 数据
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/json", response_class=JSONResponse)
async def get_json():
return {"message": "This is JSON data"}
示例:返回 HTML 内容
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.get("/html", response_class=HTMLResponse)
async def get_html():
html_content = """
<html>
<head>
<title>FastAPI HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
"""
return HTMLResponse(content=html_content, status_code=200)
示例:返回文件下载
from fastapi import FastAPI
from fastapi.responses import FileResponse
app = FastAPI()
@app.get("/download")
async def download_file():
file_path = "/path/to/file.zip"
return FileResponse(path=file_path, filename="file.zip", media_type="application/zip")
解释:
response_class
:指定响应的类型,如JSONResponse
、HTMLResponse
、FileResponse
等。media_type
:明确指定响应内容的 MIME 类型,确保客户端正确处理。
13. SSL/TLS(安全套接层/传输层安全协议)
13.1 什么是 SSL/TLS
SSL(Secure Sockets Layer,安全套接层) 和 TLS(Transport Layer Security,传输层安全协议) 是用于在计算机网络中提供安全通信的加密协议。TLS 是 SSL 的继任者,当前广泛使用的是 TLS 1.2 和 TLS 1.3。
13.2 SSL/TLS 的工作原理
- 握手过程:
- 客户端 Hello:客户端发送支持的协议版本、加密套件和随机数。
- 服务器 Hello:服务器选择协议版本、加密套件,并发送证书和随机数。
- 证书验证:客户端验证服务器证书的有效性。
- 密钥交换:客户端生成预主密钥,加密后发送给服务器。
- 生成对称密钥:双方根据预主密钥生成对称加密密钥。
- 完成握手:双方确认密钥交换完成,开始加密通信。
- 数据传输:
- 使用对称加密算法(如 AES)进行加密数据传输,确保数据的机密性和完整性。
- 连接终止:
- 安全地关闭连接,确保密钥不被泄露。
13.3 SSL/TLS 的作用
- 加密:保护数据在传输过程中的机密性,防止被窃听。
- 认证:验证通信双方的身份,确保数据发送到正确的服务器。
- 完整性:确保数据在传输过程中未被篡改。
13.4 HTTPS
HTTPS(HTTP Secure) 是在 HTTP 基础上结合 SSL/TLS 实现的安全协议。通过 HTTPS,HTTP 请求和响应在传输过程中被加密,确保数据的安全性和隐私性。
示例:
GET https://www.example.com/api/users HTTP/1.1
Host: www.example.com
13.5 获取和安装 SSL 证书
- 购买证书:从认证机构(CA)购买 SSL 证书。
- 免费证书:使用 Let’s Encrypt 提供的免费证书。
- 安装证书:根据服务器软件(如 Nginx、Apache)的指南安装证书。
- 自动化工具:使用工具如 Certbot 自动化获取和续订证书。
示例:使用 Certbot 获取 Let’s Encrypt 证书
sudo apt-get update
sudo apt-get install certbot
sudo certbot certonly --standalone -d www.example.com
解释:
- Certbot:自动化工具,用于获取和管理 SSL/TLS 证书。
certonly --standalone
:仅获取证书,不自动配置服务器。-d www.example.com
:指定需要证书的域名。
14. CORS(跨源资源共享)
14.1 什么是 CORS
CORS(Cross-Origin Resource Sharing,跨源资源共享) 是一种机制,允许浏览器从一个源(域)请求来自另一个源的资源。由于浏览器的同源策略限制,默认情况下,Web 应用无法跨域请求资源。CORS 通过使用 HTTP 头信息来放宽这一限制。
14.2 同源策略
同源策略(Same-Origin Policy) 是浏览器的安全机制,限制一个源(协议、域名、端口)加载的文档或脚本如何与另一个源的资源进行交互。
同源条件:
- 协议:相同(如
http
、https
)。 - 域名:相同(如
www.example.com
)。 - 端口:相同(如
80
、443
)。
14.3 CORS 的工作原理
CORS 通过在服务器端设置特定的 HTTP 头信息,指示浏览器允许跨源请求。主要的 CORS 头信息包括:
-
Access-Control-Allow-Origin
:- 指定允许跨域请求的源。
*
表示允许所有源。
Access-Control-Allow-Origin: https://www.allowed-origin.com
-
Access-Control-Allow-Methods
:- 指定允许的 HTTP 方法。
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
-
Access-Control-Allow-Headers
:- 指定允许的自定义头信息。
Access-Control-Allow-Headers: Content-Type, Authorization
-
Access-Control-Allow-Credentials
:- 是否允许发送 Cookie 和其他凭证。
Access-Control-Allow-Credentials: true
-
Access-Control-Max-Age
:- 预检请求的结果缓存时间。
Access-Control-Max-Age: 3600
14.4 预检请求(Preflight Request)
对于一些类型的跨域请求,浏览器会先发送一个 预检请求(OPTIONS
请求),询问服务器是否允许实际的请求。
示例:
-
预检请求:
OPTIONS /api/resource HTTP/1.1 Host: api.example.com Origin: https://www.allowed-origin.com Access-Control-Request-Method: POST Access-Control-Request-Headers: Content-Type, Authorization
-
预检响应:
HTTP/1.1 204 No Content Access-Control-Allow-Origin: https://www.allowed-origin.com Access-Control-Allow-Methods: GET, POST, PUT, DELETE Access-Control-Allow-Headers: Content-Type, Authorization Access-Control-Max-Age: 3600
14.5 在 FastAPI 中配置 CORS
FastAPI 提供了 CORSMiddleware
,可以方便地配置 CORS 策略。
安装依赖
pip install fastapi
pip install uvicorn
pip install python-multipart
示例代码
app/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# 配置 CORS
origins = [
"http://localhost",
"http://localhost:8000",
"https://www.allowed-origin.com",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins, # 允许的源
allow_credentials=True,
allow_methods=["*"], # 允许的 HTTP 方法
allow_headers=["*"], # 允许的头信息
)
@app.get("/")
async def main():
return {"message": "Hello World"}
解释:
allow_origins
:指定允许跨域请求的源,可以是具体的域名或*
。allow_credentials
:是否允许发送凭证(如 Cookie)。allow_methods
:允许的 HTTP 方法,["*"]
表示所有方法。allow_headers
:允许的头信息,["*"]
表示所有头信息。
14.6 安全性注意事项
- 限制
allow_origins
:尽量避免使用*
,明确指定允许的源,防止跨站点请求伪造(CSRF)攻击。 - 谨慎使用
allow_credentials
:开启凭证支持时,Access-Control-Allow-Origin
不能设置为*
,必须明确指定源。 - 最小化暴露信息:只允许必要的 HTTP 方法和头信息,减少潜在的攻击面。
15. Cookies 和 Sessions
15.1 什么是 Cookie
Cookie 是存储在客户端(通常是浏览器)的小块数据,由服务器发送,并在后续请求中自动发送回服务器。Cookie 常用于会话管理、个性化设置和跟踪用户行为。
15.2 Cookie 的工作原理
-
服务器发送 Cookie:
- 通过
Set-Cookie
头信息在 HTTP 响应中设置 Cookie。
Set-Cookie: sessionid=abc123; Path=/; HttpOnly; Secure
- 通过
-
浏览器存储 Cookie:
- 浏览器接收并存储 Cookie,根据
Path
和Domain
规则管理 Cookie 的有效范围。
- 浏览器接收并存储 Cookie,根据
-
浏览器发送 Cookie:
- 在符合范围的每个请求中,浏览器自动在
Cookie
头中发送存储的 Cookie。
Cookie: sessionid=abc123; csrftoken=def456
- 在符合范围的每个请求中,浏览器自动在
15.3 Cookie 的属性
- Name=Value:Cookie 的名称和值。
- Expires 或 Max-Age:指定 Cookie 的过期时间。
- Path:指定 Cookie 的有效路径。
- Domain:指定 Cookie 的有效域名。
- Secure:仅通过 HTTPS 连接发送 Cookie。
- HttpOnly:禁止通过 JavaScript 访问 Cookie,增强安全性。
- **SameSite:**控制跨站请求时 Cookie 的发送行为,防止 CSRF 攻击。
Strict
:完全不发送跨站请求的 Cookie。Lax
:在某些跨站请求中发送 Cookie(如导航)。None
:在所有跨站请求中发送 Cookie(必须与Secure
一起使用)。
15.4 什么是 Session
Session(会话) 是服务器端存储的用户数据,用于维护用户在多个请求中的状态。通常,Session ID 存储在 Cookie 中,服务器通过 Session ID 识别和管理会话数据。
15.5 Session 的工作原理
- 用户登录:
- 用户提交登录信息,服务器验证后创建一个新的 Session,生成唯一的 Session ID。
- 通过
Set-Cookie
头信息将 Session ID 发送给客户端。
- 后续请求:
- 客户端在每个请求中通过 Cookie 发送 Session ID。
- 服务器通过 Session ID 识别用户,检索并操作相应的 Session 数据。
- Session 终止:
- 用户登出或 Session 过期,服务器删除对应的 Session 数据。
- 客户端删除 Cookie 或设置过期时间,使 Cookie 失效。
15.6 Cookie 和 Session 的比较
特性 | Cookie | Session |
---|---|---|
存储位置 | 客户端(浏览器) | 服务器 |
安全性 | 相对较低,容易被窃取和篡改 | 较高,数据存储在服务器端 |
容量限制 | 每个 Cookie 约 4KB,数量有限 | 依赖服务器资源,无明显限制 |
用途 | 会话管理、用户跟踪、个性化设置 | 会话状态管理、存储敏感信息 |
性能影响 | 增加每个请求的大小,影响性能 | 不影响请求大小,需服务器维护额外状态 |
16. 认证与授权
16.1 什么是认证与授权
- 认证(Authentication):验证用户的身份,确保用户是其声称的身份。
- 授权(Authorization):确定已认证用户是否有权限访问特定资源或执行特定操作。
16.2 常见的认证机制
- 基于 Cookie 的认证:
- 使用 Cookie 存储会话 ID,适用于传统的 Web 应用。
- Token-Based Authentication(基于令牌的认证):
- 使用 JWT(JSON Web Token)或其他 Token 进行认证,适用于分布式和移动应用。
- OAuth 2.0:
- 一个授权框架,允许第三方应用在用户授权下访问资源。
- OpenID Connect:
- 基于 OAuth 2.0 的身份认证协议,提供用户身份验证功能。
16.3 JWT(JSON Web Token)
JWT 是一种基于 JSON 的轻量级认证机制,广泛应用于 Web 应用的认证和授权。
JWT 的结构
一个 JWT 由三部分组成,用点(.
)分隔:
-
Header(头部):
- 通常包含令牌类型和签名算法。
{ "alg": "HS256", "typ": "JWT" }
-
Payload(载荷):
- 包含声明(claims),描述关于实体(通常是用户)和其他数据的信息。
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
-
Signature(签名):
- 用于验证令牌的完整性和真实性。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
JWT 的优点
- 自包含:包含所有必要的信息,减少服务器查询。
- 跨平台:适用于各种客户端,如浏览器、移动应用等。
- 无状态:服务器不需要存储会话信息,易于扩展。
- 安全性:通过签名确保数据的完整性和真实性。
JWT 的缺点
- 无法撤销:一旦签发,除非过期,无法主动撤销。
- 大小较大:相比于简单的会话 ID,JWT 较大,影响请求大小。
16.4 OAuth 2.0
OAuth 2.0 是一个用于授权的框架,允许用户在不暴露密码的情况下,授权第三方应用访问其资源。
OAuth 2.0 的角色
- 资源拥有者(Resource Owner):用户,拥有访问受保护资源的权限。
- 客户端(Client):第三方应用,想要访问资源拥有者的资源。
- 资源服务器(Resource Server):存储资源的服务器,响应客户端的请求。
- 授权服务器(Authorization Server):负责验证资源拥有者的身份并颁发访问令牌。
OAuth 2.0 的流程
- 授权请求:客户端向授权服务器请求授权。
- 授权同意:资源拥有者同意授权。
- 授权授予:授权服务器颁发授权码。
- 令牌请求:客户端使用授权码向授权服务器请求访问令牌。
- 令牌响应:授权服务器颁发访问令牌。
- 资源请求:客户端使用访问令牌访问资源服务器的受保护资源。
16.5 OpenID Connect
OpenID Connect(OIDC) 是一个基于 OAuth 2.0 的身份认证协议,添加了身份验证层,允许客户端验证用户的身份并获取基本的用户信息。
OIDC 的特点
- 身份层:在 OAuth 2.0 的基础上增加了身份认证功能。
- ID Token:JWT 格式,包含用户身份信息。
- 兼容性:与 OAuth 2.0 完全兼容,易于集成。
16.6 实现认证与授权
在 FastAPI 中,可以使用以下库和工具实现认证与授权:
- FastAPI 的
Depends
和Security
:- 通过依赖注入机制,集成认证和授权逻辑。
fastapi.security
模块:- 提供了多种安全工具,如 OAuth2、HTTP Basic、API Key 等。
pyjwt
或python-jose
:- 用于生成和验证 JWT。
- 第三方库:
fastapi-users
:一个完整的用户管理和认证解决方案。Authlib
:支持 OAuth1 和 OAuth2 的库。
示例:使用 OAuth2 和 JWT 实现认证
安装依赖
pip install python-jose[cryptography] passlib[bcrypt]
定义安全工具
app/security.py
from datetime import datetime, timedelta
from typing import Optional
from jose import JWTError, jwt
from passlib.context import CryptContext
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
# 配置
SECRET_KEY = "your_secret_key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# 密码哈希
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# OAuth2
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# 用户数据库示例
fake_users_db = {
"alice": {
"username": "alice",
"full_name": "Alice Wonderland",
"email": "alice@example.com",
"hashed_password": pwd_context.hash("password123"),
"disabled": False,
}
}
# 模型
from pydantic import BaseModel
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: Optional[str] = None
class User(BaseModel):
username: str
email: Optional[str] = None
full_name: Optional[str] = None
disabled: Optional[bool] = None
class UserInDB(User):
hashed_password: str
# 工具函数
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
return pwd_context.hash(password)
def get_user(db, username: str) -> Optional[UserInDB]:
if username in db:
user_dict = db[username]
return UserInDB(**user_dict)
return None
def authenticate_user(db, username: str, password: str) -> Optional[UserInDB]:
user = get_user(db, username)
if not user:
return None
if not verify_password(password, user.hashed_password):
return None
return user
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
# 获取当前用户
from .schemas import UserResponse
async def get_current_user(token: str = Depends(oauth2_scheme)) -> User:
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
token_data = TokenData(username=username)
except JWTError:
raise credentials_exception
user = get_user(fake_users_db, username=token_data.username)
if user is None:
raise credentials_exception
return user
async def get_current_active_user(current_user: User = Depends(get_current_user)) -> User:
if current_user.disabled:
raise HTTPException(status_code=400, detail="Inactive user")
return current_user
定义 Token 路由
app/main.py
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from .security import (
authenticate_user, create_access_token, ACCESS_TOKEN_EXPIRE_MINUTES,
Token, get_current_active_user
)
from datetime import timedelta
app = FastAPI()
@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
@app.get("/users/me", response_model=UserResponse)
async def read_users_me(current_user: User = Depends(get_current_active_user)):
return current_user
解释:
/token
路由:接受用户名和密码,验证后返回 JWT 访问令牌。/users/me
路由:需要携带有效的 JWT 访问令牌,返回当前用户的信息。
测试认证流程
-
获取 Token
请求:
curl -X POST "http://127.0.0.1:8000/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "username=alice&password=password123"
响应:
{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "bearer" }
-
访问受保护路由
请求:
curl -X GET "http://127.0.0.1:8000/users/me" \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
响应:
{ "username": "alice", "email": "alice@example.com", "full_name": "Alice Wonderland", "disabled": false }
17. 速率限制与节流
17.1 什么是速率限制与节流
- 速率限制(Rate Limiting):限制客户端在特定时间窗口内发起请求的次数,防止滥用和恶意攻击。
- 节流(Throttling):动态调整请求处理速率,保护服务器资源,确保系统稳定。
17.2 速率限制的常见策略
- 固定窗口计数器(Fixed Window Counter)
- 在固定的时间窗口内统计请求次数,超过限制则拒绝。
- 滑动窗口日志(Sliding Window Log)
- 记录每个请求的时间戳,动态计算时间窗口内的请求次数。
- 滑动窗口计数器(Sliding Window Counter)
- 在固定窗口的基础上,通过部分窗口统计,平滑请求计数。
- 令牌桶算法(Token Bucket)
- 维护一个令牌桶,按速率生成令牌,客户端请求需消耗令牌。
- 漏桶算法(Leaky Bucket)
- 以固定速率处理请求,排队过多的请求会被丢弃或延迟处理。
17.3 在 FastAPI 中实现速率限制
使用 slowapi
库
安装依赖
pip install slowapi
配置速率限制
app/main.py
from fastapi import FastAPI, Request, HTTPException
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
app = FastAPI()
# 初始化 Limiter
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
@app.get("/")
@limiter.limit("10/minute")
async def root():
return {"message": "Hello, World!"}
@app.get("/limited")
@limiter.limit("5/minute")
async def limited_route():
return {"message": "This is a rate-limited route."}
解释:
Limiter
:定义速率限制策略,key_func=get_remote_address
根据客户端 IP 地址限制请求。@limiter.limit("10/minute")
:每分钟最多 10 次请求。- 异常处理:当超过限制时,自动返回
429 Too Many Requests
错误。
17.4 高级配置
- 动态速率限制:根据用户角色、API 端点等动态调整限制策略。
- 不同的时间窗口:如每小时、每天等,根据需求调整。
- 速率限制响应:返回有用的错误信息和重试指示,提升用户体验。
示例:基于用户角色的速率限制
app/security.py
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
def get_rate_limit(user_role: str) -> str:
if user_role == "admin":
return "1000/minute"
elif user_role == "user":
return "100/minute"
else:
return "10/minute"
app/main.py
from fastapi import FastAPI, Depends
from .security import limiter, get_rate_limit
from slowapi import _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from .dependencies import get_current_active_user
from pydantic import BaseModel
class User(BaseModel):
username: str
email: str
role: str
app = FastAPI()
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
@app.get("/user-endpoint")
@limiter.limit(lambda request: get_rate_limit(request.state.user.role))
async def user_endpoint(current_user: User = Depends(get_current_active_user)):
return {"message": "User endpoint"}
解释:
- 动态限制:根据用户角色动态设置速率限制。
- Lambda 函数:使用函数动态返回限制策略。
18. 缓存机制
18.1 什么是缓存
缓存(Caching) 是一种存储机制,用于保存频繁访问的数据,以减少数据检索的延迟和服务器负载。通过在客户端或服务器端缓存数据,可以显著提升应用性能和响应速度。
18.2 缓存的类型
- 客户端缓存
- 浏览器缓存:浏览器自动缓存静态资源,如图片、CSS、JavaScript 文件。
- 应用缓存:使用 Service Workers 等技术,在客户端缓存 API 响应或其他数据。
- 服务器端缓存
- 内存缓存:使用内存存储常用数据,如 Redis、Memcached。
- 磁盘缓存:将数据存储在磁盘上,适用于大规模缓存需求。
- 代理缓存
- CDN 缓存:通过内容分发网络(CDN)缓存静态和动态内容,减少服务器负载和延迟。
- 反向代理缓存:使用 Nginx、Varnish 等反向代理服务器缓存响应,提高性能。
18.3 缓存的策略
- 过期时间(Expiration)
- Expires:指定资源的过期日期和时间。
- Cache-Control:控制缓存行为,如
max-age
、no-cache
、no-store
。
- 缓存验证
- ETag:实体标签,唯一标识资源的版本。
- Last-Modified:资源的最后修改时间。
- 缓存失效
- 强制失效:通过修改资源的 URL 或版本号,强制浏览器重新获取资源。
- 条件请求:使用
If-None-Match
、If-Modified-Since
等头信息,服务器决定是否返回新内容。
18.4 在 FastAPI 中实现缓存
使用 fastapi-cache
库
安装依赖
pip install fastapi-cache2
pip install aioredis
配置缓存
app/main.py
from fastapi import FastAPI, Depends
from fastapi.responses import JSONResponse
from fastapi_cache import FastAPICache
from fastapi_cache.backends.redis import RedisBackend
from aioredis import Redis
import aioredis
app = FastAPI()
@app.on_event("startup")
async def startup():
redis = await aioredis.create_redis_pool("redis://localhost")
FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache")
@app.get("/cached")
@FastAPICache.cached(ttl=60) # 缓存 60 秒
async def get_cached_data():
# 假设这是一个耗时的操作
data = {"message": "This is cached data"}
return JSONResponse(content=data)
解释:
FastAPICache.init
:初始化缓存,指定使用 Redis 作为后端。@FastAPICache.cached(ttl=60)
:为路由添加缓存装饰器,缓存有效期为 60 秒。
18.5 高级缓存策略
- 按需缓存:仅缓存特定条件下的数据,如认证用户的数据。
- 缓存层次结构:结合客户端、服务器端和代理缓存,优化整体缓存效果。
- 缓存预热:在应用启动或特定事件后预先填充缓存,减少冷启动延迟。
- 缓存监控与分析:监控缓存命中率和性能,优化缓存策略。
示例:按用户角色缓存
app/main.py
from fastapi import FastAPI, Depends
from fastapi_cache import FastAPICache
from fastapi_cache.decorator import cache
from fastapi_cache.backends.redis import RedisBackend
from aioredis import Redis
import aioredis
from pydantic import BaseModel
class User(BaseModel):
username: str
role: str
app = FastAPI()
@app.on_event("startup")
async def startup():
redis = await aioredis.create_redis_pool("redis://localhost")
FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache")
def get_user_role(user: User) -> str:
return user.role
@app.get("/user-data")
@cache(expire=120, key_builder=lambda func, *args, **kwargs: f"user-data-{kwargs['user'].username}")
async def get_user_data(user: User = Depends()):
# 根据用户角色生成数据
if user.role == "admin":
data = {"data": "Sensitive admin data"}
else:
data = {"data": "Regular user data"}
return data
解释:
- 自定义缓存键:根据用户名生成唯一的缓存键,确保不同用户的数据被分别缓存。
- 不同角色的数据缓存:根据用户角色返回不同的数据,并缓存相应的响应。
19. API 版本控制
19.1 什么是 API 版本控制
API 版本控制 是管理和维护不同版本的 API,确保在更新或改变 API 时,不影响现有的客户端应用。版本控制允许同时存在多个 API 版本,满足不同客户的需求。
19.2 API 版本控制的策略
-
URI 版本控制:
- 将版本号包含在路径中,最常见且易于理解。
示例:
/v1/users /v2/users
-
请求头版本控制:
- 通过自定义请求头指定 API 版本。
示例:
GET /users HTTP/1.1 Host: api.example.com Accept: application/vnd.example.v1+json
-
查询参数版本控制:
- 通过查询参数指定 API 版本。
示例:
/users?version=1
-
媒体类型版本控制:
- 使用
Accept
头中的媒体类型来指定版本。
示例:
Accept: application/vnd.example.v1+json
- 使用
19.3 选择适合的版本控制策略
- URI 版本控制:简单直观,易于路由配置,适用于大多数情况。
- 请求头版本控制:不暴露版本信息,适用于需要隐藏版本细节的情况。
- 查询参数版本控制:灵活,但不够直观,可能与其他查询参数冲突。
- 媒体类型版本控制:高度灵活,适用于需要丰富版本控制的复杂应用。
19.4 在 FastAPI 中实现版本控制
示例:URI 版本控制
app/main.py
from fastapi import FastAPI
app_v1 = FastAPI()
app_v2 = FastAPI()
@app_v1.get("/users")
async def get_users_v1():
return {"version": "v1", "users": [{"id": 1, "name": "Alice"}]}
@app_v2.get("/users")
async def get_users_v2():
return {"version": "v2", "users": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]}
main_app = FastAPI()
main_app.mount("/v1", app_v1)
main_app.mount("/v2", app_v2)
解释:
app_v1
和app_v2
:分别代表不同版本的 API。main_app
:主应用,将不同版本的 API 挂载到不同的路径下。
启动应用
uvicorn app.main:main_app --reload
访问示例
- 版本 1:
http://localhost:8000/v1/users
- 版本 2:
http://localhost:8000/v2/users
19.5 进阶:动态版本控制
使用路由前缀或依赖注入,实现更灵活的版本控制策略。
示例:基于依赖的版本控制
app/main.py
from fastapi import FastAPI, Depends, HTTPException, Header
from typing import Optional
app = FastAPI()
def get_api_version(x_api_version: Optional[str] = Header(None)):
if x_api_version == "1":
return "v1"
elif x_api_version == "2":
return "v2"
else:
raise HTTPException(status_code=400, detail="Invalid or missing API version")
@app.get("/users")
async def get_users(api_version: str = Depends(get_api_version)):
if api_version == "v1":
return {"version": "v1", "users": [{"id": 1, "name": "Alice"}]}
elif api_version == "v2":
return {"version": "v2", "users": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]}
解释:
get_api_version
:通过请求头X-API-Version
获取 API 版本。- 路由处理函数:根据 API 版本返回不同的响应。
访问示例
-
版本 1:
GET /users HTTP/1.1 Host: localhost:8000 X-API-Version: 1
-
版本 2:
GET /users HTTP/1.1 Host: localhost:8000 X-API-Version: 2