前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket

# 资源压缩 zlib

30 秒速记

  • zlib 是 Node.js 内置压缩模块:gzip / deflate / brotli 都有,流式和一次性两套 API
  • 流式用 pipeline 串文件流或 HTTP 流,自动处理背压和错误传播
  • 压不压看四件事:客户端 Accept-Encoding、内容类型、体积、CPU → 图片视频已经压过,别再压
  • 动态压缩设最小体积阈值,响应加 Vary: Accept-Encoding
  • 解压不可信数据要限制输出体积,防压缩炸弹

zlib 是 Node.js 自带的压缩模块,能做 gzip、deflate、brotli,平时用得最多的是把它当成一个「转换流」接在读写流中间。 比如读文件 → 压缩 → 写文件,或者读文件 → 压缩 → 写进 HTTP 响应。我一般用 stream.pipeline 来串,它会处理背压,也能在任何一段出错时把整条链路关掉。是否压缩要看收益:文本类资源压完通常能小 70% 以上,图片视频本身已经压过,再压只是白费 CPU。解压外部数据时还得限制输出大小,不然一个几 KB 的包可能解出几个 GB。

理解「资源压缩 zlib」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

zlib 有两套 API,选错了在线上会很明显。

一套是一次性的:zlib.gzipSync(buf)、zlib.gzip(buf, cb),输入输出都是完整的 Buffer,适合几 KB 的字符串。Sync 版会阻塞事件循环,在请求处理里压大内容,整个服务都会卡住。

另一套是流式的:zlib.createGzip()、createGunzip()、createBrotliCompress() 等,返回一个 Transform 流,数据分块进、分块出,内存占用和文件大小无关。

const fs = require('fs');
const zlib = require('zlib');
const { pipeline } = require('stream/promises');

async function gzipFile(src) {
  await pipeline(
    fs.createReadStream(src),
    zlib.createGzip({ level: 6 }), // 1 最快,9 压得最小
    fs.createWriteStream(src + '.gz'),
  );
  console.log('done'); // 任一环节出错都会 reject,流也会被自动销毁
}
gzipFile('./app.js').catch((e) => { console.error(e); process.exitCode = 1; });
@前端进阶之旅: 代码已经复制到剪贴板

几个容易踩的坑:压缩本身有固定开销,'hello world' 压完是 31 字节,比原文还大,所以动态压缩要设阈值(常见是 1KB);brotli 压缩率比 gzip 高,但高等级压缩很慢,适合构建时预压缩,不适合每个请求实时压;解压不可信输入时,一次性 API 可以传 maxOutputLength,超出会抛 ERR_BUFFER_TOO_LARGE,流式解压则要自己数输出字节,超了就 destroy。

面试官追问

追问 1压缩脚本跑完只检查目标文件存在就算成功,哪里不够?
参考回答

输入损坏或中途出错时,目标文件可能已经建出来了,只是半截。要以 pipeline 成功结束为准,失败时删掉半成品、设置非零退出码,最好再解压一遍校验内容。

追问 2静态资源要压上千个文件,一次性全开流会怎样?
参考回答

文件句柄和内存会一起涨,可能直接 EMFILE。用一个并发上限,比如同时处理 8 个,逐个记录失败,最后汇总退出码。

追问 3构建时压缩失败,是继续发布原文件还是阻断 CI?
参考回答

看产物是否关键。允许降级的话,也要把失败打出来、单独告警,不能吞掉;关键产物直接阻断。别让「为了可用性」变成静默忽略错误。

追问 4压缩任务结束后进程迟迟不退出,查什么?
参考回答

查是不是有流没关、句柄没释放,比如出错分支里只关了读流没关写流。用 pipeline 能省掉大部分这类问题;最后靠 process.exit() 硬退只是掩盖。

# 概览

30 秒速记

  • 流程:浏览器在 Accept-Encoding 里报能力 → 服务端挑一种压缩 → 用 Content-Encoding 告诉浏览器
  • 常见编码:gzip(兼容最好)、br(压得更小,HTTPS 下普遍支持)、deflate(基本没人用)
  • 文本类资源(JS / CSS / HTML / JSON)收益大;图片、视频、压缩包别再压
  • 缓存层要加 Vary: Accept-Encoding,否则会把压缩版发给不支持的客户端
  • 压缩只在一层做:网关做了,应用就别再做

gzip 压缩是一次协商:浏览器在请求头 Accept-Encoding 里说自己支持什么,服务端挑一种压好,在响应头 Content-Encoding 里写明用了哪种。 浏览器拿到后自动解压,业务代码完全无感。Node.js 里干这活的就是 zlib 模块。实际项目里我更关心的是边界:图片视频本来就压过,别再压;有 CDN 缓存时要带上 Vary: Accept-Encoding;还有压缩到底放在 Nginx 还是 Node.js 里,选一层就好,两层都开容易出乱子。

做过web性能优化的同学,对性能优化大杀器gzip应该不陌生。浏览器向服务器发起资源请求,比如下载一个js文件,服务器先对资源进行压缩,再返回给浏览器,以此节省流量,加快访问速度。

浏览器通过HTTP请求头部里加上Accept-Encoding,告诉服务器,“你可以用gzip,或者defalte算法压缩资源”。

Accept-Encoding:gzip, deflate

那么,在nodejs里,是如何对资源进行压缩的呢?答案就是Zlib模块。

面试官追问

追问 1浏览器带了 Accept-Encoding: gzip,请求的是 JPG,要不要压?
参考回答

不要。Accept-Encoding 只说明它能解,不代表每个资源都值得压。JPG 本身就是压缩格式,再 gzip 体积几乎不变,CPU 白花。

追问 2抓包看到响应体是压缩数据,但没有 Content-Encoding 头,会怎样?
参考回答

浏览器会把压缩后的字节当成原始内容,JS 直接解析失败,页面一堆乱码报错。先查服务端是否漏写了头,再看代理有没有把这个头删了。

追问 3CDN 只按 URL 缓存,旧客户端收到了 gzip 内容解不开,怎么修?
参考回答

源站响应加 Vary: Accept-Encoding,让缓存按客户端的编码能力分开存。再确认 CDN 是否认这个头,有的 CDN 要在控制台单独开。

追问 4网关已经开了 gzip,Node.js 里又压一次,会出什么事?
参考回答

轻则白费 CPU,重则内容被编码两次、头和内容对不上,客户端解压失败。只让一层负责,一般交给 Nginx 或 CDN,应用返回原始内容。

# 入门实例:简单的压缩/解压缩

30 秒速记

  • 压缩:createReadStream → createGzip() → createWriteStream('x.gz')
  • 解压:createReadStream('x.gz') → createGunzip() → createWriteStream
  • 生产里用 pipeline 代替一串 pipe,出错能统一捕获、自动关流
  • 成功的判据:pipeline 正常结束 + 解压回来和原文一致,而不是「文件存在」
  • 解压不可信数据要限制输出大小

压缩和解压在 zlib 里是对称的:读流 → 压缩或解压转换流 → 写流,中间换一个 createGzip 或 createGunzip 就行。 数据是一块一块流过去的,文件再大内存也稳定。入门例子常用 a.pipe(b).pipe(c),但 pipe 不会把错误往下传,读文件失败时写流可能一直挂着,所以正式代码我都换成 pipeline。验证也别只看有没有生成 .gz,最好压完再解回来和原文比一下,再拿一个损坏的文件跑一遍,确认失败路径能报错、能退出。

理解「入门实例:简单的压缩/解压缩」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

完整的压缩 + 解压往返例子,顺便把失败路径也覆盖上:

const fs = require('fs');
const zlib = require('zlib');
const { pipeline } = require('stream/promises');

async function roundTrip() {
  fs.writeFileSync('a.txt', 'hello zlib\n'.repeat(1000));

  await pipeline(fs.createReadStream('a.txt'), zlib.
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
      • 概览
      • 入门实例:简单的压缩/解压缩
        • 压缩的例子
        • 解压的例子
      • 服务端gzip压缩
      • 服务端字符串gzip压缩
      • 写在后面
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket
完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

资源压缩 zlib|Nodejs专题系列

# 资源压缩 zlib

30 秒速记

  • zlib 是 Node.js 内置压缩模块:gzip / deflate / brotli 都有,流式和一次性两套 API
  • 流式用 pipeline 串文件流或 HTTP 流,自动处理背压和错误传播
  • 压不压看四件事:客户端 Accept-Encoding、内容类型、体积、CPU → 图片视频已经压过,别再压
  • 动态压缩设最小体积阈值,响应加 Vary: Accept-Encoding
  • 解压不可信数据要限制输出体积,防压缩炸弹

zlib 是 Node.js 自带的压缩模块,能做 gzip、deflate、brotli,平时用得最多的是把它当成一个「转换流」接在读写流中间。 比如读文件 → 压缩 → 写文件,或者读文件 → 压缩 → 写进 HTTP 响应。我一般用 stream.pipeline 来串,它会处理背压,也能在任何一段出错时把整条链路关掉。是否压缩要看收益:文本类资源压完通常能小 70% 以上,图片视频本身已经压过,再压只是白费 CPU。解压外部数据时还得限制输出大小,不然一个几 KB 的包可能解出几个 GB。

理解「资源压缩 zlib」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

zlib 有两套 API,选错了在线上会很明显。

一套是一次性的:zlib.gzipSync(buf)、zlib.gzip(buf, cb),输入输出都是完整的 Buffer,适合几 KB 的字符串。Sync 版会阻塞事件循环,在请求处理里压大内容,整个服务都会卡住。

另一套是流式的:zlib.createGzip()、createGunzip()、createBrotliCompress() 等,返回一个 Transform 流,数据分块进、分块出,内存占用和文件大小无关。

const fs = require('fs');
const zlib = require('zlib');
const { pipeline } = require('stream/promises');

async function gzipFile(src) {
  await pipeline(
    fs.createReadStream(src),
    zlib.createGzip({ level: 6 }), // 1 最快,9 压得最小
    fs.createWriteStream(src + '.gz'),
  );
  console.log('done'); // 任一环节出错都会 reject,流也会被自动销毁
}
gzipFile('./app.js').catch((e) => { console.error(e); process.exitCode = 1; });
@前端进阶之旅: 代码已经复制到剪贴板

几个容易踩的坑:压缩本身有固定开销,'hello world' 压完是 31 字节,比原文还大,所以动态压缩要设阈值(常见是 1KB);brotli 压缩率比 gzip 高,但高等级压缩很慢,适合构建时预压缩,不适合每个请求实时压;解压不可信输入时,一次性 API 可以传 maxOutputLength,超出会抛 ERR_BUFFER_TOO_LARGE,流式解压则要自己数输出字节,超了就 destroy。

面试官追问

追问 1压缩脚本跑完只检查目标文件存在就算成功,哪里不够?
参考回答

输入损坏或中途出错时,目标文件可能已经建出来了,只是半截。要以 pipeline 成功结束为准,失败时删掉半成品、设置非零退出码,最好再解压一遍校验内容。

追问 2静态资源要压上千个文件,一次性全开流会怎样?
参考回答

文件句柄和内存会一起涨,可能直接 EMFILE。用一个并发上限,比如同时处理 8 个,逐个记录失败,最后汇总退出码。

追问 3构建时压缩失败,是继续发布原文件还是阻断 CI?
参考回答

看产物是否关键。允许降级的话,也要把失败打出来、单独告警,不能吞掉;关键产物直接阻断。别让「为了可用性」变成静默忽略错误。

追问 4压缩任务结束后进程迟迟不退出,查什么?
参考回答

查是不是有流没关、句柄没释放,比如出错分支里只关了读流没关写流。用 pipeline 能省掉大部分这类问题;最后靠 process.exit() 硬退只是掩盖。

# 概览

30 秒速记

  • 流程:浏览器在 Accept-Encoding 里报能力 → 服务端挑一种压缩 → 用 Content-Encoding 告诉浏览器
  • 常见编码:gzip(兼容最好)、br(压得更小,HTTPS 下普遍支持)、deflate(基本没人用)
  • 文本类资源(JS / CSS / HTML / JSON)收益大;图片、视频、压缩包别再压
  • 缓存层要加 Vary: Accept-Encoding,否则会把压缩版发给不支持的客户端
  • 压缩只在一层做:网关做了,应用就别再做

gzip 压缩是一次协商:浏览器在请求头 Accept-Encoding 里说自己支持什么,服务端挑一种压好,在响应头 Content-Encoding 里写明用了哪种。 浏览器拿到后自动解压,业务代码完全无感。Node.js 里干这活的就是 zlib 模块。实际项目里我更关心的是边界:图片视频本来就压过,别再压;有 CDN 缓存时要带上 Vary: Accept-Encoding;还有压缩到底放在 Nginx 还是 Node.js 里,选一层就好,两层都开容易出乱子。

做过web性能优化的同学,对性能优化大杀器gzip应该不陌生。浏览器向服务器发起资源请求,比如下载一个js文件,服务器先对资源进行压缩,再返回给浏览器,以此节省流量,加快访问速度。

浏览器通过HTTP请求头部里加上Accept-Encoding,告诉服务器,“你可以用gzip,或者defalte算法压缩资源”。

Accept-Encoding:gzip, deflate

那么,在nodejs里,是如何对资源进行压缩的呢?答案就是Zlib模块。

面试官追问

追问 1浏览器带了 Accept-Encoding: gzip,请求的是 JPG,要不要压?
参考回答

不要。Accept-Encoding 只说明它能解,不代表每个资源都值得压。JPG 本身就是压缩格式,再 gzip 体积几乎不变,CPU 白花。

追问 2抓包看到响应体是压缩数据,但没有 Content-Encoding 头,会怎样?
参考回答

浏览器会把压缩后的字节当成原始内容,JS 直接解析失败,页面一堆乱码报错。先查服务端是否漏写了头,再看代理有没有把这个头删了。

追问 3CDN 只按 URL 缓存,旧客户端收到了 gzip 内容解不开,怎么修?
参考回答

源站响应加 Vary: Accept-Encoding,让缓存按客户端的编码能力分开存。再确认 CDN 是否认这个头,有的 CDN 要在控制台单独开。

追问 4网关已经开了 gzip,Node.js 里又压一次,会出什么事?
参考回答

轻则白费 CPU,重则内容被编码两次、头和内容对不上,客户端解压失败。只让一层负责,一般交给 Nginx 或 CDN,应用返回原始内容。

# 入门实例:简单的压缩/解压缩

30 秒速记

  • 压缩:createReadStream → createGzip() → createWriteStream('x.gz')
  • 解压:createReadStream('x.gz') → createGunzip() → createWriteStream
  • 生产里用 pipeline 代替一串 pipe,出错能统一捕获、自动关流
  • 成功的判据:pipeline 正常结束 + 解压回来和原文一致,而不是「文件存在」
  • 解压不可信数据要限制输出大小

压缩和解压在 zlib 里是对称的:读流 → 压缩或解压转换流 → 写流,中间换一个 createGzip 或 createGunzip 就行。 数据是一块一块流过去的,文件再大内存也稳定。入门例子常用 a.pipe(b).pipe(c),但 pipe 不会把错误往下传,读文件失败时写流可能一直挂着,所以正式代码我都换成 pipeline。验证也别只看有没有生成 .gz,最好压完再解回来和原文比一下,再拿一个损坏的文件跑一遍,确认失败路径能报错、能退出。

理解「入门实例:简单的压缩/解压缩」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

完整的压缩 + 解压往返例子,顺便把失败路径也覆盖上:

const fs = require('fs');
const zlib = require('zlib');
const { pipeline } = require('stream/promises');

async function roundTrip() {
  fs.writeFileSync('a.txt', 'hello zlib\n'.repeat(1000));

  await pipeline(fs.createReadStream('a.txt'), zlib.createGzip(), fs.createWriteStream('a.txt.gz'));
  await pipeline(fs.createReadStream('a.txt.gz'), zlib.createGunzip(), fs.createWriteStream('b.txt'));

  console.log(fs.statSync('a.txt').size, fs.statSync('a.txt.gz').size); // 11000 和几十字节
  console.log(fs.readFileSync('a.txt', 'utf8') === fs.readFileSync('b.txt', 'utf8')); // true

  fs.writeFileSync('bad.gz', 'not gzip at all');
  try {
    await pipeline(fs.createReadStream('bad.gz'), zlib.createGunzip(), fs.createWriteStream('c.txt'));
  } catch (e) {
    console.log(e.code); // Z_DATA_ERROR
    fs.rmSync('c.txt', { force: true }); // 清掉半成品
  }
}
roundTrip();
@前端进阶之旅: 代码已经复制到剪贴板

这里能看到三件事:重复内容多的文本压缩率非常高;压缩和解压只是中间那个转换流不同;解压失败时 pipeline 返回的 Promise 会失败,读写流都会被自动关闭,剩下的事就是清理半成品和设置退出码。a.pipe(b).pipe(c) 的写法做不到这些,b 出错时 c 不会收到通知,进程可能就挂在那儿。

← 9.4 MD5入门介绍及crypto模块的应用9.5 集群 cluster →

fe