为什么 Header 是 HTTP 的灵魂
前言
上一篇文章里,我们从整体上认识了 HTTP:
text
HTTP 是浏览器和服务器之间传输资源的应用层协议。
一个 HTTP 请求和响应,大致可以拆成:
text
请求:请求行 + 请求头 + 请求体
响应:状态行 + 响应头 + 响应体
如果只看请求行、状态行和响应体,我们大概能知道:
text
请求了哪个接口;
请求是否成功;
服务端返回了什么数据。
但真正让 HTTP 变得强大的,往往不是这些最基础的部分,而是 Header。
比如你在项目里遇到的很多问题,最后都会回到请求头或响应头:
- 为什么用户登录后,刷新页面还是登录状态?
- 为什么请求里会带
Cookie? - 为什么请求头里要放
Authorization? - 为什么接口明明返回了 200,浏览器却提示 CORS 错误?
- 为什么有些资源返回 304?
- 为什么 JS、CSS 能被长期缓存?
- 为什么接口返回 JSON,浏览器知道它是 JSON?
- 为什么响应内容可以被 gzip 或 Brotli 压缩?
这些问题背后,几乎都离不开 Header。
所以这一篇,我们不急着深入 Token、Cookie、缓存、跨域,而是先把 Header 这个入口讲清楚。
你可以先记住一句话:
text
Header 是 HTTP 的扩展层。
HTTP 的很多高级能力,都是通过 Header 实现的。
一、Header 是什么
Header,中文通常叫"头部"。
它是一组键值对,用来描述 HTTP 请求或响应的附加信息。
一个请求头可能长这样:
http
GET /api/user/current HTTP/1.1
Host: example.com
Accept: application/json
Authorization: Bearer xxx
Cookie: sessionId=abc123
其中:
http
Host: example.com
Accept: application/json
Authorization: Bearer xxx
Cookie: sessionId=abc123
这些就是请求头。
一个响应头可能长这样:
http
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-cache
Set-Cookie: sessionId=abc123; HttpOnly; Secure
Access-Control-Allow-Origin: https://front.example.com
其中:
http
Content-Type: application/json
Cache-Control: no-cache
Set-Cookie: sessionId=abc123; HttpOnly; Secure
Access-Control-Allow-Origin: https://front.example.com
这些就是响应头。
Header 的格式一般是:
http
字段名: 字段值
比如:
http
Content-Type: application/json
表示:
text
当前请求体或响应体的数据格式是 JSON。
二、为什么说 Header 是 HTTP 的灵魂
因为 HTTP 的基础结构很简单。
请求行只能表达:
text
用什么方法,访问哪个路径,使用哪个 HTTP 版本。
例如:
http
GET /api/user HTTP/1.1
状态行只能表达:
text
HTTP 层面请求结果是什么。
例如:
http
HTTP/1.1 200 OK
请求体和响应体主要承载具体数据。
例如:
json
{
"code": 0,
"data": {
"name": "Alice"
}
}
但问题是,真实 Web 通信远远不止这些。
浏览器和服务器还需要沟通很多额外信息:
text
这次请求来自哪个站点?
用户有没有登录?
请求体是什么格式?
响应体是什么格式?
资源能不能缓存?
缓存多久?
资源有没有变化?
内容有没有被压缩?
服务器要不要给浏览器设置 Cookie?
跨域请求能不能被前端读取?
页面允许加载哪些脚本?
这些信息放在哪里?
答案就是 Header。
所以 Header 像是 HTTP 的"能力扩展区"。
如果没有 Header,HTTP 只能完成最朴素的请求和响应。
有了 Header,HTTP 才能支持:
- 内容协商
- 数据格式说明
- 压缩传输
- 缓存控制
- 登录态维护
- Token 鉴权
- Cookie 设置
- CORS 跨域
- 安全策略
- 文件上传下载
- 连接管理
这就是为什么可以说:
text
Header 是 HTTP 的灵魂。
三、请求头和响应头有什么区别
Header 可以分为两类:
text
请求头 Request Headers
响应头 Response Headers
1. 请求头
请求头是客户端发给服务器的附加信息。
它在告诉服务器:
text
我是谁;
我想要什么;
我能接收什么;
我发给你的数据是什么格式;
我有没有携带身份凭证;
这个请求来自哪里。
比如:
http
GET /api/user/current HTTP/1.1
Host: example.com
Accept: application/json
Authorization: Bearer xxx
Cookie: sessionId=abc123
Origin: https://front.example.com
这里每个请求头都有自己的含义:
| 请求头 | 作用 |
|---|---|
Host |
告诉服务器要访问哪个主机 |
Accept |
告诉服务器客户端希望接收什么格式 |
Authorization |
携带认证信息,常见是 token |
Cookie |
携带浏览器保存的 Cookie |
Origin |
表示跨域请求来自哪个源 |
2. 响应头
响应头是服务器返回给客户端的附加信息。
它在告诉浏览器:
text
我返回的数据是什么格式;
这个资源能不能缓存;
我要不要给你设置 Cookie;
这个跨域请求允不允许;
响应内容有没有压缩;
页面有哪些安全限制。
比如:
http
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=3600
Set-Cookie: sessionId=abc123; HttpOnly
Access-Control-Allow-Origin: https://front.example.com
Content-Encoding: br
这些响应头分别表示:
| 响应头 | 作用 |
|---|---|
Content-Type |
告诉浏览器响应体是什么格式 |
Cache-Control |
告诉浏览器怎么缓存资源 |
Set-Cookie |
让浏览器保存 Cookie |
Access-Control-Allow-Origin |
告诉浏览器是否允许跨域读取 |
Content-Encoding |
告诉浏览器响应体使用了什么压缩方式 |
简单记:
text
请求头:客户端告诉服务端一些信息。
响应头:服务端告诉客户端一些信息。
四、Header 解决的第一个问题:内容是什么
HTTP 传输的内容可能有很多类型。
比如:
- HTML
- CSS
- JavaScript
- JSON
- 图片
- 字体
- 文件流
浏览器需要知道:
text
服务端返回的这一坨内容,到底应该怎么解析?
这就需要 Content-Type。
1. Content-Type
Content-Type 用来表示请求体或响应体的媒体类型。
响应里常见:
http
Content-Type: text/html; charset=utf-8
表示响应体是 HTML。
http
Content-Type: application/json
表示响应体是 JSON。
http
Content-Type: text/css
表示响应体是 CSS。
http
Content-Type: image/png
表示响应体是 PNG 图片。
前端接口里最常见的是:
http
Content-Type: application/json
当后端返回:
http
Content-Type: application/json
{
"code": 0,
"data": {
"id": 1
}
}
浏览器或请求库就知道:
text
这个响应体应该按 JSON 处理。
2. 请求里的 Content-Type
Content-Type 不只可以出现在响应头,也可以出现在请求头。
比如前端提交 JSON:
http
POST /api/login HTTP/1.1
Content-Type: application/json
{
"username": "alice",
"password": "123456"
}
它在告诉后端:
text
我发给你的请求体是 JSON。
如果提交表单:
http
Content-Type: application/x-www-form-urlencoded
username=alice&password=123456
如果上传文件:
http
Content-Type: multipart/form-data
注意,使用 FormData 上传文件时,一般不要手动设置 Content-Type:
js
const formData = new FormData()
formData.append('file', file)
fetch('/api/upload', {
method: 'POST',
body: formData
})
因为浏览器会自动生成 multipart/form-data 需要的 boundary。
3. Accept
Accept 是请求头,表示客户端希望接收什么类型的数据。
例如:
http
Accept: application/json
意思是:
text
我希望你最好返回 JSON。
Accept 和 Content-Type 的区别可以这样记:
text
Accept:我想要什么。
Content-Type:我给你的是什么。
例如:
http
Accept: application/json
Content-Type: application/json
可以理解为:
text
我希望收到 JSON;
我现在发给你的请求体也是 JSON。
五、Header 解决的第二个问题:能不能压缩
前端性能优化里,经常会提到:
text
开启 gzip 或 Brotli 压缩。
这个能力也依赖 Header。
浏览器请求时会告诉服务器:
http
Accept-Encoding: gzip, deflate, br
意思是:
text
我支持 gzip、deflate、br 这些压缩格式。
服务器如果决定使用 Brotli 压缩响应,会返回:
http
Content-Encoding: br
意思是:
text
响应体已经用 br 压缩过了。
浏览器收到后,会先解压,再交给页面使用。
压缩适合:
- HTML
- CSS
- JavaScript
- JSON
- SVG
不太适合:
- JPEG
- PNG
- MP4
- ZIP
因为这些格式本身通常已经压缩过,再压缩收益不大。
这一部分你可以先建立一个印象:
text
HTTP 压缩不是浏览器和服务器心照不宣完成的。
而是客户端通过 Accept-Encoding 表示自己支持什么,服务端通过 Content-Encoding 表示实际用了什么。
六、Header 解决的第三个问题:用户是谁
HTTP 本身是无状态的。
也就是说,HTTP 协议本身不会记住:
text
上一次请求是谁发的;
这个用户是否登录过;
这次请求和上一次请求有没有关系。
所以登录态需要额外机制。
而这些机制,也离不开 Header。
1. Cookie
Cookie 是浏览器保存的一小段数据。
服务端可以通过响应头设置 Cookie:
http
Set-Cookie: sessionId=abc123; Path=/; HttpOnly; Secure
浏览器保存后,后续请求符合条件时,会自动带上:
http
Cookie: sessionId=abc123
流程是:
text
用户登录
-> 服务端校验成功
-> 服务端通过 Set-Cookie 返回 sessionId
-> 浏览器保存 Cookie
-> 后续请求浏览器自动带上 Cookie
-> 服务端根据 Cookie 识别用户
这里有两个 Header:
text
Set-Cookie:响应头,服务端让浏览器保存 Cookie。
Cookie:请求头,浏览器把 Cookie 带回服务端。
所以 Cookie 不是前端代码每次手写到请求头里的。
很多时候它是浏览器自动管理的。
2. Authorization
另一种常见鉴权方式是 token。
登录成功后,后端可能把 token 放在响应体里返回:
json
{
"code": 0,
"data": {
"token": "xxx.yyy.zzz"
},
"message": "success"
}
前端拿到 token 后保存起来。
后续请求时,前端主动加请求头:
http
Authorization: Bearer xxx.yyy.zzz
例如:
js
fetch('/api/user/current', {
headers: {
Authorization: `Bearer ${token}`
}
})
所以:
text
Cookie 通常是浏览器自动带;
Authorization 通常是前端代码主动加。
它们都可以用于身份识别,只是机制不同。
这一块后续可以单独写一篇:
text
Cookie、Session、Token:HTTP 无状态下如何保持登录态。
七、Header 解决的第四个问题:资源能不能缓存
浏览器缓存也是通过 Header 控制的。
比如服务端返回一个 JS 文件:
http
HTTP/1.1 200 OK
Content-Type: application/javascript
Cache-Control: max-age=31536000
console.log('hello')
这里的:
http
Cache-Control: max-age=31536000
表示:
text
这个资源在一段时间内可以直接使用缓存。
这就是强缓存相关的 Header。
如果是协商缓存,可能会看到:
http
ETag: "abc123"
Last-Modified: Thu, 10 Sep 2026 08:00:00 GMT
浏览器下次请求时可能带上:
http
If-None-Match: "abc123"
If-Modified-Since: Thu, 10 Sep 2026 08:00:00 GMT
服务端判断资源没变,就返回:
http
HTTP/1.1 304 Not Modified
这里又出现了一组 Header:
| Header | 类型 | 作用 |
|---|---|---|
Cache-Control |
响应头 | 控制缓存策略 |
ETag |
响应头 | 资源标识 |
Last-Modified |
响应头 | 资源最后修改时间 |
If-None-Match |
请求头 | 携带上次收到的 ETag |
If-Modified-Since |
请求头 | 携带上次收到的修改时间 |
所以缓存问题本质上也是:
text
浏览器和服务器通过 Header 协商资源是否还能继续使用。
后续缓存文章里,我们会详细讲:
- 强缓存
- 协商缓存
- 304
- ETag
- Last-Modified
- 为什么静态资源要带 hash
- 为什么 HTML 不适合长缓存
八、Header 解决的第五个问题:跨域能不能读取
跨域问题是前端最常见的网络问题之一。
很多同学第一次看到 CORS 报错时,会以为:
text
请求没有发出去。
但实际要分情况。
对于简单请求,浏览器通常会先把真实请求发出去,并自动带上:
http
Origin: https://front.example.com
这个请求头的意思是:
text
这次请求来自 https://front.example.com。
服务端如果允许这个来源访问,就返回:
http
Access-Control-Allow-Origin: https://front.example.com
浏览器收到响应后,会检查这个响应头。
如果允许,前端 JS 可以读取响应。
如果不允许,浏览器会拦截前端读取响应。
也就是说:
text
Origin 是请求头;
Access-Control-Allow-Origin 是响应头;
浏览器根据这些 Header 判断跨域响应能不能交给前端 JS。
如果是非简单请求,比如:
js
fetch('https://api.example.com/user', {
method: 'DELETE',
headers: {
Authorization: 'Bearer xxx',
'Content-Type': 'application/json'
}
})
浏览器会先发 OPTIONS 预检请求:
http
OPTIONS /user HTTP/1.1
Origin: https://front.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: authorization, content-type
服务端需要返回:
http
Access-Control-Allow-Origin: https://front.example.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
预检通过后,浏览器才会发送真正请求。
所以跨域也不是一个神秘的浏览器问题。
它本质上也是:
text
浏览器、前端页面、服务器之间通过 Header 建立访问许可。
这一块后续可以单独写一篇:
text
CORS 跨域:为什么请求发出去了,但前端拿不到响应。
九、Header 解决的第六个问题:连接怎么管理
在 HTTP/1.1 里,长连接也是通过 Header 表达的。
比如:
http
Connection: keep-alive
表示:
text
这个 TCP 连接可以暂时不关闭,后续请求继续复用。
HTTP/1.0 时代,默认更偏短连接。
一次请求可能经历:
text
建立 TCP 连接
-> 发送请求
-> 接收响应
-> 关闭连接
HTTP/1.1 默认支持长连接,多个请求可以复用同一个 TCP 连接,从而减少连接建立和关闭的成本。
不过到了 HTTP/2,连接管理方式又发生变化。
HTTP/2 通过多路复用,让多个请求和响应在一个 TCP 连接中以 stream 的形式并发传输。
这一部分不需要在 Header 文章里展开太深,只要先知道:
text
HTTP 的连接复用、版本演进,也和请求头、响应头的能力扩展有关。
十、常见 Header 速览
为了建立整体感觉,我们可以先看一张表。
常见请求头
| 请求头 | 作用 |
|---|---|
Host |
请求目标主机 |
Accept |
客户端希望接收的数据类型 |
Accept-Encoding |
客户端支持的压缩格式 |
Accept-Language |
客户端偏好的语言 |
Content-Type |
请求体的数据类型 |
Authorization |
携带 token 等认证信息 |
Cookie |
携带浏览器保存的 Cookie |
Origin |
表示跨域请求来源 |
Referer |
表示请求来源页面 |
If-None-Match |
协商缓存,携带 ETag |
If-Modified-Since |
协商缓存,携带最后修改时间 |
常见响应头
| 响应头 | 作用 |
|---|---|
Content-Type |
响应体的数据类型 |
Content-Length |
响应体长度 |
Content-Encoding |
响应体压缩方式 |
Cache-Control |
缓存控制 |
Expires |
资源过期时间 |
ETag |
资源标识,用于协商缓存 |
Last-Modified |
资源最后修改时间 |
Set-Cookie |
服务端设置 Cookie |
Location |
重定向地址 |
Access-Control-Allow-Origin |
CORS 允许的来源 |
Access-Control-Allow-Headers |
CORS 允许的请求头 |
Access-Control-Allow-Methods |
CORS 允许的请求方法 |
Content-Security-Policy |
内容安全策略 |
Strict-Transport-Security |
强制 HTTPS 访问策略 |
这张表不需要一口气全部背下来。
更好的方式是按场景记:
text
内容格式:Content-Type、Accept
压缩传输:Accept-Encoding、Content-Encoding
登录鉴权:Cookie、Set-Cookie、Authorization
缓存:Cache-Control、ETag、Last-Modified、If-None-Match、If-Modified-Since
跨域:Origin、Access-Control-Allow-Origin、Access-Control-Allow-Headers
安全:Content-Security-Policy、Strict-Transport-Security
这样就不会觉得 Header 是一堆零散字段。
十一、一个完整例子串起来
假设用户已经登录,前端请求当前用户信息。
前端页面地址是:
text
https://front.example.com
接口地址是:
text
https://api.example.com/user/current
请求可能是:
http
GET /user/current HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer xxx.yyy.zzz
Origin: https://front.example.com
If-None-Match: "user-current-v1"
这个请求里包含了很多信息:
text
Accept 表示希望接收 JSON;
Authorization 表示携带 token;
Origin 表示请求来源,用于 CORS;
If-None-Match 表示询问缓存资源是否变化。
服务端可能返回:
http
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-cache
ETag: "user-current-v2"
Access-Control-Allow-Origin: https://front.example.com
{
"code": 0,
"data": {
"id": 1,
"name": "Alice"
},
"message": "success"
}
响应头也在表达很多信息:
text
Content-Type 表示响应体是 JSON;
Cache-Control 表示使用缓存前要先验证;
ETag 表示当前资源版本;
Access-Control-Allow-Origin 表示允许这个前端来源读取响应。
你会发现,一个简单的接口请求,背后已经涉及:
- 数据格式
- token 鉴权
- 跨域
- 缓存
- 响应体结构
而这些能力的入口,几乎都在 Header 里。
十二、前端调试时怎么看 Header
平时排查网络问题,最常用的是 Chrome DevTools。
打开方式:
text
F12
-> Network
-> 点击某个请求
-> Headers
你一般会看到几块:
text
General
Response Headers
Request Headers
Payload
Preview / Response
常见排查思路:
1. 接口返回数据格式不对
看:
text
Response Headers 里的 Content-Type
比如服务端是否返回了:
http
Content-Type: application/json
2. 登录态没带上
看:
text
Request Headers 里有没有 Cookie;
Request Headers 里有没有 Authorization;
Application 面板里有没有对应 Cookie 或 localStorage token。
3. 跨域失败
看:
text
Request Headers 里的 Origin;
Response Headers 里的 Access-Control-Allow-Origin;
预检请求 OPTIONS 是否成功;
Access-Control-Allow-Headers 是否包含 Authorization、Content-Type 等字段。
4. 缓存没生效
看:
text
Response Headers 里的 Cache-Control、ETag、Last-Modified;
Request Headers 里的 If-None-Match、If-Modified-Since;
Status Code 是否是 200、304;
Size 是否显示 from memory cache 或 from disk cache。
所以学 Header 不是为了背名词。
它会直接提升你调试接口、定位问题、看懂 Network 面板的能力。
十三、不要把 Header 当成纯前端或纯后端问题
Header 是前后端共同约定的一部分。
前端会设置一些请求头:
js
fetch('/api/user', {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
}
})
后端会读取这些请求头:
text
读取 Authorization,校验 token;
读取 Cookie,找到 session;
读取 Origin,判断是否允许跨域;
读取 If-None-Match,判断缓存是否命中。
后端也会设置响应头:
text
Set-Cookie
Cache-Control
ETag
Access-Control-Allow-Origin
Content-Type
浏览器再根据这些响应头决定:
text
怎么解析响应;
要不要保存 Cookie;
能不能把跨域响应交给 JS;
资源能不能使用缓存;
要不要解压响应体;
要不要执行安全策略。
所以 Header 不是某一端的事情。
它是:
text
浏览器、前端代码、服务端之间的通信约定。
十四、本篇小结
这一篇我们主要讲了为什么 Header 是 HTTP 的灵魂。
可以总结成几句话:
text
Header 是 HTTP 的扩展层。
请求头是客户端告诉服务端的附加信息。
响应头是服务端告诉客户端的附加信息。
HTTP 的很多核心能力都通过 Header 实现。
Header 解决的问题包括:
- 内容是什么:
Content-Type、Accept - 能不能压缩:
Accept-Encoding、Content-Encoding - 用户是谁:
Cookie、Set-Cookie、Authorization - 能不能缓存:
Cache-Control、ETag、Last-Modified - 跨域能不能读取:
Origin、Access-Control-Allow-Origin - 页面是否安全:
Content-Security-Policy、Strict-Transport-Security
如果只看请求行和响应体,HTTP 像是一个简单的问答协议。
但加上 Header 后,HTTP 才能支撑现代 Web 里的缓存、鉴权、跨域、压缩、安全和性能优化。
所以可以这样理解:
text
请求行和状态行决定一次 HTTP 通信的基本动作;
请求体和响应体承载具体数据;
Header 决定这次通信的规则、上下文和扩展能力。