人人都会AI编程

9.1 HTTP/HTTPS 模块

更新时间:2026-07-10

Node.js 内置的 httphttps 模块提供了构建 HTTP 服务器和发起 HTTP 客户端请求的基础能力。它们是整个 Node.js Web 生态的基石,Express、Koa 等框架底层都依赖这两个模块。掌握它们,不仅能帮助你理解框架的工作原理,也能在没有第三方库的情况下快速搭建轻量级服务或代理。

9.1.1 创建 HTTP 服务器

http.createServer() 是最常用的入口。它返回一个 http.Server 实例,并接受一个请求监听函数,每当有新请求到达时,该函数就会被调用。

const http = require('http');

const server = http.createServer((req, res) => {
  // req: http.IncomingMessage 实例,包含请求信息
  // res: http.ServerResponse 实例,用于构造响应
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain; charset=utf-8');
  res.end('你好,这里是 Node.js HTTP 服务器');
});

server.listen(3000, () => {
  console.log('服务器已启动, http://localhost:3000');
});

核心对象:

  • req(IncomingMessage):提供 methodurlheaders 等属性,并实现了 Readable Stream 接口,可按流的方式读取请求体。
  • res(ServerResponse):实现了 Writable Stream 接口,可逐步写入响应头和响应体。常用方法包括 writeHead()setHeader()write()end()

server.listen(port) 启动监听后,服务器便开始接收请求。可以是端口号,也可以是 Unix Socket 路径。

9.1.2 处理请求

1. 获取请求报文

  • 请求行req.methodreq.url。注意 req.url 包含路径和查询字符串,可以使用 url.parse 解析。
  • 请求头req.headers 是一个对象,键名统一小写,如 req.headers['content-type']
  • 请求体:需要监听 data 事件和 end 事件来收集数据。
let body = '';
req.on('data', (chunk) => {
  body += chunk;           // 假定为字符串编码
});
req.on('end', () => {
  console.log('请求体:', body);
});

更规范的做法是使用 content-type 判断并解析 JSON、URL 编码等格式。

2. 路由与请求方法判断

原生模块没有路由系统,需要手动判断:

if (req.method === 'GET' && req.url === '/users') {
  // 返回用户列表
} else if (req.method === 'POST' && req.url === '/users') {
  // 创建用户
} else {
  res.statusCode = 404;
  res.end('Not Found');
}

对于复杂的路由,通常引入 Express 等框架,但理解原生的处理过程对排查问题极有帮助。

3. 解析查询字符串

Node.js 内置的 querystring 模块可以解析 req.url 中的查询参数:

const url = require('url');
const parsed = url.parse(req.url, true);  // true 表示自动解析 query
console.log(parsed.query);                // 如 { id: '123' }

9.1.3 构造响应

响应行和响应头

res.writeHead(statusCode, headers) 可以一次性写入状态码和头信息。延迟设置可使用 res.statusCoderes.setHeader(),但必须在第一次调用 res.write()res.end() 之前设置。

res.writeHead(200, {
  'Content-Type': 'application/json',
  'X-Powered-By': 'Node.js'
});

响应体

res.write() 可多次调用,写入分块数据;res.end() 结束响应并可选的最后一次写入。如果只有一次数据,直接 res.end(data) 即可。

const data = { msg: 'success' };
res.end(JSON.stringify(data));

设置 HTTP 状态码

常见状态码直接用 res.statusCode 设置。需要保持描述一致,如 404 表示资源不存在。

9.1.4 处理文件上传

处理上传文件需要解析 multipart/form-data 格式的请求体。原生实现较繁琐,通常使用 busboyformidable 库,但理解原理依然有用。

以下为简化版原生解析思路(仅演示思路,生产环境请使用成熟库):

if (req.headers['content-type'].startsWith('multipart/form-data')) {
  const boundary = '--' + req.headers['content-type'].split('boundary=')[1];
  let chunks = [];
  req.on('data', (chunk) => chunks.push(chunk));
  req.on('end', () => {
    const body = Buffer.concat(chunks).toString('binary');
    // 按 boundary 分割并解析
    // 提取 filename, content-type, 文件内容等
    // 将文件写入磁盘
  });
}

实际开发中,formidable 可以很方便地处理上传:

const formidable = require('formidable');

const form = new formidable.IncomingForm();
form.parse(req, (err, fields, files) => {
  // fields 为普通文本字段
  // files 包含文件信息,如 files.avatar.path 为临时存储路径
});

与原生模块配合时,将 req 直接传入 form.parse() 即可。

9.1.5 Cookie 与 Session 原生实现

Cookie

服务器通过 Set-Cookie 响应头将客户端数据写入浏览器。读取时从 req.headers.cookie 获取。

设置 Cookie:

res.setHeader('Set-Cookie', ['username=Jin', 'language=zh; HttpOnly']);

每个 Cookie 以分号分隔属性,如 HttpOnly 禁止 JavaScript 访问、Max-Age 设置有效期。

读取 Cookie:

const cookieHeader = req.headers.cookie || '';
const cookies = cookieHeader.split(';').reduce((acc, pair) => {
  const [k, v] = pair.trim().split('=');
  acc[k] = v;
  return acc;
}, {});

Session

HTTP 无状态,Session 是服务器为维持用户状态而创建的数据存储。原生实现流程为:

  1. 客户端请求到达时,检查 Cookie 中是否存在 sessionId
  2. 若无,生成唯一标识(UUID 等),并在服务器内存(或 Redis)中创建 Session 对象,将 sessionId 通过 Cookie 发送给客户端。
  3. 若有,根据 sessionId 取出之前存储的数据。
  4. 在请求生命周期内,req.session 可读写;结束时将 Session 对象更新。
const sessions = new Map(); // 简单内存存储

const sessionId = parsedCookie.sessionId;
let session;
if (sessionId && sessions.has(sessionId)) {
  session = sessions.get(sessionId);
} else {
  session = { user: null };
  const newId = generateUniqueId(); // 生成唯一ID
  sessions.set(newId, session);
  res.setHeader('Set-Cookie', `sessionId=${newId}; HttpOnly`);
}

// 在业务中使用 session.user = { name: 'Jin' };

生产环境建议: 使用 express-session 等专人维护的模块,它们提供了内存、Redis、MongoDB 等多种存储后端,并处理了过期、安全等细节,但理解原生原理在调试时依旧必要。

9.1.6 HTTPS 服务器

https 模块用法与 http 几乎一致,区别在于需要提供 SSL/TLS 证书:

const https = require('https');
const fs = require('fs');

const options = {
  key: fs.readFileSync('private-key.pem'),
  cert: fs.readFileSync('certificate.pem')
};

https.createServer(options, (req, res) => {
  res.writeHead(200);
  res.end('Hello HTTPS');
}).listen(443);

开发测试时可使用自签名证书,但部署时请从认证机构获取可信证书。结合 http 模块可将 80 端口自动重定向到 HTTPS。

9.1.7 发起 HTTP/HTTPS 客户端请求

http.request()http.get() 用于从 Node.js 发起 HTTP 请求,例如调用后端 API、抓取网页等。

const https = require('https');

const options = {
  hostname: 'api.github.com',
  path: '/users/nodejs',
  method: 'GET',
  headers: { 'User-Agent': 'Node.js' }
};

const req = https.request(options, (res) => {
  let data = '';
  res.on('data', (chunk) => data += chunk);
  res.on('end', () => {
    console.log(JSON.parse(data));
  });
});

req.on('error', (e) => {
  console.error(`请求失败: ${e.message}`);
});

req.end(); // 结束请求,可写入请求体

http.get()http.request() 的快捷方式,固定 method: 'GET' 且自动调用 req.end()

POST 请求携带请求体:

const postData = JSON.stringify({ name: 'Jin' });
const req = http.request({
  hostname: 'example.com',
  path: '/api/create',
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Content-Length': Buffer.byteLength(postData)
  }
}, (res) => { /* ... */ });
req.write(postData);
req.end();

9.1.8 常见问题与最佳实践

  1. 永远不要阻塞事件循环:不要在主线程中执行同步的文件读取或长计算,否则会阻塞整个服务器,导致其他请求无法响应。
  2. 合理处理超时:客户端请求和服务器响应均可设置超时,避免连接长期挂起。
   req.setTimeout(5000, () => {
     req.destroy();
   });
   res.setTimeout(5000, () => {
     res.end('Request Timeout');
   });
   
  1. 限制请求体大小:防止巨量恶意请求耗尽内存,可在 req.on('data') 中累计长度并主动断开。
  2. 错误处理:务必监听 reqreserror 事件,防止进程异常退出。
  3. 使用 pipelinepipe 处理流:转发大文件时,用 stream.pipe() 而非一次性读入内存。
  4. 安全头:使用 helmet 库,或至少手动静置 X-Powered-By 头,避免泄露服务器信息。

9.1.9 小结

Node.js 的 HTTP 模块虽然简洁,但它完整覆盖了 HTTP 协议的核心操作——从解析请求到构造响应,从处理 Cookie/Session 到客户端请求。深入理解它,是精通 Web 框架和网络编程的基础,同时也是排查性能问题和安全漏洞的必备技能。在真实项目中,你通常会使用 Express、Koa 等封装层,但底层 http.IncomingMessagehttp.ServerResponse 始终是它们交互的对象。掌握本节内容,能让你在面对任何 Node.js Web 技术时都心中有底。