HTTP 报文压缩

根据 HTTP 协议,服务器返回内容给浏览器会尝试把 response 内容进行压缩,在浏览器解压后使用,以提升传输速度。工作原理主要是

  1. 浏览器发送请求时通过 request header accept-encoding 标识支持的压缩格式
  2. 服务端从列表中选择一种用来对响应内容压缩,并通过 response header content-encoding 指明使用的格式
  3. 浏览器得到响应正文后,依据 content-encoding 进行解压

    Node.js 压缩

    Node.js 内容压缩主要通过内置的 zlib 模块实现,zlib 提供了几种压缩方法,和 accept-encoding 的对应关系
accept-encoding zlib
gzip zlib.createGzip()
deflate zlib.createDeflate()
br zlib.createBrotliCompress()

zlib 几个方法创建的对象都是 Transform 流,使用非常简单

  1. const { createGzip } = require('zlib');
  2. const { createReadStream, createWriteStream } = require('fs');
  3. const gzip = createGzip();
  4. const source = createReadStream('input.txt');
  5. const destination = createWriteStream('input.txt.gz');
  6. source.pipe(gzip).pipe(destination);

判断内容是否需要压缩

因为压缩有利于提升 Web 性能,所以尽可能对数据进行压缩,但压缩是一个有 CPU 开销的任务,有些文件类型本身已经压缩过,比如 jpeg 图片,无需再次压缩消耗 CPU,甚至再次压缩可能导致体积变大

可以安装、使用模块 compressible 判断模块是否需要压缩

  1. npm install -S compressible
  1. const compressible = require('compressible');
  2. // 参数是 MIME
  3. compressible('text/html'); // true
  4. compressible('image/png'); // false

简单实现

为了实现功能需要引用几个新的模块, accepts 模块比直接使用 req.headers 方便很多

  1. const zlib = require('zlib');
  2. const compressible = require('compressible');
  3. const accepts = require('accepts');

对返回文件内容部分逻辑做简单修改

  1. const contentType = mime.contentType(path.extname(url));
  2. let compression;
  3. if (compressible(contentType)) {
  4. const encodings = accepts(req).encodings();
  5. const serverCompatibleCompressions = [
  6. { method: 'gzip', stream: zlib.createGzip() },
  7. { method: 'deflate', stream: zlib.createDeflate() },
  8. { method: 'br', stream: zlib.createBrotliCompress() },
  9. ];
  10. // 按照浏览器指定优先级在服务器选择压缩方式
  11. for (let i = 0; i < encodings.length; i++) {
  12. compression = serverCompatibleCompressions.find(com => com.method === encodings[i]);
  13. if (compression) {
  14. break;
  15. }
  16. }
  17. }
  18. if (compression) {
  19. res.writeHead(200, {
  20. 'Content-Type': contentType,
  21. // 指定服务器使用的压缩方式,浏览器使用对应的解压方式
  22. 'Content-Encoding': compression.method,
  23. });
  24. fs.createReadStream(filePath).pipe(compression.stream).pipe(res);
  25. } else {
  26. res.writeHead(200, {
  27. 'Content-Type': contentType,
  28. });
  29. fs.createReadStream(filePath).pipe(res);
  30. }

image.png
原体积 607k,压缩后变成了 139k,对传输性能优化非常明显
image.png
代码完整参考:https://github.com/Samaritan89/static-server/tree/v3
变更部分:https://github.com/Samaritan89/static-server/commit/ed99886e29cf656ce96e1e333f6e8823640e18f8