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() │
└─────────────────────────────────────────┘
3. Cookie 与 Session 机制
┌───────────┐
│ 浏览器 │
│ (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 有效期 │ │ │
└───────────────┘ └───────────────┘