首页 / Node.js 教程 / zlib、http2 与 tls

Node.js 教程

zlib、http2 与 tls

本教程共 76 篇 · 第 32 篇 · 更新于 2026-07-25 · 约 6 分钟阅读

Node.jszlibhttp2tls压缩

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 里没有 reqres,核心概念是 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();
Note

HTTP/2 的服务器推送(Server Push)曾被寄予厚望,但主流浏览器已逐步减少支持。如果你在做新系统设计,不建议把 Push 当核心机制,优先优化资源合并和缓存策略。

tls:安全传输层

tls 模块是 httpshttp2 的底层支撑。日常写 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 多租户场景里用得很广。