☰
第1章:requests术语全景与HTTP协议原理
2026/10/17 3:19:44 网站建设 项目流程

1. 项目背景

业务场景

某电商公司新入职了 3 名开发工程师,他们被分配到订单系统的对接组,需要调用支付网关、物流追踪、用户中心等十余个第三方 REST API。组长在第一天就抛出了任务:用 Python 写一个通用的 HTTP 客户端封装,对接各业务方的接口。
然而问题来了——当团队开始动手时,发现每个人对 HTTP 的理解程度参差不齐。有人用os.system("curl ...")拼字符串,有人直接上socket编程手写 HTTP 报文,更有人不知道 HTTP 和 HTTPS 的区别,直接把 API Key 写在 URL 参数里明文传输。代码 review 时,资深工程师看得心惊肉跳。

痛点

问题一:术语混乱。HTTP、HTTPS、URL、URI、Cookie、Session、代理——团队成员对同一概念的理解各不相同。有人说"发个 POST 请求"实际上发的是 GET 带 body;有人说"用 Session 保持登录"实际上理解成了服务端的 Session 机制而非客户端的 Cookie 持久化。

问题二:协议认知缺失。大部分开发知道 HTTP 是"请求-响应"模型,但不清楚一次完整的 HTTPS 请求经历了 DNS 解析 → TCP 三次握手 → TLS 握手 → HTTP 请求 → HTTP 响应 → 四次挥手这一完整链路。当遇到线上"请求慢了 2 秒"时,无法定位是 DNS、TCP、TLS 还是服务端处理的问题。

问题三:requests 黑盒化使用。很多开发者会requests.get(url)就算通关,但不知道底层经历了什么——Session 如何管理连接?Adapter 如何桥接 urllib3?urllib3 又如何调用标准库?这种黑盒用法导致在遇到超时、SSL 错误、连接池耗尽等生产问题时束手无策。

痛点可视化流程:

开发者视角:requests.get(url) → ❓魔法❓ → Response 实际情况: DNS解析 → TCP握手 → TLS协商 → 发送HTTP报文 → 等待服务器处理 → 接收HTTP响应 → 解析响应体 ↑ 可能慢 ↑ ↑ 可能失败 ↑ ↑ 证书过期 ↑ ↑超时↑ ↑连接断开↑

2. 项目设计

小胖(刚啃完一块披萨,满嘴油光):
“大师,我就不明白了。不就是调个接口嘛,一个requests.get()不就搞定了?我上家公司的代码全是这么写的,从来没出过问题。为啥还要学什么 HTTP 协议、请求头、Cookie 这一大堆?这不就跟食堂打饭一样——上去说你要啥,大妈给你打,完事儿!”

大师(放下手中的咖啡,微微一笑):
“小胖你这个比喻很形象。不过我问你一个问题——如果食堂有 10 个窗口,你怎么知道去哪个窗口?如果要刷卡付费,你的饭卡信息怎么传递给收银台?如果今天你感冒了,大妈怎么知道少给你打辣椒?”

小胖(挠了挠头):
“呃……那我得先看窗口上写着卖什么。刷卡的话,把卡给大妈刷一下?”

大师:
“你看,这不就对上了——窗口编号就是 URL 路径,刷卡就是认证(Authorization),告诉大妈不要辣椒就是请求头(Header)。HTTP 协议本质上就是客户端和服务器之间的一套约定——你告诉我你想要什么(Request),我告诉你我有没有(Response),中间用一套大家都懂的格式来传话。”

小白(从笔记本屏幕后探出头来,推了推眼镜):
“那requests.get()到底帮我们做了什么?它不可能真的一行代码就搞定一切吧?我总觉得这背后有好多层抽象。”

大师:
“好问题。让我们把 requests 的架构画出来——”

大师在白板上画出架构图:

┌─────────────────────────────────────────────────────┐ │ 【用户层】 │ │ requests.get() / post() / Session │ │ Request → PreparedRequest → Response │ ├─────────────────────────────────────────────────────┤ │ 【适配层】 │ │ HTTPAdapter.send() │ │ 职责:代理解析、SSL配置、连接池选择、重试策略 │ ├─────────────────────────────────────────────────────┤ │ 【传输层:urllib3】 │ │ PoolManager / HTTPConnectionPool │ │ 职责:连接复用、Keep-Alive、超时控制 │ ├─────────────────────────────────────────────────────┤ │ 【标准库层】 │ │ http.client.HTTPConnection / ssl.SSLContext │ │ 职责:TCP连接、TLS握手、HTTP报文解析 │ ├─────────────────────────────────────────────────────┤ │ 【操作系统层】 │ │ socket / TCP / DNS / TLS │ └─────────────────────────────────────────────────────┘

大师:
“所以你看,requests.get(url)这一行代码,至少跨越了四层抽象。每一层都有自己可能出问题的地方。”

小白(眼睛一亮):
“那如果一个请求失败了,我怎么知道是请求构建的问题、连接层的问题,还是服务器的问题?如果每个层面都会抛出不同的异常,异常处理岂不是要写很多层?”

大师:
"这就是为什么我们要理解 HTTP 协议的全链路。我给你一个故障定位的决策树:

  1. 如果DNS 解析失败→ 检查网线插没插、DNS 配置对不对
  2. 如果TCP 连接超时→ 检查防火墙规则、目标端口是否开放
  3. 如果TLS 握手失败→ 检查证书是否过期、CA 是否受信任
  4. 如果HTTP 请求发出但无响应→ 检查服务端是否 Hang、负载是否过高
  5. 如果HTTP 响应返回 4xx/5xx→ 检查参数是否正确、服务端日志"

小胖(若有所思):
“等等,大师。你说 Session 可以保持 Cookie——那我是不是可以理解为,Session就像我在食堂办了一张饭卡,上面有我的余额和消费记录。每次刷卡不用自报家门,刷卡机自己就知道我是谁了?”

大师(赞许地点头):
“正是!你这个比喻非常精准。”

生活比喻技术映射
食堂窗口编号URL 路径(Path)
饭卡余额与身份Cookie / Session
刷卡动作每次请求自动携带 Cookie
办卡充值的柜台登录接口,返回 Set-Cookie
这张卡只能在食堂用Cookie 的 Domain 限制

小白:
“那最后一个问题。我听说 HTTP 是无状态的,但 Cookie 又让它看起来有状态?这不是矛盾吗?”

大师:
“这恰恰是 HTTP 协议设计的精髓——协议层面无状态,应用层面有状态。HTTP 每一次请求在协议层面都是独立的,服务器不记得上一秒谁来请求过。但通过在请求头里塞 Cookie/Token,我们在应用层实现了状态保持。就像你去菜市场买菜,卖菜大妈不认识你,但你掏出会员卡,系统就认出你和你上次的消费记录——菜市场本身是无状态的,会员系统是有状态的。”


3. 项目实战

环境准备

依赖与版本:

组件版本用途
Python3.9+运行环境
requests2.32.4HTTP 客户端库
urllib32.2.3requests 底层传输依赖
Wireshark4.x抓包分析 HTTP 报文

安装命令:

# 创建虚拟环境python-mvenv venv# 激活虚拟环境 (Windows)venv\Scripts\activate# 激活虚拟环境 (Mac/Linux)sourcevenv/bin/activate# 安装 requestspipinstallrequests==2.32.4# 验证安装python-c"import requests; print(requests.__version__)"# 输出: 2.32.4

分步实现

步骤一:解剖一个最简单的 HTTP GET 请求

目标:发起一个 GET 请求到 httpbin.org,理解 Response 对象的基本属性。

importrequests# 发送 GET 请求response=requests.get("https://httpbin.org/get")# Response 对象就是整个 HTTP 响应的 Python 表示print(f"状态码:{response.status_code}")# 200print(f"响应头:{dict(response.headers)}")# 所有响应头字典print(f"编码:{response.encoding}")# utf-8print(f"耗时:{response.elapsed}")# 0:00:00.523456print(f"请求URL:{response.url}")# https://httpbin.org/getprint(f"响应体(前200字符):{response.text[:200]}")# 关键属性速查表# response.status_code → HTTP 状态码 (200, 404, 500...)# response.headers → 响应头 (CaseInsensitiveDict)# response.encoding → 自动检测的编码# response.text → 解码后的文本响应体# response.content → 原始二进制响应体# response.json() → JSON 解析后的 dict# response.url → 最终请求的 URL (可能经过了重定向)# response.elapsed → 从发送到接收的耗时 (timedelta)# response.request → 对应的 PreparedRequest 对象

运行输出:

状态码: 200 响应头: {'Date': 'Sun, ...', 'Content-Type': 'application/json', ...} 编码: utf-8 耗时: 0:00:00.523456 请求URL: https://httpbin.org/get 响应体(前200字符): { "args": {}, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Host": "httpbin.org", "User-Agent": "python-requests/2.32.4", ... } }
步骤二:用 Wireshark 抓取一次完整的 HTTPS 请求

目标:验证一次requests.get()背后的完整网络交互过程。

抓包步骤:

  1. 打开 Wireshark,选择网卡 → 过滤条件ssl.handshake.extensions_server_name contains "httpbin.org" or http
  2. 在 Python 中执行requests.get("https://httpbin.org/get")
  3. 停止抓包,分析捕获到的数据包

抓包分析——看到的 TCP 交互序列:

[1] 客户端 → 服务器: TCP SYN (三次握手开始) [2] 服务器 → 客户端: TCP SYN-ACK [3] 客户端 → 服务器: TCP ACK (TCP 连接建立) ↑ 以上:TCP 三次握手,耗时 ~50ms [4] 客户端 → 服务器: TLS ClientHello - 支持的 TLS 版本: 1.2, 1.3 - 支持的加密套件: TLS_AES_256_GCM_SHA384... - SNI: httpbin.org [5] 服务器 → 客户端: TLS ServerHello, Certificate, ServerHelloDone - 选定 TLS 1.3 - 服务器证书链 [6] 客户端 → 服务器: TLS ClientKeyExchange, ChangeCipherSpec, Finished [7] 服务器 → 客户端: TLS ChangeCipherSpec, Finished ↑ 以上:TLS 握手,耗时 ~150ms [8] 客户端 → 服务器: HTTP GET /get HTTP/1.1 Host: httpbin.org User-Agent: python-requests/2.32.4 Accept: */* Accept-Encoding: gzip, deflate [9] 服务器 → 客户端: HTTP/1.1 200 OK Content-Type: application/json ...(响应体) ↑ 以上:HTTP 请求-响应,耗时 ~300ms [10-12] TCP FIN/ACK (四次挥手)

映射表——抓包结果 vs requests API:

Wireshark 中看到的requests API 对应
DNS A 记录查询无需代码,操作系统自动完成
TCP SYN → SYN-ACK → ACK无需代码,urllib3 自动完成
TLS ClientHelloverify=True触发证书验证
TLS Certificateresponse.cert获取服务器证书信息
HTTP GET /getrequests.get("https://httpbin.org/get")
HTTP/1.1 200 OKresponse.status_code == 200
Content-Type: application/jsonresponse.headers["Content-Type"]
步骤三:打印完整的请求响应信息(含隐式头)

目标:编写一个诊断函数,输出一次请求的完整信息,包括 requests 隐式添加的头。

importrequestsdefinspect_request(url:str,method:str="GET",**kwargs):"""打印请求和响应的完整诊断信息"""session=requests.Session()print("="*60)print(f"REQUEST:{method}{url}")print("="*60)# 构建 PreparedRequest 查看发送前的状态req=requests.Request(method,url,**kwargs)prepared=session.prepare_request(req)print(f"\n[发送前的 PreparedRequest]")print(f" Method:{prepared.method}")print(f" URL:{prepared.url}")print(f" Headers:")fork,vinprepared.headers.items():print(f"{k}:{v}")print(f" Body:{prepared.body}")# 发送请求response=session.send(prepared)print(f"\n[响应 Response]")print(f" Status:{response.status_code}{response.reason}")print(f" Elapsed:{response.elapsed}")print(f" Final URL:{response.url}")print(f" Redirects:{len(response.history)}")ifresponse.history:fori,rinenumerate(response.history):print(f" [{i}]{r.status_code}→{r.headers.get('Location','N/A')}")print(f" Response Headers:")fork,vinresponse.headers.items():print(f"{k}:{v}")print(f" Content ({len(response.content)}bytes):{response.text[:300]}...")print("="*60)returnresponse# 运行诊断inspect_request("https://httpbin.org/get",params={"name":"小明","page":1})

运行输出(关键部分):

[发送前的 PreparedRequest] Method: GET URL: https://httpbin.org/get?name=%E5%B0%8F%E6%98%8E&page=1 Headers: User-Agent: python-requests/2.32.4 ← 隐式添加 Accept-Encoding: gzip, deflate ← 隐式添加 Accept: */* ← 隐式添加 Connection: keep-alive ← 隐式添加 Host: httpbin.org ← 隐式添加 Body: None [响应 Response] Status: 200 OK Elapsed: 0:00:00.623451 Final URL: https://httpbin.org/get?name=%E5%B0%8F%E6%98%8E&page=1 Redirects: 0

可能遇到的坑及解决方法

坑1:SSL 证书验证失败

SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed

原因:在 Windows 上 Python 可能找不到系统 CA 证书包。

解决:

# 方案1:安装 certifi 包并指定pipinstallcertifi
importcertifi requests.get("https://example.com",verify=certifi.where())
# 方案2:安装 python-certifi-win32 (Windows 专享)pipinstallpip-system-certs

⚠️ 警告:永远不要在生产代码中使用verify=False,这会让你的应用暴露于中间人攻击。

坑2:代理干扰

ProxyError: Cannot connect to proxy.

原因:系统环境变量中设置了HTTP_PROXY,但代理不可用。

解决:

# 禁用系统代理requests.get("https://example.com",proxies={"http":None,"https":None})# 或配置正确的代理requests.get("https://example.com",proxies={"https":"http://127.0.0.1:7890"})

测试验证

编写单元测试验证对 requests 基本功能的理解:

importpytestimportrequestsclassTestRequestsBasics:"""验证第1章所学的 requests 基本概念"""deftest_get_request_returns_200(self):"""验证 GET 请求能正常返回 200"""resp=requests.get("https://httpbin.org/get")assertresp.status_code==200deftest_response_has_expected_attributes(self):"""验证 Response 对象包含所有预期属性"""resp=requests.get("https://httpbin.org/get")asserthasattr(resp,"status_code")asserthasattr(resp,"headers")asserthasattr(resp,"text")asserthasattr(resp,"content")asserthasattr(resp,"request")asserthasattr(resp,"url")asserthasattr(resp,"elapsed")asserthasattr(resp,"encoding")deftest_prepared_request_url_encoding(self):"""验证 requests 能正确处理 URL 参数编码"""resp=requests.get("https://httpbin.org/get",params={"q":"小胖"})assert"小胖"notinresp.urlassert"%E5%B0%8F%E8%83%96"inresp.url# UTF-8 percent-encodeddeftest_requests_adds_implicit_headers(self):"""验证 requests 会自动添加 Host/User-Agent 等头"""session=requests.Session()req=requests.Request("GET","https://httpbin.org/get")prepared=session.prepare_request(req)assert"Host"inprepared.headersassert"User-Agent"inprepared.headers

运行测试:

pytest test_chapter1.py-v

4. 项目总结

核心知识回顾

层次关键技术requests 对应
DNS域名 → IP操作系统自动完成
TCP三次握手/四次挥手urllib3 连接管理
TLS加密协商/证书验证verify/cert参数
HTTP请求报文/响应报文Request/Response 模型
SessionCookie 自动管理requests.Session
Adapter传输层抽象HTTPAdapter

优点 & 缺点

对比维度requestsurllib(标准库)http.client(标准库)
API 易用性★★★★★★★☆☆☆★☆☆☆☆
Cookie 自动管理★★★★★☆☆☆☆☆☆☆☆☆☆
Session 连接池★★★★☆☆☆☆☆☆☆☆☆☆☆
SSL 证书处理★★★★☆★★☆☆☆★★☆☆☆
异步支持☆☆☆☆☆★★★★☆☆☆☆☆☆
学习曲线★★★★★★★★☆☆★★☆☆☆

适用场景

  • API 数据采集:调用 RESTful 接口、Web Scraping
  • 微服务间通信:同步 HTTP 调用的首选方案
  • 自动化测试:接口测试、端到端测试
  • 运维监控:健康检查、指标拉取
  • 快速原型:验证第三方 API、PoC 开发

不适用场景

  • 高并发异步场景:需要 asyncio 配合的推荐 aiohttp/httpx
  • WebSocket 长连接:requests 不支持 WebSocket 协议,需 websocket-client

注意事项

  • 超时设置:requests 默认timeout=None(永不超时),生产环境务必显式设置
  • Session 复用:不要每次请求都创建新 Session,否则连接池功能无效
  • verify=False 危险:仅在测试环境使用,生产环境绝对禁用
  • 版本兼容:requests 2.x 与 urllib3 1.x/2.x 的 API 有差异,注意版本锁定

常见踩坑经验

案例一:生产环境请求卡死。某次故障排查发现某个微服务在高峰期 CPU 飙升到 100%,最终定位到代码中requests.get(url)没有设置timeout,后端 Hang 住后客户端连接永远不释放,导致线程池耗尽。根因:默认 timeout=None。修复:所有请求强制配置timeout=(3.0, 30.0)。

案例二:SSL 验证失败让 CI 全红。CI 流水线突然全部失败,报 SSL 证书错误。排查后发现 LetsEncrypt 根证书过期,CI 镜像未更新 CA 包。根因:镜像过旧。修复:Dockerfile 中主动apt-get update && apt-get install ca-certificates。

案例三:Cookie 丢失导致鉴权反复失败。开发反馈登录成功后的数据查询却报 401。排查发现每次请求用的是requests.get()而非 Session 对象,Cookie 无法跨请求保持。根因:滥用无状态 API。修复:登录 + 后续请求统一使用同一个requests.Session实例。

思考题

  1. 架构理解题:如果不依赖 requests,只使用 Python 标准库(socket + ssl),请写出一个最简单的 HTTP GET 请求客户端。你需要手动构造 HTTP 报文、解析响应。对比使用 requests 的代码量,理解 requests 帮你节省了多少工作。

  2. 协议思考题:HTTP/1.1 中Connection: keep-alive的作用是什么?如果服务器返回Connection: close,requests 如何处理?查阅 HTTPAdapter 源码,找出连接复用的判断条件(提示:src/requests/adapters.py第 300 行附近)。

延伸阅读与资源

后端工程师的 AI 转型第一课:Ollama 与私有化大模型实战
10倍开发者的 Dify 魔法书:从零构建全栈 AI 应用
后端工程师转型AI第一课-Ollama 与私有化大模型实战

大型语言模型(LLM) vLLM 高性能推理落地实战

Agent开发之LlamaIndex 实战修炼与源码进阶

大语言模型Transformers 实战修炼与源码剖析

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询