网络概念及格式

网络编程基础概念笔记


1. OSI 模型与网络层次

1.1 什么是 OSI 模型

OSI(Open Systems Interconnection,开放系统互联)模型 是由国际标准化组织(ISO)制定的网络通信参考模型,将网络通信过程分为七个层次,每层负责特定的功能。

1.2 OSI 模型的七层

  1. 物理层(Physical Layer):
    • 传输原始的比特流,涉及硬件设备、信号传输等。
  2. 数据链路层(Data Link Layer):
    • 负责在物理层上建立可靠的数据传输,处理帧的传输和错误检测。
  3. 网络层(Network Layer):
    • 负责数据包的传输、路由选择和 IP 地址管理。
  4. 传输层(Transport Layer):
    • 提供端到端的通信服务,如 TCP 和 UDP 协议。
  5. 会话层(Session Layer):
    • 管理用户会话,控制通信双方的对话和同步。
  6. 表示层(Presentation Layer):
    • 负责数据的格式转换、加密和压缩。
  7. 应用层(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 模型简化为四层:

  1. 网络接口层(对应 OSI 的物理层和数据链路层)
  2. 互联网层(对应 OSI 的网络层)
  3. 传输层(对应 OSI 的传输层)
  4. 应用层(对应 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) 的过程。

建立连接(三次握手):

  1. SYN:客户端发送一个 SYN(同步)报文段,告诉服务器客户端打算建立连接,并初始化序列号。
  2. SYN-ACK:服务器回应一个 SYN-ACK 报文段,表示同意建立连接,并回复客户端的序列号,同时自己也初始化序列号。
  3. ACK:客户端发送一个 ACK 报文段,确认收到服务器的 SYN-ACK,连接建立完成。

断开连接(四次挥手):

  1. FIN:主动关闭连接的一方发送一个 FIN(终止)报文段,表示不再发送数据。
  2. ACK:被动关闭连接的一方回应一个 ACK,确认收到 FIN。
  3. FIN:被动关闭连接的一方发送一个 FIN,表示也不再发送数据。
  4. 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 的特点

  1. 无连接:发送方和接收方之间不建立连接,数据包(数据报)直接发送。
  2. 不可靠:不保证数据包的送达、顺序和完整性。数据包可能会丢失、重复或乱序。
  3. 低开销:由于没有连接建立和维护,UDP 的头部较小(8 字节),开销低。
  4. 支持广播和多播:可以向多个目标发送数据包。

3.3 UDP 的应用场景

  • 实时应用:如视频会议、在线游戏,需要快速传输,容忍一定的数据丢失。
  • DNS 查询:快速的请求响应,通常一次性通信。
  • 流媒体传输:音视频流需要持续传输,偶尔丢失数据不影响整体体验。

3.4 UDP 与 TCP 的比较

特性TCPUDP
连接性面向连接无连接
可靠性可靠,保证数据顺序和完整性不可靠,可能丢失、重复或乱序
流量控制有流量控制和拥塞控制机制无流量控制和拥塞控制机制
速度较慢,因为有额外的控制开销较快,开销低
应用场景HTTP、FTP、SMTP、数据库连接等DNS、视频流、在线游戏、实时通信等

4. IP 地址(IPv4 和 IPv6)

4.1 什么是 IP 地址

IP 地址(Internet Protocol Address) 是分配给网络中每个设备的唯一标识符,用于在网络中定位和通信。IP 地址分为 IPv4IPv6 两种版本。

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 的类型

  1. 静态 NAT
    • 一一映射私有 IP 地址到公共 IP 地址,适用于需要固定公共 IP 的服务器。
  2. 动态 NAT
    • 动态分配公共 IP 地址给私有网络中的设备,公共 IP 数量需大于私有 IP。
  3. 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 端口号的范围

  1. 知名端口(Well-Known Ports)
    • 范围:0-1023
    • 用途:预定义的服务和协议,如 HTTP(80)、HTTPS(443)、FTP(21)、SSH(22)、SMTP(25)。
  2. 注册端口(Registered Ports)
    • 范围:1024-49151
    • 用途:由软件公司和应用程序注册使用,如 MySQL(3306)、PostgreSQL(5432)、Redis(6379)。
  3. 动态或私有端口(Dynamic or Private Ports)
    • 范围:49152-65535
    • 用途:临时分配给客户端应用程序,用于会话通信。

6.3 常见的知名端口

服务/协议端口号描述
HTTP80超文本传输协议
HTTPS443安全超文本传输协议
FTP21文件传输协议
SSH22安全外壳协议
SMTP25简单邮件传输协议
DNS53域名系统
MySQL3306MySQL 数据库服务器
PostgreSQL5432PostgreSQL 数据库服务器
Redis6379Redis 内存数据存储
MongoDB27017MongoDB NoSQL 数据库
Telnet23远程登录协议

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 的工作原理

  1. 查询过程:
    • 当用户在浏览器中输入域名时,浏览器首先检查本地缓存是否有该域名的 IP 地址。
    • 如果没有,操作系统会向配置的 DNS 服务器发送查询请求。
    • DNS 服务器可能会递归查询其他 DNS 服务器,直到找到相应的 IP 地址。
    • 最终,IP 地址返回给用户的设备,浏览器使用该 IP 地址建立连接。
  2. DNS 记录类型:
    • A 记录:将域名映射到 IPv4 地址。
    • AAAA 记录:将域名映射到 IPv6 地址。
    • CNAME 记录:别名记录,将一个域名指向另一个域名。
    • MX 记录:邮件交换记录,指定邮件服务器。
    • TXT 记录:文本记录,用于各种验证和配置,如 SPF、DKIM。
    • NS 记录:指定域名的权威 DNS 服务器。

7.3 DNS 服务器类型

  1. 递归解析器:
    • 接受客户端的 DNS 查询,负责递归查找最终的 IP 地址。
  2. 权威 DNS 服务器:
    • 对特定域名负责,提供最终的 DNS 记录响应。
  3. 根 DNS 服务器:
    • 位于 DNS 层次结构的顶端,指向各个顶级域(如 .com, .org)的权威 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 协议的特点

  1. 简单性:HTTP 协议设计简洁,易于理解和实现。使用明文传输,便于调试和测试。
  2. 无状态性:每个 HTTP 请求都是独立的,服务器不会记住之前的请求。这使得协议更加简洁,但也带来了需要管理状态的挑战(如使用 Cookie 或 Token)。
  3. 灵活性:HTTP 支持多种数据格式和传输方式,可以通过扩展方法和头信息实现更多功能。
  4. 可扩展性:通过头信息(Headers)和方法(Methods)的扩展,可以不断增加新的功能和特性。
  5. 基于文本:HTTP 报文使用可读的文本格式,易于理解和调试。
  6. 无连接与持久连接:
    • 无连接:默认情况下,每个请求/响应周期完成后连接就关闭了。
    • 持久连接(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 请求由以下部分组成:

  1. 请求行(Request Line):包含请求方法、请求 URI 和协议版本。

    GET /path/resource HTTP/1.1
    
  2. 请求头(Headers):一组键值对,提供关于客户端环境和请求正文的附加信息。

    Host: example.com
    User-Agent: Mozilla/5.0
    Accept: text/html,application/xhtml+xml
    
  3. 空行:请求头和请求体之间的空行,用于分隔。

  4. 请求体(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 响应由以下部分组成:

  1. 状态行(Status Line):包含协议版本、状态码和状态消息。

    HTTP/1.1 200 OK
    
  2. 响应头(Headers):一组键值对,提供关于服务器环境和响应正文的附加信息。

    Content-Type: application/json
    Content-Length: 27
    
  3. 空行:响应头和响应体之间的空行,用于分隔。

  4. 响应体(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 状态码分类

  1. 1xx:信息性状态码
    • 表示请求已接收,继续处理。
    • 示例100 Continue
  2. 2xx:成功状态码
    • 表示请求已成功被服务器接收、理解和接受。
    • 示例:
      • 200 OK:请求成功。
      • 201 Created:请求成功,服务器创建了新的资源。
      • 204 No Content:请求成功,但没有内容返回。
  3. 3xx:重定向状态码
    • 表示需要客户端进一步操作以完成请求。
    • 示例:
      • 301 Moved Permanently:资源已永久移动到新位置。
      • 302 Found:临时重定向。
      • 304 Not Modified:资源未修改,使用缓存。
  4. 4xx:客户端错误状态码
    • 表示请求包含语法错误或无法完成。
    • 示例:
      • 400 Bad Request:请求语法错误。
      • 401 Unauthorized:未授权,需要认证。
      • 403 Forbidden:服务器拒绝请求。
      • 404 Not Found:请求的资源不存在。
  5. 5xx:服务器错误状态码
    • 表示服务器未能完成合法的请求。
    • 示例:
      • 500 Internal Server Error:服务器内部错误。
      • 502 Bad Gateway:无效的网关。
      • 503 Service Unavailable:服务不可用。
      • 504 Gateway Timeout:网关超时。

10.3 常见的 HTTP 状态码

状态码描述
200OK - 请求成功
201Created - 资源已创建
204No Content - 无内容
301Moved Permanently - 永久移动
302Found - 临时重定向
304Not Modified - 未修改
400Bad Request - 请求有误
401Unauthorized - 未授权
403Forbidden - 禁止访问
404Not Found - 未找到
500Internal Server Error - 服务器内部错误
502Bad Gateway - 无效的网关
503Service Unavailable - 服务不可用
504Gateway 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

组成部分详解:

  1. Scheme(方案):
    • 指定了访问资源所使用的协议。
    • 常见的方案包括 httphttpsftpmailto 等。
    • 示例:http://, https://
  2. User Information(用户信息)(可选):
    • 包含用户名和密码,用于访问需要认证的资源。
    • 格式:user:password@
    • 示例:user:pass@
  3. Host(主机):
    • 指定了服务器的域名或 IP 地址。
    • 示例:www.example.com, 192.168.1.1
  4. Port(端口)(可选):
    • 指定了服务器上的端口号,默认情况下不同方案有默认端口(如 HTTP 的 80,HTTPS 的 443)。
    • 示例::8080
  5. Path(路径):
    • 指定了服务器上资源的具体位置。
    • 类似于文件系统中的路径。
    • 示例:/users/1
  6. Query(查询)(可选):
    • 提供了资源的参数,以键值对的形式传递。
    • ? 开头,每个参数之间用 & 分隔。
    • 示例:?search=fastapi&limit=10
  7. Fragment(片段)(可选):
    • 指定了资源内的一个片段或位置,通常用于定位文档中的特定部分。
    • # 开头。
    • 示例:#section2

完整示例:

https://user:password@www.example.com:443/users/1?search=fastapi&limit=10#details
  • Schemehttps
  • User Informationuser:password@
  • Hostwww.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

解析:

  • Schemehttps
  • User Informationjohn:secret@
  • Hostwww.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 类型
HTMLtext/html
CSStext/css
JavaScriptapplication/javascript
JSONapplication/json
XMLapplication/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:指定响应的类型,如 JSONResponseHTMLResponseFileResponse 等。
  • 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 的工作原理

  1. 握手过程:
    • 客户端 Hello:客户端发送支持的协议版本、加密套件和随机数。
    • 服务器 Hello:服务器选择协议版本、加密套件,并发送证书和随机数。
    • 证书验证:客户端验证服务器证书的有效性。
    • 密钥交换:客户端生成预主密钥,加密后发送给服务器。
    • 生成对称密钥:双方根据预主密钥生成对称加密密钥。
    • 完成握手:双方确认密钥交换完成,开始加密通信。
  2. 数据传输:
    • 使用对称加密算法(如 AES)进行加密数据传输,确保数据的机密性和完整性。
  3. 连接终止:
    • 安全地关闭连接,确保密钥不被泄露。

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 证书

  1. 购买证书:从认证机构(CA)购买 SSL 证书。
  2. 免费证书:使用 Let’s Encrypt 提供的免费证书。
  3. 安装证书:根据服务器软件(如 Nginx、Apache)的指南安装证书。
  4. 自动化工具:使用工具如 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) 是浏览器的安全机制,限制一个源(协议、域名、端口)加载的文档或脚本如何与另一个源的资源进行交互。

同源条件:

  • 协议:相同(如 httphttps)。
  • 域名:相同(如 www.example.com)。
  • 端口:相同(如 80443)。

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 的工作原理

  1. 服务器发送 Cookie

    • 通过 Set-Cookie 头信息在 HTTP 响应中设置 Cookie。
    Set-Cookie: sessionid=abc123; Path=/; HttpOnly; Secure
    
  2. 浏览器存储 Cookie

    • 浏览器接收并存储 Cookie,根据 PathDomain 规则管理 Cookie 的有效范围。
  3. 浏览器发送 Cookie

    • 在符合范围的每个请求中,浏览器自动在 Cookie 头中发送存储的 Cookie。
    Cookie: sessionid=abc123; csrftoken=def456
    

15.3 Cookie 的属性

  • Name=Value:Cookie 的名称和值。
  • ExpiresMax-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 的工作原理

  1. 用户登录:
    • 用户提交登录信息,服务器验证后创建一个新的 Session,生成唯一的 Session ID。
    • 通过 Set-Cookie 头信息将 Session ID 发送给客户端。
  2. 后续请求:
    • 客户端在每个请求中通过 Cookie 发送 Session ID。
    • 服务器通过 Session ID 识别用户,检索并操作相应的 Session 数据。
  3. Session 终止:
    • 用户登出或 Session 过期,服务器删除对应的 Session 数据。
    • 客户端删除 Cookie 或设置过期时间,使 Cookie 失效。

15.6 Cookie 和 Session 的比较

特性CookieSession
存储位置客户端(浏览器)服务器
安全性相对较低,容易被窃取和篡改较高,数据存储在服务器端
容量限制每个 Cookie 约 4KB,数量有限依赖服务器资源,无明显限制
用途会话管理、用户跟踪、个性化设置会话状态管理、存储敏感信息
性能影响增加每个请求的大小,影响性能不影响请求大小,需服务器维护额外状态

16. 认证与授权

16.1 什么是认证与授权

  • 认证(Authentication):验证用户的身份,确保用户是其声称的身份。
  • 授权(Authorization):确定已认证用户是否有权限访问特定资源或执行特定操作。

16.2 常见的认证机制

  1. 基于 Cookie 的认证:
    • 使用 Cookie 存储会话 ID,适用于传统的 Web 应用。
  2. Token-Based Authentication(基于令牌的认证):
    • 使用 JWT(JSON Web Token)或其他 Token 进行认证,适用于分布式和移动应用。
  3. OAuth 2.0:
    • 一个授权框架,允许第三方应用在用户授权下访问资源。
  4. OpenID Connect:
    • 基于 OAuth 2.0 的身份认证协议,提供用户身份验证功能。

16.3 JWT(JSON Web Token)

JWT 是一种基于 JSON 的轻量级认证机制,广泛应用于 Web 应用的认证和授权。

JWT 的结构

一个 JWT 由三部分组成,用点(.)分隔:

  1. Header(头部)

    • 通常包含令牌类型和签名算法。
    {
        "alg": "HS256",
        "typ": "JWT"
    }
    
  2. Payload(载荷)

    • 包含声明(claims),描述关于实体(通常是用户)和其他数据的信息。
    {
        "sub": "1234567890",
        "name": "John Doe",
        "iat": 1516239022
    }
    
  3. Signature(签名)

    • 用于验证令牌的完整性和真实性。
    HMACSHA256(
      base64UrlEncode(header) + "." +
      base64UrlEncode(payload),
      secret
    )
    
JWT 的优点
  • 自包含:包含所有必要的信息,减少服务器查询。
  • 跨平台:适用于各种客户端,如浏览器、移动应用等。
  • 无状态:服务器不需要存储会话信息,易于扩展。
  • 安全性:通过签名确保数据的完整性和真实性。
JWT 的缺点
  • 无法撤销:一旦签发,除非过期,无法主动撤销。
  • 大小较大:相比于简单的会话 ID,JWT 较大,影响请求大小。

16.4 OAuth 2.0

OAuth 2.0 是一个用于授权的框架,允许用户在不暴露密码的情况下,授权第三方应用访问其资源。

OAuth 2.0 的角色
  1. 资源拥有者(Resource Owner):用户,拥有访问受保护资源的权限。
  2. 客户端(Client):第三方应用,想要访问资源拥有者的资源。
  3. 资源服务器(Resource Server):存储资源的服务器,响应客户端的请求。
  4. 授权服务器(Authorization Server):负责验证资源拥有者的身份并颁发访问令牌。
OAuth 2.0 的流程
  1. 授权请求:客户端向授权服务器请求授权。
  2. 授权同意:资源拥有者同意授权。
  3. 授权授予:授权服务器颁发授权码。
  4. 令牌请求:客户端使用授权码向授权服务器请求访问令牌。
  5. 令牌响应:授权服务器颁发访问令牌。
  6. 资源请求:客户端使用访问令牌访问资源服务器的受保护资源。

16.5 OpenID Connect

OpenID Connect(OIDC) 是一个基于 OAuth 2.0 的身份认证协议,添加了身份验证层,允许客户端验证用户的身份并获取基本的用户信息。

OIDC 的特点
  • 身份层:在 OAuth 2.0 的基础上增加了身份认证功能。
  • ID Token:JWT 格式,包含用户身份信息。
  • 兼容性:与 OAuth 2.0 完全兼容,易于集成。

16.6 实现认证与授权

在 FastAPI 中,可以使用以下库和工具实现认证与授权:

  • FastAPI 的 DependsSecurity
    • 通过依赖注入机制,集成认证和授权逻辑。
  • fastapi.security 模块:
    • 提供了多种安全工具,如 OAuth2、HTTP Basic、API Key 等。
  • pyjwtpython-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 访问令牌,返回当前用户的信息。

测试认证流程

  1. 获取 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"
    }
    
  2. 访问受保护路由

    请求:

    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 速率限制的常见策略

  1. 固定窗口计数器(Fixed Window Counter)
    • 在固定的时间窗口内统计请求次数,超过限制则拒绝。
  2. 滑动窗口日志(Sliding Window Log)
    • 记录每个请求的时间戳,动态计算时间窗口内的请求次数。
  3. 滑动窗口计数器(Sliding Window Counter)
    • 在固定窗口的基础上,通过部分窗口统计,平滑请求计数。
  4. 令牌桶算法(Token Bucket)
    • 维护一个令牌桶,按速率生成令牌,客户端请求需消耗令牌。
  5. 漏桶算法(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 缓存的类型

  1. 客户端缓存
    • 浏览器缓存:浏览器自动缓存静态资源,如图片、CSS、JavaScript 文件。
    • 应用缓存:使用 Service Workers 等技术,在客户端缓存 API 响应或其他数据。
  2. 服务器端缓存
    • 内存缓存:使用内存存储常用数据,如 Redis、Memcached。
    • 磁盘缓存:将数据存储在磁盘上,适用于大规模缓存需求。
  3. 代理缓存
    • CDN 缓存:通过内容分发网络(CDN)缓存静态和动态内容,减少服务器负载和延迟。
    • 反向代理缓存:使用 Nginx、Varnish 等反向代理服务器缓存响应,提高性能。

18.3 缓存的策略

  1. 过期时间(Expiration)
    • Expires:指定资源的过期日期和时间。
    • Cache-Control:控制缓存行为,如 max-ageno-cacheno-store
  2. 缓存验证
    • ETag:实体标签,唯一标识资源的版本。
    • Last-Modified:资源的最后修改时间。
  3. 缓存失效
    • 强制失效:通过修改资源的 URL 或版本号,强制浏览器重新获取资源。
    • 条件请求:使用 If-None-MatchIf-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 版本控制的策略

  1. URI 版本控制

    • 将版本号包含在路径中,最常见且易于理解。

    示例

    /v1/users
    /v2/users
    
  2. 请求头版本控制

    • 通过自定义请求头指定 API 版本。

    示例

    GET /users HTTP/1.1
    Host: api.example.com
    Accept: application/vnd.example.v1+json
    
  3. 查询参数版本控制

    • 通过查询参数指定 API 版本。

    示例

    /users?version=1
    
  4. 媒体类型版本控制

    • 使用 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_v1app_v2:分别代表不同版本的 API。
  • main_app:主应用,将不同版本的 API 挂载到不同的路径下。

启动应用

uvicorn app.main:main_app --reload

访问示例

  • 版本 1http://localhost:8000/v1/users
  • 版本 2http://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
    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值