CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
HTTP 协议与缓存/课时/请求与响应头
RawBlame
课时目录12 节

HTTP 协议与缓存

  1. 01HTTP 是什么
  2. 02方法与语义
  3. 03状态码
  4. 04请求与响应头
  5. 05HTTPS 与 TLS
  6. 06HTTP 缓存
  7. 07Cookie 与会话
  8. 08CORS
  9. 09REST 与资源设计
  10. 10HTTP/2 与 HTTP/3
  11. 11应用层缓存对照
  12. 12综合练习

请求与响应头

Content-Type、Accept、Authorization。

35 分钟

请求与响应头

这一课你将能做到

  1. 解释 Header 是「键值对元数据」,分清请求头与响应头各自管什么、往哪个方向说话
  2. 亲手设置并读懂 Host、Accept、Content-Type、Authorization、Cache-Control、ETag、Set-Cookie 的常见用法,并用 curl -H 复现一条浏览器请求
  3. 在 DevTools Headers 面板与 curl 之间互转同一条请求,并指出 Nginx 反代会追加/改写哪些头

为什么要学

同一条 GET /api/tracks:没有 Content-Type 约定,后端可能把 JSON 当表单解析直接 415;没有 Authorization,管理接口只会 401;没有 Cache-Control,用户天天看旧数据;没有 CORS 相关头,前端跨域全军覆没。缓存、CORS、Cookie、压缩、内容协商——本 track 后面一半的内容全是头在说话。

面试里「常见请求头响应头有哪些」看似送分,实际考察你有没有真的排查过问题:答得出 X-Forwarded-For 的作用、Vary 的坑,说明摸过生产。本课把头当「便签」建立直觉:头是元数据,body 是包裹内容。

先建立图像

请求信封
  请求行:GET /api/tracks HTTP/1.1
  ├─ Host / User-Agent / Accept / Authorization …   ← 请求头:客户端说的话
  └─(空行)
  └─ body(POST/PUT 才常有)

响应信封
  状态行:HTTP/1.1 200 OK
  ├─ Content-Type / Set-Cookie / ETag / Cache-Control … ← 响应头:服务器说的话
  └─(空行)
  └─ body

高频头速查表:

头方向人话示例
Host请求我要访问哪个站点(虚拟主机路由)Host: coderoadmap.cn
Accept请求我希望收到什么格式application/json
Content-Type双向本侧 body 是什么格式application/json;charset=UTF-8
Authorization请求我的凭证Bearer eyJhbGci…
Cookie请求浏览器自动带的小票satoken=abc123
Set-Cookie响应请浏览器存这张小票见 Cookie 课
Cache-Control响应为主能不能缓存、缓存多久max-age=60
ETag响应内容指纹(版本号)"a1b2c3"
If-None-Match请求我本地是这个版本,变了没
配合 ETag → 304
User-Agent请求我是什么客户端Mozilla/5.0 …
X-Forwarded-For请求(代理追加)原始客户端 IP 链1.2.3.4, 10.0.0.2

跟着做

步骤 1:只看响应头

# -I 只取头(发 HEAD 请求)
curl -sI https://coderoadmap.cn/api/tracks
# 本机对照
curl -sI http://localhost:8080/api/hello

记录三件事:content-type 是什么;有没有 cache-control / etag;有没有 set-cookie。这三个答案决定了这条响应能不能缓存、怎么被解析、是否影响会话。

步骤 2:Accept 与内容协商——「期望」不等于「结果」

# 明确说我要 JSON
curl -i -H "Accept: application/json" https://coderoadmap.cn/api/tracks

# 偏要 HTML(观察是否仍返回 JSON)
curl -i -H "Accept: text/html" https://coderoadmap.cn/api/tracks

多数纯 API 忽略 Accept 永远回 JSON——内容协商是双方约定,不是魔法。Spring MVC 的 @RestController 默认按 produces 与路径后缀协商;若启用 @EnableWebMvc + 内容协商配置,行为才严格跟随 Accept。

步骤 3:POST 的 Content-Type 是接口联调第一坑

# 正确:声明 JSON,Spring @RequestBody 才能反序列化
curl -i -X POST http://localhost:8080/api/notes \
  -H "Content-Type: application/json" \
  -d '"hello"'

# 错误示范 1:不声明(curl 默认 application/x-www-form-urlencoded)
curl -i -X POST http://localhost:8080/api/notes -d '"hello"'
# 错误示范 2:声明成表单但 body 是 JSON
curl -i -X POST http://localhost:8080/api/notes \
  -H "Content-Type: application/x-www-form-urlencoded" -d '{"a":1}'

错误示范会得到 415 Unsupported Media Type。必须记住的三类:

application/json                     —— REST API
application/x-www-form-urlencoded    —— 传统表单键值对
multipart/form-data                  —— 文件上传

步骤 4:Authorization 与 Cookie 谁在起作用

# Bearer 方式:头是显式的
curl -i http://localhost:8080/admin/tracks -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.xxx"

# Cookie 方式:头是浏览器自动带的(curl 要手动模拟)
curl -c jar.txt http://localhost:8080/api/auth/login -X POST \
  -H "Content-Type: application/json" -d '{"username":"u","password":"p"}'
curl -b jar.txt -i http://localhost:8080/admin/tracks

两种都是「把凭证放进请求头」,差别在于 Cookie 是浏览器按存储规则自动附加,Authorization 是代码显式设置(Cookie 课对比两者)。

步骤 5:DevTools 与 curl 互转

  1. Network 面板 → 选 api/tracks → Headers 分栏:上半 Request Headers,下半 Response Headers
  2. 右键该请求 → Copy → Copy as cURL,粘贴到终端执行
  3. 对比你手写的极简 curl:浏览器多出的 sec-ch-ua、accept-language 等是浏览器自动补的客户端提示

复现 bug 时不用全贴,保留关键头即可:Cookie、Authorization、Content-Type、Origin。

步骤 6:Nginx 反代改写了什么头(架构视角)

生产配置里常见这几行(对照本站 Nginx 模板):

location /api/ {
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host $host;                            # 保留原始域名
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  # 追加真实客户端 IP 链
    proxy_set_header X-Forwarded-Proto $scheme;             # 告诉应用外层是 https
}

验证差异:curl -i http://localhost:8080/api/hello 直连 Spring,没有 X-Forwarded-*;走 Nginx 反代后(本机配一条 location /api/ 再 nginx -s reload)响应前日志里能看到应用收到了这些头。Spring 侧要开启对 Forwarded 头的信任,request.getRemoteAddr() / 判断 HTTPS 才是对的——否则限流按 Nginx 的 IP 限、Secure Cookie 判断错,都是这类配置的坑。

讲清楚要点

  1. 因为 Host 决定虚拟主机,所以同一台 Nginx 上挂多个站点全靠它分流——HTTP/1.1 里 Host 是必填头;缺了它,反代不知道把请求转给谁。
  2. 因为 Content-Type 描述「发送方自己的 body」,所以请求与响应各自声明各自的——请求头 Accept 是「期望收到的格式」,两者经常同时出现在一个 POST 里且含义不同。
  3. 因为 Authorization 头在传输层是明文可见的,所以凭证必须走 HTTPS——明文 HTTP 上等于把 Token 写在明信片上(HTTPS 课展开)。
  4. 因为缓存与条件请求全靠头驱动,所以改了 JSON 却不更新 ETag/Cache-Control,客户端会继续用旧副本——「发了版用户看不到」多半是头的锅(缓存课展开)。
  5. 因为中间代理会追加/改写头,所以排错要分清「浏览器发出的」与「应用收到的」——对照 Nginx 访问日志与 Spring 日志,X-Forwarded-* 就是两边传话的信使。
  6. 因为 HTTP/2 把头名规范为小写,所以代码里不要用大小写敏感的方式取头——用框架的大小写不敏感 API(如 Spring 的 @RequestHeader),别手写字符串精确匹配。

你可能会懵的地方

  1. Accept 和 Content-Type 总搞混? 记一句话:Content-Type 说的是「我带的」,Accept 说的是「我想收的」。GET 通常只有 Accept;POST JSON 常两个都有——Content-Type 声明请求体,Accept 声明期望的响应格式。

  2. 为什么 Copy as cURL 那么长? 浏览器自动加了一堆安全与协商头。排查时保留关键头即可;有些头(如 sec-fetch-*)删了反而不容易复现,定位「服务器行为差异」时尽量原样保留。

  3. 自定义头 X-Request-Id 可以加吗? 可以,是链路追踪的好实践:Nginx 或网关生成,透传到 Spring 日志。注意两点:自定义头会触发 CORS 预检(CORS 课),且头名不要带下划线以外的特殊字符——另外 Nginx 默认丢弃带下划线的头(underscores_in_headers on 可改),这是真实生产坑。

  4. 415 与 400 怎么分? 415 是「Content-Type 我不认识/不支持」,头的问题;400 是「格式认识但内容解析/校验失败」,体的问题。联调报错先看是 415 还是 400,能直接定位改头还是改 body。

  5. 一个头可以出现多次吗? 可以。Set-Cookie 一条响应里可出现多次(发多张小票);Accept、Cache-Control 也允许重复或逗号合并。解析时用框架的 getHeaders() 而不是只取第一个。

错法对照

错法问题正解
POST JSON 不写 Content-TypeSpring 无法选择消息转换器,直接 415显式 application/json;charset=UTF-8
把密钥放进自定义头却走明文 HTTP中间任何节点都能读走 Token全程 HTTPS + 短时效 Token
出 4xx 只盯 body,不看 Allow/WWW-Authenticate/Retry-After错过服务器给出的修复线索4xx 先读相关响应头再动手
假设代理会原样转发 AuthorizationNginx/网关可能剥离或改写,造成「本地通线上不通」对照两端日志确认;内部服务间调用固定转发规则
拿到 502 里的 HTML 错误页当 JSON 解析前端一堆 Unexpected token < 报错先检查响应 Content-Type 与状态码,再决定怎么解析

卡点急救

  1. 本地 curl 通、线上 401:先对比两条请求的头差异——线上少没少 Authorization(网关剥离?)、Cookie 域名对不对(Domain 设成子域导致带不上)。用 curl -v 分别打两边,逐行 diff 请求头。
  2. Spring 拿到的客户端 IP 全是 127.0.0.1:流量经 Nginx 反代,应用看到的是 Nginx。确认 Nginx 有 proxy_set_header X-Forwarded-For,且 Spring 配置了信任 Forwarded 头(server.forward-headers-strategy=framework 或 native)。
  3. 响应头里中文/文件名乱码:Content-Disposition 里的中文文件名要按 RFC 5987 编码:attachment; filename*=UTF-8''%E8%AF%BE%E8%A1%A8.xlsx;直接塞中文不同浏览器表现不一,别赌。

面试常问

  • 常见的 HTTP 请求头和响应头有哪些?各什么作用? 请求头:Host(虚拟主机路由)、User-Agent(客户端标识)、Accept(期望格式)、Content-Type(请求体格式)、Authorization(凭证)、Cookie(会话)。响应头:Content-Type(响应体格式)、Content-Length(长度)、Cache-Control/ETag(缓存)、Set-Cookie(发会话票)、Location(重定向/创建资源位置)。能按「鉴权、缓存、格式、路由」四类归组比逐个背更显功底。

  • 说下 X-Forwarded-For 的原理和风险? 反代把原始客户端 IP 追加进去:client, proxy1, proxy2。风险:客户端可以伪造第一段,只有可信代理链追加的部分可靠;取「最左」可能拿到伪造 IP。正确做法是只信任自家网关追加的段,且配合 X-Real-IP 等由 Nginx 显式覆盖。

  • 为什么有了 Content-Type 还需要 Accept? Content-Type 描述当前报文体的格式(事实),Accept 表达客户端能处理的格式(期望)。服务器据此做内容协商:同一个 URL,浏览器要 HTML、脚本要 JSON,可以返回不同表示——这是 REST「资源多表示」的基础。

自测三问

  1. 各举两个请求头与两个响应头,说明方向与作用。
  2. 管理端 POST 为什么同时需要 Content-Type 与 Authorization?缺一个分别会发生什么?
  3. 在 DevTools 里如何确认某响应允许缓存?重点看哪个头?Nginx 反代后排查 IP 问题要先看什么头?

小结一句话

头是 HTTP 的便签:类型、鉴权、缓存、主机路由都写在上面——会读头,等于会读半条链路。

延伸

  • MDN:HTTP headers
  • MDN:Content-Type
  • 下一课:HTTPS 与 TLS——明文 HTTP 上头和 body 全是明信片;TLS 如何防窃听、防篡改、防冒充,证书为什么装在 Nginx 上。
加载本节练习…
← 上一课状态码下一课 →HTTPS 与 TLS