Appearance
第 4 讲|HTTP 和 Web
浏览器找到服务器之后,还需要一套双方都能理解的"通信格式"——这就是 HTTP(HyperText Transfer Protocol)。
理解 HTTP 在整个通信栈中的位置:
| 层 | 作用 |
|---|---|
| HTTP | 应用层协议,规定"请求什么、如何回应" |
| TLS | 在 TCP 之上加密(HTTPS 时存在) |
| TCP | 提供可靠的双向字节流 |
| IP | 在网络中路由数据包 |
HTTP 是文本协议,人类直接能读。这一特性让 HTTP 比二进制协议更易调试——用 curl -v 或浏览器开发工具看到的内容,基本就是协议传输的原始文本。
本讲围绕 HTTP 的核心概念展开:请求与响应的结构、HTTP 方法、Header、状态码、登录状态的实现机制,以及静态资源与动态接口的分工。
一、请求与响应的结构

HTTP 请求
一个最简单的 GET 请求的完整文本:
http
GET /docs/index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html结构:
| 行 | 含义 |
|---|---|
| 第 1 行 | 请求行:方法 + 路径 + 协议版本 |
| 第 2 行起 | Header:键值对,每行一个 |
| 空行 | 标记 Header 结束 |
| 空行之后 | Body(POST 等才有) |
HTTP 响应
服务端的响应有相同的结构:
http
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 1024
Cache-Control: max-age=300
<html>...</html>| 行 | 含义 |
|---|---|
| 第 1 行 | 状态行:协议版本 + 状态码 + 状态文本 |
| 第 2 行起 | Header |
| 空行 | 标记 Header 结束 |
| 空行之后 | Body(响应体) |
用 curl 看完整 HTTP 文本
bash
curl -v https://www.example.com-v 参数让 curl 输出请求和响应的完整文本(包括所有 Header)。这是排查 HTTP 问题最直接的工具——比浏览器 Network 面板更原始、信息更完整,且可在服务器内部直接使用。
更精细的用法:
bash
curl -I https://www.example.com # 只看响应头(HEAD 请求)
curl -X POST -H "Content-Type: application/json" \
-d '{"name":"test"}' https://api.example.com/users # 自定义方法和 Body
curl -H "Cookie: session=abc" https://api.example.com # 带 Cookie
curl --resolve www.example.com:443:1.2.3.4 \
https://www.example.com # 强制 DNS 解析到指定 IP二、HTTP 方法
HTTP 方法表明请求的语义意图——读、写、改、删:
| 方法 | 语义 | 典型场景 | 是否幂等 |
|---|---|---|---|
GET | 读取资源 | 打开页面、查询列表、获取详情 | 是 |
POST | 创建资源 / 提交数据 | 登录、提交表单、新建任务 | 否 |
PUT | 整体替换资源 | 更新一个对象的全部字段 | 是 |
PATCH | 局部更新资源 | 只改某一个字段 | 否 |
DELETE | 删除资源 | 删除任务、删除文件 | 是 |
HEAD | 只取响应头,不取响应体 | 检查资源是否存在 | 是 |
OPTIONS | 查询服务端支持的方法 | CORS 预检请求 | 是 |
幂等性的运维意义
幂等指同一个请求执行多次的效果与执行一次相同。这一性质对运维和应用设计都很关键:
- 幂等方法(GET / PUT / DELETE)在网络异常时可以安全重试
- 非幂等方法(POST)重试可能造成重复扣款、重复提交等副作用
实际项目中需要保证 POST 的幂等性时,通常引入幂等键(idempotency key)——客户端在请求 Header 中带一个唯一标识,服务端用它去重。
方法与路径的关系是约定而非强制
GET /api/users 通常表示"列出用户",POST /api/users 通常表示"创建用户"——这是 REST 风格的约定,不是协议强制规则。
服务端的路由系统决定每个"方法 + 路径"组合对应的代码处理逻辑。仅看一个 URL 无法确定它的行为,必须参考接口文档或代码。运维侧排查时遇到陌生接口,直接查后端代码的路由配置(如 Spring 的 @RequestMapping、Flask 的 @app.route、FastAPI 的 @app.post),比猜测可靠。
三、HTTP Header
Header 是请求和响应的元数据,信息密度高,排查时经常需要细看。
常用 Header
| Header | 出现位置 | 作用 |
|---|---|---|
Host | 请求 | 访问的域名,Nginx 按它分流到不同后端 |
Content-Type | 请求 / 响应 | Body 的格式(application/json、text/html、multipart/form-data) |
Content-Length | 请求 / 响应 | Body 的字节数 |
Authorization | 请求 | 认证信息,通常是 Bearer <token> |
Cookie | 请求 | 浏览器存的站点数据,登录态常依赖它 |
Set-Cookie | 响应 | 服务端要求浏览器存储的 Cookie |
Cache-Control | 请求 / 响应 | 缓存策略,客户端和 CDN 都参考它 |
User-Agent | 请求 | 客户端类型(浏览器、爬虫、SDK 版本等) |
Referer | 请求 | 当前请求是从哪个页面跳过来的(注意拼写,历史遗留) |
Location | 响应 | 重定向目标 URL |
X-Forwarded-For | 请求 | 经过代理后保留的原始客户端 IP |
X-Real-IP | 请求 | 同上,Nginx 常用的另一种命名 |
Host 头的关键作用
同一个 IP 后面可能挂多个站点,服务器靠 Host 头区分:

nginx
# Nginx 配置示例
server {
server_name www.example.com;
proxy_pass http://web-backend;
}
server {
server_name api.example.com;
proxy_pass http://api-backend;
}www.example.com 和 api.example.com 解析到同一个 IP,但 Nginx 根据请求的 Host 头分发到不同后端。
缺失或错误的 Host 头会导致 Nginx 走 default_server 配置,通常返回错误页或 404。
X-Forwarded-For:获取真实客户端 IP
请求经过 Nginx、负载均衡、API 网关等多层代理后,后端直接 connect 的对端 IP 不是真实用户 IP,而是上一层代理的 IP。
获取真实 IP 的标准做法:每一层代理在请求 Header 中追加 X-Forwarded-For,后端读取这个 Header。
Nginx 配置示例:
nginx
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;后端读取这个 Header(以 Python Flask 为例):
python
real_ip = request.headers.get('X-Forwarded-For', request.remote_addr)
# X-Forwarded-For 可能有多个,逗号分隔:client, proxy1, proxy2
# 第一个才是真实客户端
if real_ip and ',' in real_ip:
real_ip = real_ip.split(',')[0].strip()X-Forwarded-For 是可伪造的——客户端可以在请求中直接带这个 Header。生产环境需要把"信任的代理 IP 列表"作为校验依据,只接受来自这些代理设置的 X-Forwarded-For,而不是盲目使用 Header 中的第一个 IP。
CORS 相关 Header
跨域请求(前端域名 ≠ 后端域名)由 CORS(Cross-Origin Resource Sharing)规范控制:
| Header | 出现位置 | 作用 |
|---|---|---|
Origin | 请求 | 请求的源(协议+域名+端口) |
Access-Control-Allow-Origin | 响应 | 服务端允许哪些源访问 |
Access-Control-Allow-Methods | 响应 | 允许的 HTTP 方法 |
Access-Control-Allow-Headers | 响应 | 允许的自定义请求 Header |
跨域问题在 第 5 讲 前端和后端 展开。
四、状态码
状态码按数字范围分类,记住范围比记具体码更重要:

| 范围 | 类别 | 含义 |
|---|---|---|
| 1xx | Informational | 请求已收到,继续处理 |
| 2xx | Success | 请求成功 |
| 3xx | Redirection | 资源已移到其他位置或使用缓存 |
| 4xx | Client Error | 客户端请求有问题 |
| 5xx | Server Error | 服务端处理出错 |
常用状态码与排查方向
| 状态码 | 含义 | 排查方向 |
|---|---|---|
| 200 | 成功 | 无需排查 |
| 301 / 302 | 永久 / 临时重定向 | 重定向配置,如 HTTP → HTTPS |
| 304 | Not Modified,客户端用了本地缓存 | 不是错误 |
| 400 | Bad Request,请求格式错误 | 前端发送的字段、JSON 格式问题 |
| 401 | Unauthorized,未登录或认证失败 | Token 过期、Cookie 缺失 |
| 403 | Forbidden,已认证但无权限 | 角色不足、IP 黑名单、WAF 拦截 |
| 404 | Not Found,资源不存在 | 路径错、路由未注册、文件已删除 |
| 405 | Method Not Allowed,方法不被允许 | 用 GET 访问只支持 POST 的接口 |
| 408 | Request Timeout | 客户端发送请求过慢被服务端关闭 |
| 413 | Payload Too Large | 上传文件超过限制 |
| 429 | Too Many Requests,被限流 | 触发了应用或网关的限流规则 |
| 500 | Internal Server Error | 后端代码异常,看应用日志 |
| 502 | Bad Gateway | 入口层连不上后端,看 Nginx error log |
| 503 | Service Unavailable | 服务不可用、过载、维护模式 |
| 504 | Gateway Timeout | 入口层等后端超时 |
同一状态码的多种根因
同一个状态码可能对应不同的根因,仅看状态码无法确定问题,需要结合日志和上下文:
404 的可能原因:
- URL 路径拼写错误
- Nginx
location配置未匹配 - 后端路由未注册(如 Flask 缺少对应的
@app.route) - 静态资源文件已被删除
- K8s Ingress 规则未匹配
502 的可能原因(看 Nginx error log 通常能直接定位):
connect() failed (111: Connection refused) — 后端端口无服务监听
no live upstreams while connecting to upstream — 所有后端实例健康检查失败
upstream prematurely closed connection — 后端进程崩了
upstream timed out — 应该返回 504 但有些版本返回 502500 与 502 的本质区别:
- 500:后端收到了请求,处理时出错(代码 bug、SQL 错、外部调用失败)。排查方向:应用日志
- 502:入口层根本没连上后端。排查方向:入口层日志 + 后端进程状态
混淆这两个状态码会让排查方向完全错位。
五、登录状态:Cookie / Session / Token

HTTP 是无状态协议
HTTP 协议本身不维护客户端状态——服务端处理完一个请求后,与下一个请求没有任何关联。但实际应用中,用户登录后希望刷新页面、关闭浏览器再打开都保持登录状态。这种持续的"状态"是通过 Cookie、Session、Token 等机制叠加在 HTTP 之上实现的。
方案 1:Cookie + Session(传统)
特点:
- 用户标识(session_id)存在浏览器 Cookie 中
- 实际的会话数据存在服务端(内存、Redis、数据库)
- 浏览器每次请求自动带 Cookie
痛点:
- 服务端要存会话数据,多实例必须共享 Session 存储(否则用户请求落到不同实例时会掉登录)
- 跨域、跨服务难以共享会话
方案 2:Token / JWT(前后端分离主流)
特点:
- Token(通常是 JWT)由服务端签发,前端自己存(localStorage、内存、SessionStorage)
- 每次请求在
Authorization: Bearer <token>Header 中携带 - 服务端通过签名校验 token,不需要服务端存会话
- 天然支持多实例、跨域、移动端
缺点:
- Token 一旦签发,在过期前无法主动撤销(除非维护黑名单)
- Token 内容可被解码看到(只是签名防篡改,不是加密)——敏感信息不能放 Token 里
JWT 的结构:base64(header).base64(payload).base64(signature),可以在 jwt.io 在线解码查看。
方案 3:Session + Token 混合
实际生产中很多系统采用混合方案:
- 短生命周期 Access Token(15 分钟过期)
- 长生命周期 Refresh Token(7 天,服务端有记录可撤销)
- Access Token 过期时,前端用 Refresh Token 换新 Access Token
兼顾性能(每次请求不查服务端)与安全性(可主动撤销)。
六、静态资源与动态接口
一个网站对外提供的内容大致分两类:
| 类型 | 内容 | 部署位置 |
|---|---|---|
| 静态资源 | HTML、CSS、JS、图片、字体、视频 | Nginx、对象存储、CDN |
| 动态接口 | 用户数据、订单、任务、统计数字 | 后端应用 + 数据库 + 缓存 |
传统 vs 前后端分离
- 传统模式(PHP、JSP 时代):服务端直接渲染完整 HTML 返回。前后端代码混在一起,通常由后端框架(如 Django、Rails)处理
- 前后端分离(现代主流):浏览器先拉取静态资源(HTML/CSS/JS),JS 执行后调用后端 API 获取 JSON 数据,前端动态渲染
前后端分离的优势:
- 前端可以独立部署、独立构建,迭代速度快
- 静态资源放 CDN,加载快、源站压力小
- 后端只关注数据接口,前端可以是 Web、移动端、桌面应用共用一套 API
白屏问题的排查流程
页面白屏时按以下顺序排查:
bash
# 1. 打开浏览器 Network 面板,看 HTML 是否 200
# - 不是 200 → 入口层(Nginx/CDN)问题
# - 是 200 → 继续
# 2. 看 JS / CSS 文件是否 200
# - 404 → 构建路径错误、发布路径错误、版本号不一致
# - 是 200 → 继续
# 3. 看 API 请求的状态码
# - 401 → 登录态丢失,看 Token 是否有效
# - 403 → 权限问题
# - 500 → 后端代码异常,看后端日志
# - CORS 报错 → 跨域配置问题(Access-Control-Allow-Origin)
# - 200 但页面还白屏 → 前端 JS 报错,看 Console 面板按这个流程通常能在几分钟内定位到具体哪一层出问题——比起没头绪地猜效率高得多。