← 返回 Day 64 正文

Day 64 · 图解

Day 064 — 网络请求:图解

1. HTTP 请求-响应流程

CLIENT                              SERVER
  │                                   │
  │  ┌─ TCP 三次握手 ──────────────┐  │
  │  │ SYN ──────────────→         │  │
  │  │        ←────────── SYN+ACK  │  │
  │  │ ACK ──────────────→         │  │
  │  └─────────────────────────────┘  │
  │                                   │
  │  ┌─ HTTP 请求 (Request) ──────┐  │
  │  │ GET /api/weather?city=北京  │  │
  │  │ Host: api.weather.com      │  │
  │  │ User-Agent: Python/3.12    │  │
  │  │ Accept: application/json   │  │
  │  │ Cookie: session=abc123     │  │
  │  └─────────────→──────────────┘  │
  │                                   │
  │  │           服务器处理请求       │
  │  │           (路由/验证/查询DB)  │
  │  │                   │           │
  │  │                   ▼           │
  │  │           响应生成            │
  │                                   │
  │  ┌─ HTTP 响应 (Response) ────┐  │
  │  │ ←───────────────────────── │  │
  │  │ HTTP/1.1 200 OK           │  │
  │  │ Content-Type: application │  │
  │  │ /json                     │  │
  │  │ Set-Cookie: lang=zh-CN   │  │
  │  │                           │  │
  │  │ {"temp": 28, "desc": "晴"}│  │
  │  └────────────────────────────┘  │
  │                                   │
  │  ┌─ TCP 四次挥手 ──────────────┐  │
  │  │ FIN ──────────────→         │  │
  │  │        ←──────────── ACK    │  │
  │  │        ←──────────── FIN    │  │
  │  │ ACK ──────────────→         │  │
  │  └─────────────────────────────┘  │
  ▼                                   ▼

2. requests 库架构

你的 Python 代码
    │
    ▼
┌─────────────────────────────────────────┐
│           requests 库 (高级 API)        │
│  get()  post()  put()  delete()        │
│  Session()  Response.json()            │
│  Hooks   Auth   Proxies                │
└──────────────────┬──────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────┐
│          urllib3 (底层 HTTP 客户端)      │
│  ConnectionPool  HTTPSConnection       │
│  重试机制  连接复用   SSL/TLS          │
└──────────────────┬──────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────┐
│          http.client (标准库)           │
│  HTTPConnection  HTTPSConnection       │
│  HTTP/1.1   Socket 通信               │
└──────────────────┬──────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────┐
│         socket (TCP/UDP 传输层)         │
│  bind()  connect()  send()  recv()     │
└─────────────────────────────────────────┘
                ┌───────────┐
                │  浏览器    │
                │  (Client)  │
                └─────┬─────┘
                      │
      1. POST /login  │  {username, password}
                      │
                      ▼
                ┌───────────┐
                │  服务器    │  ─→ 验证身份
                │  (Server)  │  ─→ 生成 Session ID
                └─────┬─────┘  ─→ 存到 Session Store
                      │
      2. Set-Cookie:  │  session_id=abc123
         Max-Age=3600 │
         Path=/       │
         HttpOnly     │
                      │
                      ▼
                ┌───────────┐
                │  浏览器    │  ─→ 保存 Cookie
                │  (Client)  │  ─→ 以后每次请求自动携带
                └─────┬─────┘
                      │
      3. GET /profile │  Cookie: session_id=abc123
                      │
                      ▼
                ┌───────────┐
                │  服务器    │  ─→ 查找 Session
                │  (Server)  │  ─→ 获取用户信息
                └───────────┘  ─→ 返回响应

Cookie 属性:
  Domain  = 哪个域名可用
  Path    = 哪个路径可用
  Max-Age = 过期时间
  Secure  = 仅 HTTPS
  HttpOnly= JS 不可访问 (防 XSS)
  SameSite= 跨站请求限制 (防 CSRF)

4. 重试机制原理

请求开始
  │
  ├── 成功? ──→ 返回结果 ✅
  │
  └── 失败? (网络错误/5xx/429)
        │
        ├── 还有重试次数?
        │     │
        │     ├── 是 → 等待 backoff 时间
        │     │       │
        │     │       ▼
        │     │   再次请求 ──→ 计数 +1
        │     │
        │     └── 否 → 抛出 RetryError ❌
        │
        └── 不重试的错误 (4xx 非 429)
              │
              ▼
          直接抛出异常

退避算法 (Exponential Backoff):
  Wait = backoff_factor × (2 ^ (attempt - 1))

  attempt=1:  0.5 × 1 = 0.5 秒
  attempt=2:  0.5 × 2 = 1   秒
  attempt=3:  0.5 × 4 = 2   秒
  attempt=4:  0.5 × 8 = 4   秒

5. 连接池工作原理

requests.Session
    │
    ▼
┌──────────────────────────────────────────┐
│          ConnectionPool                  │
│  ┌────────────────────────────────────┐  │
│  │  空闲连接队列 (Ready Queue)        │  │
│  │  ┌─────┐ ┌─────┐ ┌─────┐         │  │
│  │  │conn1│ │conn2│ │conn3│  ...    │  │
│  │  │idle │ │idle │ │idle │         │  │
│  │  └─────┘ └─────┘ └─────┘         │  │
│  └────────────────────────────────────┘  │
│                                          │
│  请求到达:                               │
│  1. 从空闲队列取一个连接                 │
│  2. 如果队列空 → 创建新连接              │
│  3. 用完 → 归还到空闲队列                │
│  4. 连接空闲太久 → 关闭回收              │
└──────────────────────────────────────────┘

无连接池:
  请求1 → 建立连接 → 请求 → 关闭连接
  请求2 → 建立连接 → 请求 → 关闭连接
  请求3 → 建立连接 → 请求 → 关闭连接
  (每次都要 TCP 三次握手 + TLS 握手)

有连接池:
  请求1 → 建立连接 → 请求 → 归还连接 ──┐
  请求2 → 复用连接 → 请求 → 归还连接 ──┤
  请求3 → 复用连接 → 请求 → 归还连接 ──┘
  (只有第 1 次需要握手,后续复用)

6. 天气 CLI 应用架构

┌───────────────┐     ┌───────────────┐     ┌───────────────┐
│  用户输入      │────→│  WeatherCLI   │────→│  WeatherAPI   │
│  python3      │     │  主程序       │     │  HTTP 请求    │
│  weather.py   │     │  · 参数解析   │     │  · wttr.in    │
│  北京         │     │  · 显示格式化 │     │  · 自动重试   │
└───────────────┘     └───────┬───────┘     └───────────────┘
                              │                        │
                              ▼                        ▼
                     ┌───────────────┐     ┌───────────────┐
                     │  WeatherCache  │     │  HTTP 响应    │
                     │  本地缓存      │     │  JSON 数据    │
                     │  /tmp/weather  │     │              │
                     │  30min 有效期  │     │              │
                     └───────────────┘     └───────────────┘