HTTP 报文压缩
根据 HTTP 协议,服务器返回内容给浏览器会尝试把 response 内容进行压缩,在浏览器解压后使用,以提升传输速度。工作原理主要是
- 浏览器发送请求时通过 request header
accept-encoding
标识支持的压缩格式 - 服务端从列表中选择一种用来对响应内容压缩,并通过 response header
content-encoding
指明使用的格式 - 浏览器得到响应正文后,依据
content-encoding
进行解压Node.js 压缩
Node.js 内容压缩主要通过内置的zlib
模块实现,zlib 提供了几种压缩方法,和 accept-encoding 的对应关系
accept-encoding | zlib |
---|---|
gzip | zlib.createGzip() |
deflate | zlib.createDeflate() |
br | zlib.createBrotliCompress() |
zlib 几个方法创建的对象都是 Transform 流,使用非常简单
const { createGzip } = require('zlib');
const { createReadStream, createWriteStream } = require('fs');
const gzip = createGzip();
const source = createReadStream('input.txt');
const destination = createWriteStream('input.txt.gz');
source.pipe(gzip).pipe(destination);
判断内容是否需要压缩
因为压缩有利于提升 Web 性能,所以尽可能对数据进行压缩,但压缩是一个有 CPU 开销的任务,有些文件类型本身已经压缩过,比如 jpeg 图片,无需再次压缩消耗 CPU,甚至再次压缩可能导致体积变大
可以安装、使用模块 compressible
判断模块是否需要压缩
npm install -S compressible
const compressible = require('compressible');
// 参数是 MIME
compressible('text/html'); // true
compressible('image/png'); // false
简单实现
为了实现功能需要引用几个新的模块, accepts
模块比直接使用 req.headers 方便很多
const zlib = require('zlib');
const compressible = require('compressible');
const accepts = require('accepts');
对返回文件内容部分逻辑做简单修改
const contentType = mime.contentType(path.extname(url));
let compression;
if (compressible(contentType)) {
const encodings = accepts(req).encodings();
const serverCompatibleCompressions = [
{ method: 'gzip', stream: zlib.createGzip() },
{ method: 'deflate', stream: zlib.createDeflate() },
{ method: 'br', stream: zlib.createBrotliCompress() },
];
// 按照浏览器指定优先级在服务器选择压缩方式
for (let i = 0; i < encodings.length; i++) {
compression = serverCompatibleCompressions.find(com => com.method === encodings[i]);
if (compression) {
break;
}
}
}
if (compression) {
res.writeHead(200, {
'Content-Type': contentType,
// 指定服务器使用的压缩方式,浏览器使用对应的解压方式
'Content-Encoding': compression.method,
});
fs.createReadStream(filePath).pipe(compression.stream).pipe(res);
} else {
res.writeHead(200, {
'Content-Type': contentType,
});
fs.createReadStream(filePath).pipe(res);
}
原体积 607k,压缩后变成了 139k,对传输性能优化非常明显
代码完整参考:https://github.com/Samaritan89/static-server/tree/v3
变更部分:https://github.com/Samaritan89/static-server/commit/ed99886e29cf656ce96e1e333f6e8823640e18f8