第7章 7.1 HTTP 协议与 header

⏱️ 阅读约 90 分钟,实战部分建议边看边敲

🎯 开场 3 分钟:为什么要学这个?

上完第 6 章「6.5 综合实战:MVC 框架迷你版」,你现在手里有了一个带感的工具箱——路由、控制器、数据层,能搭出一个完整的小网站了。

但你有没有遇到过这些糟心事?

  • 打开某个网页,白屏转圈圈,最后蹦出来个 404 Not Found
  • 登录 APP 成功后,下次打开居然还要重新登录(Cookie 没带好)
  • 调用别人接口,返回了数据但乱码了(编码没声明)

这些问题的根源,都在 HTTP 协议里。

HTTP 是浏览器和服务器之间的「对话语言」。你写的代码发出去的每一个请求、服务器返回的每一个响应,背后都在说着 HTTP 话。

这一章我们就来搞清楚:HTTP 协议到底是什么?header(头部)里塞的那些字段都是干啥的? 学完之后,你就能看懂请求和响应里那些「隐形的情报」,debug 起来不再是盲人摸象。


🧱 基础 25 分钟:核心概念

\n\nSimple tech illustration expla\n\nAI comic creation scene, creat\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 一般包含 iPhoneMobile 关键词。


练习 2(2 分钟):加个登录判断

题目:在项目 2 的 query_github_user 函数里,加一个 if 判断:如果是 403 状态码,打印「请求太频繁,请稍后再试」。

  • 输入:查询一个存在/不存在的用户
  • 预期输出:403 时打印特定提示语

提示:403 表示请求次数超限,不一定是用户不存在。


练习 3(3 分钟):换个 API 调

题目:用项目 2 的方法,改成调用「JSONPlaceholder」这个测试 API(https://jsonplaceholder.typicode.com/users/1),打印用户的 nameemail 字段。

  • 输入: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 是贴在每个包裹上的「快递单」——学会了读懂它,你就能排查从登录失败到页面乱码的一切奇怪问题。

延伸学习资源

  1. MDN HTTP 文档 — 最权威的 HTTP 参考手册
  2. httpbin.org — 在线 HTTP 测试工具,本文的实验都在这里做的
  3. 《HTTP 权威指南》— 大部头经典,想深入可以啃

互动钩子:你在实际开发中遇到过哪些 HTTP 相关的坑?比如被反爬拦截、Cookie 失效、编码乱码……评论区聊聊,老粉优先回复!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。