第7章 7.1 HTTP 协议与 header
⏱️ 阅读约 90 分钟,实战部分建议边看边敲
🎯 开场 3 分钟:为什么要学这个?
上完第 6 章「6.5 综合实战:MVC 框架迷你版」,你现在手里有了一个带感的工具箱——路由、控制器、数据层,能搭出一个完整的小网站了。
但你有没有遇到过这些糟心事?
- 打开某个网页,白屏转圈圈,最后蹦出来个 404 Not Found
- 登录 APP 成功后,下次打开居然还要重新登录(Cookie 没带好)
- 调用别人接口,返回了数据但乱码了(编码没声明)
这些问题的根源,都在 HTTP 协议里。
HTTP 是浏览器和服务器之间的「对话语言」。你写的代码发出去的每一个请求、服务器返回的每一个响应,背后都在说着 HTTP 话。
这一章我们就来搞清楚:HTTP 协议到底是什么?header(头部)里塞的那些字段都是干啥的? 学完之后,你就能看懂请求和响应里那些「隐形的情报」,debug 起来不再是盲人摸象。
🧱 基础 25 分钟:核心概念
\n\n
\n\n
\n\n什么是 HTTP?先把它想成「快递包裹」
HTTP(HyperText Transfer Protocol)—— 超文本传输协议。说人话:服务器和浏览器之间互相「喊话」的规则。
你可以把它想象成寄快递:
| 现实中的快递 | HTTP 通信 |
|---|---|
| 你(发件人) | 浏览器 / 客户端 |
| 快递公司 | HTTP 协议 |
| 包裹里的东西(衣服、食品) | 请求/响应的 body(数据内容) |
| 快递单上的信息(收件人、地址、电话) | HTTP header(头部信息) |
header 就是贴在「包裹」上的那张快递单——它不装东西,但告诉快递公司怎么送、怎么收、要不要签字验货。
请求(Request)和响应(Response)
HTTP 是一种「一问一答」的通信模式:
你 → 发请求 → 服务器
你 ← 回响应 ← 服务器
举个例子,你去餐厅点菜:
你(请求):服务员,来一份宫保鸡丁
服务员(响应):好的,这是您的宫保鸡丁(或者:不好意思,厨师下班了)
HTTP 请求同理,只不过换成了「GET /index.html HTTP/1.1」这种格式。
一个请求长什么样?
# 这是一个 HTTP 请求的「文本格式」,我们用代码模拟看一下
request_text = """GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html
Cookie: session_id=abc123"""
print("=== HTTP 请求示例 ===")
print(request_text)
运行结果:
=== HTTP 请求示例 ===
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html
Cookie: session_id=abc123
第一行叫请求行(Request Line):GET 是方法,/index.html 是路径,HTTP/1.1 是版本。
下面几行,每行一个 Key: Value,就是 header(头部)。
响应长什么样?
response_text = """HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 1234
Set-Cookie: session_id=xyz789; Path=/
<!DOCTYPE html>
<html>这里是网页内容...</html>"""
print("=== HTTP 响应示例 ===")
print(response_text)
运行结果:
=== HTTP 响应示例 ===
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 1234
Set-Cookie: session_id=xyz789; Path=/
<!DOCTYPE html>
<html>这里是网页内容...</html>
第一行是状态行(Status Line):200 是状态码,OK 是状态描述。
空行之后是 body(正文内容),就是你在浏览器里能看到的东西。
状态码:服务器的「暗号」
状态码是服务器告诉你「这份答复是个什么情况」的数字:
| 状态码 | 含义 | 类比 |
|---|---|---|
| 200 | 一切正常 | 「给您打包好了,请慢用」 |
| 301/302 | 跳转 | 「这道菜移到了新窗口,请去那边排队」 |
| 400 | 请求有错 | 「您这单子填错了,退回重写」 |
| 401/403 | 没权限 | 「对不起,会员才能进」 |
| 404 | 找不到 | 「这道菜不在菜单上」 |
| 500 | 服务器崩了 | 「后厨着火了,出不了菜了」 |
常用请求头(Request Headers)
| 请求头 | 干啥的 | 举个例子 |
|---|---|---|
Host |
目标网站地址 | Host: www.example.com |
User-Agent |
你是谁(浏览器版本) | User-Agent: Mozilla/5.0... |
Accept |
你想要什么类型的数据 | Accept: application/json |
Cookie |
带上之前的「会员卡」 | Cookie: session_id=abc |
Content-Type |
发送的数据格式 | Content-Type: application/json |
常用响应头(Response Headers)
| 响应头 | 干啥的 | 举个例子 |
|---|---|---|
Content-Type |
返回的数据是什么类型 | Content-Type: text/html; charset=utf-8 |
Content-Length |
数据有多大(字节数) | Content-Length: 2048 |
Set-Cookie |
服务器让你记住的「会员卡」 | Set-Cookie: theme=dark; Path=/ |
Cache-Control |
浏览器要不要缓存 | Cache-Control: max-age=3600 |
Access-Control-Allow-Origin |
允许谁来拿数据(跨域) | Access-Control-Allow-Origin: * |
用 Python 发起一个真实请求
光看不行,我们来真枪实弹。用 requests 库发一个请求,看看响应头里都有啥:
import requests
# 发送一个 GET 请求
response = requests.get("https://httpbin.org/get")
print("=== 状态码 ===")
print(response.status_code)
print("\n=== 响应头(Headers)===")
for key, value in response.headers.items():
print(f"{key}: {value}")
print("\n=== 响应体(Body)===")
print(response.text[:200]) # 只打印前200字符
运行结果:
=== 状态码 ===
200
=== 响应头(Headers)===
Date: Thu, 26 Jun 2025 12:00:00 GMT
Content-Type: application/json
Content-Length: 256
...
=== 响应体(Body)===
{
"args": {},
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.28.0"
},
...
}
httpbin.org 是一个专门用来测试 HTTP 请求的网站,它会把你的请求头原封不动地返回来,让你看看自己「寄出去的快递单」长什么样。
发 POST 请求,带上自定义 Header
import requests
# 定义自定义请求头
headers = {
"User-Agent": "MyPythonApp/1.0",
"Accept": "application/json",
"X-My-Custom-Header": "HelloServer"
}
# POST 请求,带 data
response = requests.post(
"https://httpbin.org/post",
data={"username": "xiaoming", "action": "login"},
headers=headers
)
print("=== 状态码 ===")
print(response.status_code)
print("\n=== 服务器收到的 Headers ===")
result = response.json()
print(result["headers"])
print("\n=== 服务器收到的 Data ===")
print(result["form"])
运行结果:
=== 状态码 ===
200
=== 服务器收到的 Headers ===
{'Host': 'httpbin.org', 'User-Agent': 'MyPythonApp/1.0', 'Accept': 'application/json', 'X-My-Custom-Header': 'HelloServer'}
=== 服务器收到的 Data ===
{'username': 'xiaoming', 'action': 'login'}
看,X-My-Custom-Header 是我们自定义的头部,服务器原封不动收到了。这个机制常用来传 API 密钥、版本号、来源标识等等。
🔥 实战 35 分钟:3 个递进的小项目
项目 1(5 分钟):用 Header「伪装」成浏览器
目标:用 Python 发请求,但让服务器以为你是一个 Chrome 浏览器。
import requests
# 模拟 Chrome 浏览器的 User-Agent
chrome_headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8"
}
response = requests.get("https://www.baidu.com", headers=chrome_headers)
print(f"状态码: {response.status_code}")
print(f"响应头 Content-Type: {response.headers.get('Content-Type', 'N/A')}")
print(f"返回内容长度: {len(response.text)} 字符")
预期输出:
状态码: 200
响应头 Content-Type: text/html
返回内容长度: 12345 字符
一句话解释:通过设置 User-Agent,我们「伪装」成了 Chrome,服务器就会返回给浏览器看的完整 HTML,而不是拒绝或者返回错误格式。
项目 2(15 分钟):读取 JSON API 并解析 Header
目标:调用一个公开 API,读出数据,并检查关键 Header。
这次我们用 GitHub 的公开 API,查询一个用户的信息:
import requests
def query_github_user(username):
"""查询 GitHub 用户信息,同时展示响应头"""
url = f"https://api.github.com/users/{username}"
headers = {
"Accept": "application/vnd.github.v3+json", # 明确要 JSON 格式
"User-Agent": "Python-HTTP-Demo/1.0"
}
response = requests.get(url, headers=headers)
# 打印关键响应头
print(f"=== 响应头分析 ===")
print(f"状态码: {response.status_code}")
print(f"Content-Type: {response.headers.get('Content-Type')}")
print(f"X-RateLimit-Remaining: {response.headers.get('X-RateLimit-Remaining', 'N/A')} (剩余请求次数)")
print(f"ETag: {response.headers.get('ETag', 'N/A')} (缓存标识)")
if response.status_code == 200:
data = response.json()
print(f"\n=== 用户信息 ===")
print(f"用户名: {data['login']}")
print(f"粉丝数: {data['followers']}")
print(f"仓库数: {data['public_repos']}")
elif response.status_code == 404:
print(f"\n用户 '{username}' 不存在")
else:
print(f"\n请求失败: {response.json().get('message', '未知错误')}")
return response
# 测试
query_github_user("torvalds")
query_github_user("fakeuser12345")
预期输出:
=== 响应头分析 ===
状态码: 200
Content-Type: application/json; charset=utf-8
X-RateLimit-Remaining: 59 (剩余请求次数)
ETag: "xxx123xxx"
=== 用户信息 ===
用户名: torvalds
粉丝数: 123456
仓库数: 6
=== 响应头分析 ===
状态码: 404
用户 'fakeuser12345' 不存在
一句话解释:X-RateLimit-Remaining 这个 Header 告诉你「今天还能调几次 API」,用完了服务器会拒绝服务。ETag 用来做缓存,下次请求时带上它,服务器如果数据没变就返回 304 Not Modified,省流量。
项目 3(15 分钟):做一个「Header 检查器」小工具
目标:输入一个 URL,自动发送请求,打印出所有 Header 信息,并标注每个 Header 的「体检结论」。
这个工具可以帮你快速了解任意网站的技术栈。
import requests
from datetime import datetime
def inspect_headers(url):
"""检查任意 URL 的 HTTP Headers,给出健康报告"""
try:
response = requests.get(url, timeout=10)
status_code = response.status_code
print(f"\n{'='*50}")
print(f"📍 检查目标: {url}")
print(f"⏰ 检查时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"{'='*50}")
# 状态码诊断
print(f"\n🏥 体检结果:")
if status_code == 200:
print(f" ✅ 状态码 200 - 服务器正常运行")
elif status_code == 301 or status_code == 302:
print(f" 🔄 状态码 {status_code} - 发生重定向")
print(f" 📍 重定向到: {response.headers.get('Location', 'N/A')}")
elif status_code == 403:
print(f" 🔒 状态码 403 - 访问被拒绝(可能被反爬)")
elif status_code == 404:
print(f" ❌ 状态码 404 - 页面不存在")
else:
print(f" ⚠️ 状态码 {status_code} - 需要关注")
# 安全相关 Header 检查
print(f"\n🔐 安全 Header 检查:")
security_headers = {
"Content-Security-Policy": "内容安全策略(CSP)",
"X-Frame-Options": "防点击劫持",
"X-Content-Type-Options": "防止 MIME 类型嗅探",
"Strict-Transport-Security": "强制 HTTPS",
"Set-Cookie": "会话 Cookie"
}
for header, description in security_headers.items():
if header in response.headers:
print(f" ✅ {header}: {description}")
else:
print(f" ⚪ {header}: 未设置({description})")
# 性能相关 Header
print(f"\n⚡ 性能 Header:")
cache_header = response.headers.get("Cache-Control", "未设置")
expires_header = response.headers.get("Expires", "未设置")
print(f" 缓存策略: {cache_header}")
print(f" 过期时间: {expires_header}")
# 服务器指纹
print(f"\n🖥️ 服务器指纹:")
server = response.headers.get("Server", "未知")
x_powered = response.headers.get("X-Powered-By", "未知")
print(f" Server: {server}")
print(f" X-Powered-By: {x_powered}")
# 完整 Header 列表
print(f"\n📋 完整 Header 列表(共 {len(response.headers)} 项):")
for key, value in response.headers.items():
print(f" {key}: {value[:60]}{'...' if len(value) > 60 else ''}")
except requests.exceptions.Timeout:
print(f"❌ 连接超时(超过10秒)")
except requests.exceptions.RequestException as e:
print(f"❌ 请求失败: {e}")
# 测试几个典型网站
inspect_headers("https://www.baidu.com")
inspect_headers("https://github.com")
预期输出:
==================================================
📍 检查目标: https://www.baidu.com
⏰ 检查时间: 2025-06-26 12:00:00
==================================================
🏥 体检结果:
状态码 200 - 服务器正常运行
🔐 安全 Header 检查:
Content-Security-Policy: 未设置(内容安全策略(CSP))
X-Frame-Options: 未设置(防点击劫持)
..
⚡ 性能 Header:
存策略: no-cache
期时间: Thu, 26 Jun 2025 12:05:00 GMT
🖥️ 服务器指纹:
erver: Apache
-Powered-By: PHP/7.4
...
一句话解释:这个工具把乱七八糟的 Header 翻译成了「体检报告」,让你一眼看出这个网站安全不安全、快不快、用的什么技术。
💪 进阶 20 分钟:常见坑 + 性能小贴士
坑 1:忘记处理中文编码
❌ 错误写法(乱码):
response = requests.get("https://example.com")
print(response.text) # 遇到中文可能乱码
✅ 正确写法(自动识别编码):
response = requests.get("https://example.com")
response.encoding = 'utf-8' # 手动指定,或者让 requests 自动检测
print(response.text)
原因:服务器返回的 Content-Type 里的 charset 不一定对,requests 会尝试自动检测,但有时候会判断错误。
坑 2:请求后不检查状态码直接用数据
❌ 错误写法(崩溃风险):
response = requests.get("https://example.com/user/99999")
data = response.json() # 如果返回 404,json() 可能报错
print(data["name"])
✅ 正确写法(先检查再使用):
response = requests.get("https://example.com/user/99999")
if response.status_code == 200:
data = response.json()
print(data["name"])
else:
print(f"请求失败,状态码: {response.status_code}")
坑 3:Session 不复用,Cookie 丢光光
❌ 错误写法(每次请求都是新「会话」):
requests.get("https://example.com/login", data={"user": "xiaoming"})
requests.get("https://example.com/profile") # 这次请求不认识你
✅ 正确写法(同一个 Session):
session = requests.Session()
session.post("https://example.com/login", data={"user": "xiaoming"})
session.get("https://example.com/profile") # 带着登录态来的
原因:Session 会自动保留 Cookie,相当于在浏览器里连续操作,而不是每次都开新标签页。
坑 4:POST 请求 Body 格式和 Header 不匹配
❌ 错误写法(服务器会懵):
requests.post(url, data={"name": "小明"}, headers={"Content-Type": "application/json"})
✅ 正确写法:
import json
requests.post(url, data=json.dumps({"name": "小明"}), headers={"Content-Type": "application/json"})
# 或者更简单:
requests.post(url, json={"name": "小明"}) # json 参数自动处理格式和 Header
性能小贴士:开启连接复用
HTTP/1.1 默认支持连接复用,但如果你的请求特别频繁,可以用 Session 来保持 TCP 连接不关闭:
session = requests.Session()
adapter = requests.adapters.HTTPAdapter(pool_connections=10, pool_maxsize=20)
session.mount('http://', adapter)
session.mount('https://', adapter)
# 发送 100 个请求,复用连接,省去每次建立 TCP 握手的时间
for i in range(100):
session.get(f"https://httpbin.org/get?id={i}")
效果:每个请求省去约 50-200ms 的 TCP 握手时间(取决于网络延迟),100 个请求能省下 5-20 秒。
调试技巧:用 logging 打印完整请求
import requests
import logging
# 开启 requests 的调试日志
logging.basicConfig(level=logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
response = requests.get("https://httpbin.org/get")
输出会显示完整的 HTTP 握手过程,包括请求头、响应头、状态码,对排查问题特别有用。
✏️ 练习题 + 作业题
练习 1(2 分钟):改个浏览器身份
题目:把项目 1 里的 User-Agent 改成 iPhone 的 Safari 浏览器,重新运行,记录服务器返回的 Content-Type 有什么不同。
- 输入:修改 headers 字典
- 预期输出:能成功请求并打印结果
提示:iPhone 的 User-Agent 一般包含 iPhone 和 Mobile 关键词。
练习 2(2 分钟):加个登录判断
题目:在项目 2 的 query_github_user 函数里,加一个 if 判断:如果是 403 状态码,打印「请求太频繁,请稍后再试」。
- 输入:查询一个存在/不存在的用户
- 预期输出:403 时打印特定提示语
提示:403 表示请求次数超限,不一定是用户不存在。
练习 3(3 分钟):换个 API 调
题目:用项目 2 的方法,改成调用「JSONPlaceholder」这个测试 API(https://jsonplaceholder.typicode.com/users/1),打印用户的 name 和 email 字段。
- 输入:
GET https://jsonplaceholder.typicode.com/users/1 - 预期输出:
Name: Leanne Graham,Email: Sincere@april.biz
提示:JSONPlaceholder 返回的是标准 JSON,不需要特殊的 Accept 头。
练习 4(5 分钟):串项目 2 和项目 3
题目:用项目 2 的 X-RateLimit-Remaining 检测逻辑,改造项目 3 的 Header 检查器——如果 X-RateLimit-Remaining 小于 10,打印警告「API 剩余请求次数不足」。
- 输入:检查 GitHub API 的任意端点
- 预期输出:打印警告或正常结果
提示:GitHub API 的每个响应头里都有 X-RateLimit-Remaining。
练习 5(5 分钟):看图分析报错
题目:假设你收到了一个请求的响应头如下,分析哪里有问题:
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: -1
Cache-Control: no-store
- 输入:分析上述响应头
- 预期输出:指出至少 2 个问题
提示:Content-Length: -1 正常吗?no-store 是什么意思?
作业:做一个「API 侦探」工具
需求描述:做一个命令行小工具,输入一个 URL,自动分析它的 HTTP 响应头,生成一份「诊断报告」。
功能点:
1. 发送 GET 请求,获取响应头
2. 判断并标注状态码(正常/重定向/错误)
3. 检查并标注安全相关 Header(有无 CSP、X-Frame-Options、HSTS 等)
4. 如果是重定向,自动跟随跳转(最多 3 次),记录完整的跳转链
加分项:
1. 支持 -v 参数,打印完整 Header 列表
2. 支持 -o filename.txt 参数,把报告保存到文件
验收标准:
- 能跑起来,命令行参数解析正常
- 报告输出清晰,有 emoji 分隔
- 代码有注释,关键逻辑有说明
提交方式:评论区贴代码或 GitHub 链接
📚 总结 + 资源
一句话总结:HTTP 协议是互联网的「快递系统」,Header 是贴在每个包裹上的「快递单」——学会了读懂它,你就能排查从登录失败到页面乱码的一切奇怪问题。
延伸学习资源:
- MDN HTTP 文档 — 最权威的 HTTP 参考手册
- httpbin.org — 在线 HTTP 测试工具,本文的实验都在这里做的
- 《HTTP 权威指南》— 大部头经典,想深入可以啃
互动钩子:你在实际开发中遇到过哪些 HTTP 相关的坑?比如被反爬拦截、Cookie 失效、编码乱码……评论区聊聊,老粉优先回复!

评论(0)