zlib、http2 与 tls
本教程共 76 篇 · 第 32 篇 · 更新于 2026-07-25 · 约 6 分钟阅读
32. zlib、http2 与 tls
本节目标:数据压缩、HTTP/2 服务和 TLS 加密连接。
这一章打包三个偏「基础设施」的模块:用 zlib 压缩数据、用 http2 提升传输效率、用 tls 保障通信安全。它们各自独立,但在生产环境里经常一起出现——比如一个启用了 TLS 的 HTTP/2 服务器,自动用 zlib 压缩响应正文。
zlib:压缩与解压
zlib 模块提供 Gzip、Deflate 和 Brotli 的支持。日常接触最多的是 Gzip,HTTP 服务器的 Content-Encoding: gzip 就靠它实现。
压缩字符串
import zlib from 'node:zlib';
const input = 'Node.js zlib module is powerful and easy to use. '.repeat(50);
zlib.gzip(input, (err, compressed) => {
if (err) throw err;
console.log('Original:', input.length, 'bytes');
console.log('Compressed:', compressed.length, 'bytes');
zlib.gunzip(compressed, (err, decompressed) => {
if (err) throw err;
console.log('Match:', decompressed.toString() === input);
});
});
如果数据量小,同步版本更省事:
const compressed = zlib.gzipSync(input);
const restored = zlib.gunzipSync(compressed);
流式处理大文件
压缩一个几 GB 的日志文件,如果一次性读进内存,机器会直接崩。用流来流水线作业:
import zlib from 'node:zlib';
import { createReadStream, createWriteStream } from 'node:fs';
const input = createReadStream('./server.log');
const output = createWriteStream('./server.log.gz');
const gzip = zlib.createGzip();
input.pipe(gzip).pipe(output);
output.on('finish', () => {
console.log('Compression done');
});
解压反过来用 createGunzip() 即可。这种模式在日志归档、文件传输里非常常见。
在 HTTP 响应里启用压缩
浏览器发请求时,通常会在 Accept-Encoding 里声明自己支持 gzip。服务端检测到这个头,就可以把响应压缩后再发出去:
import http from 'node:http';
import zlib from 'node:zlib';
const server = http.createServer((req, res) => {
const acceptEncoding = req.headers['accept-encoding'] || '';
const body = 'Hello World!\n'.repeat(100);
if (acceptEncoding.includes('gzip')) {
res.setHeader('Content-Encoding', 'gzip');
res.setHeader('Content-Type', 'text/plain');
zlib.gzip(body, (err, compressed) => {
if (err) {
res.statusCode = 500;
res.end('Compression failed');
return;
}
res.end(compressed);
});
} else {
res.setHeader('Content-Type', 'text/plain');
res.end(body);
}
});
server.listen(3000);
生产环境建议直接用 Express 的 compression 中间件或 Fastify 的内置压缩,它们会帮你处理缓存策略、阈值判断和流式压缩。
http2:多路复用与头部压缩
HTTP/2 最大的改进是多路复用:在一个 TCP 连接上可以同时传多个请求和响应,不用再像 HTTP/1.1 那样开六个连接、排队等队首阻塞解除。
Node.js 的 http2 模块已经稳定。浏览器里的 HTTP/2 必须跑在 TLS 上,所以示例都以 https 为基础。
创建 HTTP/2 服务器
import http2 from 'node:http2';
import fs from 'node:fs';
const server = http2.createSecureServer({
key: fs.readFileSync('./server.key'),
cert: fs.readFileSync('./server.crt'),
allowHTTP1: true, // 允许老客户端回退到 HTTP/1.1
});
server.on('stream', (stream, headers) => {
const path = headers[':path'];
if (path === '/') {
stream.respond({
':status': 200,
'content-type': 'text/plain',
});
stream.end('Hello from HTTP/2');
} else {
stream.respond({ ':status': 404 });
stream.end('Not Found');
}
});
server.listen(8443, () => {
console.log('HTTP/2 server at https://localhost:8443');
});
注意 API 的变化:HTTP/2 里没有 req 和 res,核心概念是 stream。头部用 stream.respond() 发送,:status 是伪头部,代替原来的 res.statusCode。
生成自签名证书(开发用)
上面的代码需要证书文件。本地开发可以用 OpenSSL 快速生成:
openssl req -x509 -newkey rsa:2048 -nodes -sha256 \
-subj '/CN=localhost' -keyout server.key -out server.crt
浏览器访问时会报「不安全」,点继续就行。生产环境务必使用正规 CA 签发的证书,Let’s Encrypt 可以免费申请。
HTTP/2 客户端
Node.js 也能当 HTTP/2 客户端:
import http2 from 'node:http2';
const client = http2.connect('https://localhost:8443', {
rejectUnauthorized: false, // 仅用于自签名证书开发环境
});
const req = client.request({ ':path': '/' });
req.on('response', headers => {
console.log('Status:', headers[':status']);
});
req.setEncoding('utf8');
req.on('data', chunk => console.log(chunk));
req.on('end', () => client.close());
req.end();
NoteHTTP/2 的服务器推送(Server Push)曾被寄予厚望,但主流浏览器已逐步减少支持。如果你在做新系统设计,不建议把 Push 当核心机制,优先优化资源合并和缓存策略。
tls:安全传输层
tls 模块是 https 和 http2 的底层支撑。日常写 Web 服务,你更常接触的是高层的 https 模块,但了解几个 TLS 概念对排查问题很有帮助。
最简 HTTPS 服务器
import https from 'node:https';
import fs from 'node:fs';
const server = https.createServer({
key: fs.readFileSync('./server.key'),
cert: fs.readFileSync('./server.crt'),
}, (req, res) => {
res.writeHead(200);
res.end('Secure connection');
});
server.listen(443);
控制 TLS 版本和加密套件
老旧的 TLS 1.0/1.1 已经有已知漏洞,生产环境应该禁用:
import https from 'node:https';
import fs from 'node:fs';
const server = https.createServer({
key: fs.readFileSync('./server.key'),
cert: fs.readFileSync('./server.crt'),
minVersion: 'TLSv1.2',
maxVersion: 'TLSv1.3',
}, (req, res) => {
res.end('Modern TLS only');
});
server.listen(443);
minVersion: 'TLSv1.2' 会拒绝只支持 TLS 1.0/1.1 的古老客户端。v24 LTS 的默认行为已经很安全,但显式声明能让安全审计更清晰。
TLS 与 SNI
一台服务器上挂多个域名、每个域名用不同证书,靠的是 TLS 的 SNI(Server Name Indication)扩展。Node.js 支持通过 SNICallback 动态选择证书:
import tls from 'node:tls';
import fs from 'node:fs';
const server = tls.createServer({
SNICallback: (servername, cb) => {
if (servername === 'a.example.com') {
cb(null, tls.createSecureContext({
key: fs.readFileSync('./a.key'),
cert: fs.readFileSync('./a.crt'),
}));
} else {
cb(null, tls.createSecureContext({
key: fs.readFileSync('./default.key'),
cert: fs.readFileSync('./default.crt'),
}));
}
},
}, socket => {
socket.write(`Hello ${socket.servername}\n`);
socket.end();
});
server.listen(443);
这个能力在反向代理和 SaaS 多租户场景里用得很广。