你是否遇到过页面加载慢到让用户流失?打开控制台,大体积资源直接传输 ------ 这正是 HTTP 压缩没做好的坑!Accept-Encoding
和Content-Encoding
是性能优化关键:客户端声明解压能力,服务器按需压缩,实测文本资源体积可减 60%-80%。本文手把手教你搞定旧浏览器兼容、CPU 负载等难题,让网站在各类网络环境轻快运行,告别 "传输慢" 痛点。
HTTP 压缩的基本原理
HTTP 压缩本质上是通过牺牲服务器压缩时间和客户端解压时间,换取网络传输的数据量减少的技术。这种权衡在当今网络环境下非常划算,因为 CPU 计算能力远比网络带宽充足,尤其是在移动端用户越来越多的情况下。这个压缩协商过程正是由 Accept-Encoding 和 Content-Encoding 这两个 HTTP 头完成的。
要注意的是,这里讨论的是响应体(Body)的压缩(如 gzip/Brotli),而 HTTP/2 的 HPACK 是针对请求头(Headers)的压缩,两者互不冲突且可同时启用,共同减少传输体积。
MIME 类型 :用于标识文件类型的标准格式,如text/html
表示 HTML 文档,image/jpeg
表示 JPEG 图片,服务器根据不同 MIME 类型决定是否应用压缩。

Accept-Encoding 详解
Accept-Encoding 是由客户端发送的 HTTP 请求头,用来告诉服务器:"嘿,我能理解这些压缩算法,你可以用它们来压缩响应内容"。
常见的压缩算法及对比
算法 | 压缩率 | 压缩速度 | 解压速度 | 浏览器支持 | 特点 |
---|---|---|---|---|---|
gzip | 中等 | 快 | 快 | 全部支持 | 通用性最好的选择 |
br (Brotli) | 高(比 gzip 高 20-26%) | 慢(比 gzip 慢 15%) | 中等 | Chrome 51+, Firefox 44+, Edge 15+, Safari 11+, iOS 11+, Opera 38+(IE 不支持) | 适合静态资源 |
deflate | 中等 | 快 | 快 | 广泛 | 老旧但稳定 |
zstd | 高 | 很快 | 很快 | Chrome 91+, Firefox 104+, Edge 91+, Safari 16.4+, Opera 77+ | 新兴算法 |
identity | 无压缩 | - | - | 全部 | 表示不压缩 |
这就像你整理行李箱一样。gzip 就像常规的压缩整理,可以把衣服叠好放进去;Brotli 则像是真空压缩袋,可以压得更小但需要更多时间操作;而 identity 相当于完全不整理,直接塞进行李箱。
带权重的 Accept-Encoding
浏览器可以通过 q 值(质量因子)表示对不同压缩算法的偏好程度:
css
Accept-Encoding: br;q=1.0, gzip;q=0.8, *;q=0.1
这表示浏览器优先选择 br 算法(q=1.0),其次是 gzip(q=0.8),最后才是其他算法(q=0.1)。
q 值遵循 RFC 7231 规范,取值范围为 0-1(1.0 表示最高优先级),未指定 q 值的算法默认为 q=1.0,*
表示匹配所有未明确列出的算法,默认 q=0.1。
服务器会优先选择客户端 q 值最高且自身支持的算法。若多种算法 q 值相同,则按照客户端列出的顺序选择(如br;q=1.0, gzip;q=1.0
时会选择 br)。若客户端完全没有携带 Accept-Encoding 头,则服务器默认不进行压缩(等价于客户端只支持 identity)。

Content-Encoding 详解
Content-Encoding 是由服务器发送的 HTTP 响应头,用来告诉客户端:"我已经使用了这种算法压缩了响应内容,你需要用对应的方法解压"。
当服务器收到带有 Accept-Encoding 头的请求后,会从中选择一种自己支持的压缩算法,然后:
- 使用选定的算法压缩响应内容
- 在响应头中添加
Content-Encoding: 算法名称
- 发送压缩后的内容给客户端
例如:
css
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Encoding: gzip
...
[压缩后的内容]
这个过程就像邮寄包裹。你告诉快递公司(服务器):"我可以接收用真空袋压缩的、普通纸箱装的,或者任何包装方式的包裹",快递公司则根据自己的能力和你的偏好选择一种包装方式,并在包裹上贴上标签(Content-Encoding)说明用了哪种包装,这样你收到后就知道该怎么拆了。
压缩算法的选择逻辑

实战案例:Nginx 配置压缩
Nginx 是目前最流行的 Web 服务器之一,配置 HTTP 压缩非常简单:
nginx
http {
# 定义公共的压缩文件类型(避免重复定义)
set $compress_types "text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript";
# 开启gzip压缩
gzip on;
gzip_comp_level 6;
gzip_min_length 1k;
gzip_types $compress_types;
gzip_static on; # 优先使用.gz文件而非实时压缩
gzip_vary on;
# 启用Brotli压缩(需要安装ngx_brotli模块)
brotli on;
brotli_comp_level 6;
brotli_types $compress_types;
brotli_static on; # 优先使用.br文件而非实时压缩
brotli_vary on;
# 对压缩后的静态资源设置1年缓存
location /static/ {
root /var/www/html;
gzip_static on;
brotli_static on;
add_header Cache-Control "public, max-age=31536000, immutable";
}
}
实战案例:Node.js 服务器启用压缩
在 Node.js 中,使用 Express 框架可以轻松实现 HTTP 压缩:
javascript
const express = require('express');
const compression = require('compression'); // 基于zlib库实现,Node.js v11.0.0+原生支持Brotli
const app = express();
// 使用压缩中间件
app.use(compression({
// 仅压缩大于1KB的响应
threshold: 1024,
// 筛选要压缩的类型
filter: (req, res) => {
if (req.headers['x-no-compression']) {
// 不要压缩带有这个头的请求
return false;
}
// 默认行为是压缩所有能压缩的响应
return compression.filter(req, res);
},
// 压缩级别
level: 6,
// 处理错误
onerror: (err, req, res) => {
console.error('压缩发生错误:', err);
// 即使压缩失败,也继续发送未压缩的响应
res.end();
}
}));
// 后续路由定义...
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('服务已启动,端口3000');
});
对于 Node.js 低版本,可以这样处理 Brotli 支持:
javascript
const https = require('https');
const zlib = require('zlib');
https.get('https://example.com', (res) => {
const encoding = res.headers['content-encoding'];
let body = '';
let stream = res;
if (encoding === 'gzip') {
stream = res.pipe(zlib.createGunzip());
} else if (encoding === 'deflate') {
stream = res.pipe(zlib.createInflate());
} else if (encoding === 'br') {
// 注意:需要Node.js v11.0.0+才原生支持brotli
if (parseInt(process.versions.node.split('.')[0]) >= 11) {
stream = res.pipe(zlib.createBrotliDecompress());
} else {
// Node.js < v11.0.0 需使用第三方模块
console.error('当前Node.js版本不支持Brotli,请升级或安装brotli模块');
res.destroy();
return;
}
} else if (encoding) {
console.error(`不支持的内容编码: ${encoding}`);
res.destroy();
return;
}
stream.on('data', (chunk) => { body += chunk; });
stream.on('end', () => { console.log(body); });
stream.on('error', (err) => { console.error('解压失败:', err); });
});
实战案例:检测压缩是否生效
你可以使用 curl 命令来检查服务器是否正确处理了压缩:
bash
# 请求时指定Accept-Encoding
curl -H "Accept-Encoding: gzip, deflate, br" -I https://example.com
# 结果中应该能看到Content-Encoding头
# HTTP/2 200
# content-type: text/html; charset=UTF-8
# content-encoding: br
# ...
也可以使用更直观的方式查看压缩效果:
bash
# 查看未压缩大小(强制禁用压缩)
curl -H "Accept-Encoding: identity" https://example.com | wc -c # wc -c 统计字节数
# 查看压缩后大小(实际传输大小,%{size_download}为curl内置变量)
curl -H "Accept-Encoding: gzip" --write-out "压缩后大小: %{size_download} bytes\n" --output /dev/null https://example.com
这就像你想知道快递包装是否真的省空间一样,一边用原始体积测量,一边用压缩后的体积测量,这样你就能确切地知道压缩的效果了。
处理多重压缩和特殊情况
有时候我们会遇到内容需要经过多道压缩的情况,可以通过多个 Content-Encoding 值来表示:
css
Content-Encoding: gzip, br
这表示内容首先经过 br 压缩,然后又经过 gzip 压缩。客户端需要按照相反的顺序解压:先 gzip 解压,再 br 解压。
不过在实际生产环境中,多重压缩很少使用,原因有:
- 压缩算法可能相互干扰,导致压缩率下降(实测 gzip+br 相比单用 br 可能导致体积增加 5%-15%)
- 增加了服务器和客户端的计算负担(双重压缩可能使 CPU 使用率提高 40%以上)
- 可能引起兼容性问题(部分旧版浏览器无法正确处理多重 Content-Encoding)
- RFC 7231 第 6.3.3 节明确建议使用单一压缩算法,以简化客户端处理逻辑

特殊情况:分块传输与压缩的结合
分块传输编码 (Chunked Transfer Encoding):允许服务器逐块发送数据的 HTTP 传输机制,使用Transfer-Encoding: chunked
响应头。
当响应使用分块传输编码时,压缩与分块的处理顺序也很重要:
makefile
HTTP/1.1 200 OK
Content-Type: text/html
Content-Encoding: gzip
Transfer-Encoding: chunked
[压缩并分块的内容]
这种情况下,服务器会先将整个内容压缩,然后再分块传输。客户端则需要:
- 接收并重组所有分块
- 对重组后的内容进行解压
注意:如果同时存在 Content-Encoding 和 Transfer-Encoding,处理顺序是固定的,不受头部出现顺序影响。
压缩与缓存的交互
使用压缩时,必须设置Vary: Accept-Encoding
响应头,告诉 CDN 和代理服务器:不同的 Accept-Encoding 请求应该缓存不同版本的响应。
makefile
HTTP/1.1 200 OK
Content-Type: text/html
Content-Encoding: gzip
Vary: Accept-Encoding
Cache-Control: max-age=3600
[压缩内容]
如果不设置这个头,可能导致缓存服务器将 gzip 版本内容返回给不支持 gzip 的客户端,造成解析错误。
HTTPS 与压缩的关系
在 HTTPS(TLS/SSL)环境中使用压缩时,需要注意一个重要细节:TLS 记录层限制。
TLS 协议将数据分割成称为"记录"的块,每个记录的默认最大大小为 16KB。当压缩后的 HTTP 响应体超过这个限制时,会被分割成多个 TLS 记录,可能导致额外的 TCP 分片和网络延迟。每个分片都需要单独经过 TCP 确认,增加往返时间(RTT),在高延迟网络中尤其明显。
nginx
# Nginx中调整SSL缓冲区大小
ssl_buffer_size 8k; # 默认16k,减小可避免过度分片
对于较大的静态资源,建议:
- 将大文件拆分为多个较小文件(如 JS 拆分为多个较小模块,每个控制在 15KB 以内)
- 适当降低压缩等级,避免部分算法在高压缩率下产生较大的数据块
- 对于超过 1MB 的大型资源,考虑使用 HTTP/2 的多路复用能力
HTTP/3 与压缩
HTTP/3 基于 QUIC 协议,采用了不同的压缩策略。与 HTTP/2 的 HPACK 不同,HTTP/3 使用 QPACK 头部压缩算法,在保持高压缩率的同时解决了 HTTP/2 中的队头阻塞问题。
对于响应体压缩,HTTP/3 仍然使用 Content-Encoding 机制,但由于 QUIC 的流控制更加智能,大型压缩响应的传输效率通常更高。在实现 HTTP/3 的服务器中,通常建议:
- 优先考虑 Brotli 压缩,其次是 zstd,最后是 gzip
- 对于 JSON 等结构化数据,Brotli 比 gzip 可额外节省 15-20%的传输量
在前端代码中处理压缩内容
现代浏览器默认支持自动解压 Content-Encoding 指定的压缩格式,但如果你在 JavaScript 中使用 fetch 或 XMLHttpRequest,可能需要注意一些细节:
javascript
// 使用fetch API时,浏览器会自动处理Content-Encoding
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
console.log('获取的数据:', data);
})
.catch(error => {
console.error('请求失败:', error);
});
常见问题与解决方案
问题 1:不是所有内容都适合压缩
像 JPEG、PNG 这类已经压缩过的格式,再次压缩收益很小,反而会浪费 CPU 资源。实际数据显示,对于 JPEG 图片进行 gzip 压缩,通常只能减少 0-2%的体积,但 CPU 使用率会增加 15-20%。
解决方案:在服务器配置中排除这些格式:
nginx
# Nginx配置
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# 注意这里没有包含image/jpeg等格式
问题 2:某些旧浏览器不支持特定压缩算法
特别是 Brotli(br)压缩,在 IE11 及以下版本完全不支持。
解决方案:服务器需要根据 Accept-Encoding 选择合适的算法,而不是强制使用最新的压缩方式。可以设置变通方案:
nginx
# 检测User-Agent为IE时使用gzip而非br
map $http_user_agent $brotli_status {
default on;
"~MSIE" off;
"~Trident" off;
}
brotli $brotli_status;
问题 3:压缩可能导致 CPU 使用率升高
高压缩级别会消耗更多 CPU 资源,在高流量网站可能成为瓶颈。测试数据显示,gzip 从压缩级别 5 到 9,CPU 使用率可能增加 3 倍,而压缩率仅提高约 5%。
解决方案:选择合适的压缩级别,比如 gzip 的 4-6 级别通常是压缩率和 CPU 消耗的良好平衡点:
nginx
# 平衡的压缩级别设置
gzip_comp_level 5;
brotli_comp_level 4;
静态资源预压缩技术
对于静态资源,可以提前压缩并保存为.gz
和.br
文件,避免服务器实时压缩带来的 CPU 开销:
生成预压缩文件
命令行工具方式:
bash
# 生成gzip压缩文件
find . -type f -name "*.js" -o -name "*.css" -o -name "*.html" | xargs gzip -9 -k
# 生成Brotli压缩文件(需安装brotli工具)
find . -type f -name "*.js" -o -name "*.css" -o -name "*.html" | xargs brotli -q 11
使用构建工具:
在 Webpack 项目中,可使用compression-webpack-plugin
:
javascript
// webpack.config.js
const CompressionPlugin = require('compression-webpack-plugin');
const zlib = require('zlib');
module.exports = {
// ...其他配置
plugins: [
// 生成.gz文件
new CompressionPlugin({
filename: '[path][base].gz',
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 1024,
minRatio: 0.8,
}),
// 生成.br文件
new CompressionPlugin({
filename: '[path][base].br',
algorithm: 'brotliCompress',
test: /\.(js|css|html|svg)$/,
compressionOptions: {
level: 11,
},
threshold: 1024,
minRatio: 0.8,
})
],
};
预压缩的优势在于可以使用更高压缩级别(如 gzip 级别 9、brotli 级别 11),而不用担心 CPU 开销,特别适合资源大小固定的网站。
一份完整的 Apache 配置示例
apache
<IfModule mod_deflate.c>
# 启用压缩
SetOutputFilter DEFLATE
# 仅压缩这些MIME类型
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript application/json
# 仅当客户端支持压缩时启用
SetEnvIfNoCase Accept-Encoding "gzip|deflate|br" HAVE_ENCODING
Header append Vary Accept-Encoding env=HAVE_ENCODING
# 排除老旧浏览器
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
# 设置压缩级别
DeflateCompressionLevel 6
</IfModule>
生产环境的干货技巧
-
分类处理资源:HTML/CSS/JS 文件必须启用压缩,图片/视频/字体文件一般无需重复压缩
-
动态内容设置阈值 :API 接口返回小于 1KB 的内容通常不值得压缩,设置合理的
min_length
-
确保 CDN 正确缓存 :添加
Vary: Accept-Encoding
头,保证 CDN 为不同压缩格式缓存不同版本 -
兼容老旧设备:低端移动设备解压能力有限,可针对特定 User-Agent 降低压缩级别或选择更快的算法
-
定期检测压缩效果:使用 Chrome DevTools、Lighthouse 等工具检查压缩是否生效,对比传输大小和原始大小
-
压缩预处理 :对静态资源提前压缩存储
.gz
和.br
文件,减轻服务器实时压缩负担 -
监控 CPU 使用率:在高并发环境下,观察压缩对服务器 CPU 的影响,必要时调整压缩级别
-
设置资源缓存时间:压缩后的静态资源应该设置长期缓存,进一步减少传输次数:
arduino
Cache-Control: public, max-age=31536000, immutable
immutable
指令告知客户端资源内容不变,可直接使用缓存,无需校验
- 针对 API 优化:对于 JSON 数据响应,优先使用 Brotli 压缩,其次是 zstd,因为它们对结构化数据的压缩效率比 gzip 高 15%-25%
总结
特性 | Accept-Encoding | Content-Encoding |
---|---|---|
发送方 | 客户端 | 服务器 |
含义 | 告知服务器客户端支持的压缩算法 | 告知客户端服务器使用的压缩算法 |
常见值 | gzip, deflate, br, zstd, identity | gzip, deflate, br, zstd |
多值表示 | 按优先级排序(带 q 值),如br;q=1.0,gzip |
多重压缩顺序(从右至左解压),如gzip,br |
默认行为 | 没有此头时假定只支持无压缩 | 没有此头时表示内容未压缩 |
与缓存关系 | 触发 Vary 头的设置 | 需配合 Vary: Accept-Encoding 使用 |