人人都会AI编程

发起 HTTP/HTTPS 客户端请求

更新时间:2026-07-10

在 Node.js 中不仅要能接收 HTTP 请求,很多场景下服务本身也需要扮演客户端的角色:调用第三方 API、请求下游微服务、抓取网页内容等。httphttps 模块提供了最原生的客户端请求能力。理解这套 API 的原理,能帮助你在不引入第三方库的情况下完成基本的 HTTP 调用,也能更好地理解诸如 axiosgot 等流行库的内部实现。

原生 http.get 快速发起 GET 请求

对于不需要自定义请求头、不需要发送 Body 的简单 GET 场景,http.get 是最快捷的方法。它自动将请求方法设为 GET,并调用 req.end() 结束请求。

const https = require('https');

https.get('https://api.github.com/users/github', (res) => {
  let data = '';

  // 响应数据可能分块到达,需要拼接
  res.on('data', (chunk) => {
    data += chunk;
  });

  // 数据接收完毕
  res.on('end', () => {
    try {
      const parsed = JSON.parse(data);
      console.log(`用户名称: ${parsed.name}`);
      console.log(`粉丝数: ${parsed.followers}`);
    } catch (e) {
      console.error('JSON 解析失败:', e.message);
    }
  });
}).on('error', (err) => {
  console.error('请求失败:', err.message);
});

输出会是类似这样的结果:

用户名称: GitHub
粉丝数: 12345

http.get 实际上返回一个 http.ClientRequest 对象,可以监听 error 事件。在 Node.js 中,如果没有为这个返回对象添加错误处理器,请求过程中发生的错误会直接抛出并可能导致进程崩溃。因此 始终为请求对象添加 error 监听器 是一个铁律。

通用请求方法:http.request

http.requestget 更灵活,可以指定任意 HTTP 方法(POST / PUT / DELETE 等)、自定义请求头以及发送 Body 数据。它返回同样的 ClientRequest 对象,但 需要手动调用 req.end() 来发送请求。

发送 POST 请求(带 JSON Body)

const https = require('https');

// 待发送数据
const postData = JSON.stringify({
  title: 'foo',
  body: 'bar',
  userId: 1,
});

const options = {
  hostname: 'jsonplaceholder.typicode.com',
  path: '/posts',
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Content-Length': Buffer.byteLength(postData),
  },
};

const req = https.request(options, (res) => {
  let responseData = '';

  res.on('data', (chunk) => {
    responseData += chunk;
  });

  res.on('end', () => {
    console.log('状态码:', res.statusCode);
    console.log('响应:', JSON.parse(responseData));
  });
});

req.on('error', (err) => {
  console.error('请求出错:', err.message);
});

// 写入请求体并终止请求
req.write(postData);
req.end();

要点说明:

  • Content-TypeContent-Length 是服务端正确解析 JSON 的必要头信息,尤其是 Content-Length,部分严格的服务端会根据它来判断请求体是否接收完整。
  • req.write() 可多次调用,用于发送大文件或分块数据;对于一次性的数据,也可以直接将数据写在 req.end(postData) 里。
  • 请求对象有 error 事件,响应对象也可能有 error 事件(例如响应流意外中断)。健壮的代码应当同时监听两者。

设置超时与终止请求

原生 API 并不自动提供超时机制,长时间未响应的请求会导致程序挂起。可以通过 req.setTimeout()req.on('timeout', ...) 来实现超时控制:

const req = https.request(options, (res) => { /* ... */ });

req.setTimeout(5000, () => {
  // 超时后销毁请求,触发 error 事件
  req.destroy(new Error('请求超时'));
});

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

req.end();

当请求在 5 秒内未完成时,req.destroy() 会主动断开底层 Socket,并触发 error 事件。注意要在销毁后清理可能的资源泄漏。

对于 http.get,同样可以链式调用 setTimeout

https.get('https://api.example.com', { timeout: 5000 }, (res) => { /* ... */ })
     .on('error', console.error);

HTTPS 的特有配置:忽略证书验证(仅开发调试)

在对接自签名证书或测试环境时,HTTPS 模块默认会验证服务器证书的有效性,导致 UNABLE_TO_VERIFY_LEAF_SIGNATURE 之类的错误。可以在开发阶段 临时 设置环境变量:

process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

绝对禁止在生产环境关闭证书验证,这样做会使连接容易遭受中间人攻击。更安全的做法是为特定请求传入自定义的 https.Agent

const agent = new https.Agent({
  rejectUnauthorized: false, // 仅用于内部测试
});

https.get('https://self-signed.internal', { agent }, (res) => { /* ... */ });

处理重定向

http/https 模块 不会自动跟随重定向。当收到 301/302 状态码时,需要手动提取 Location 头并重新发起请求。实际开发中这个逻辑往往被封装为函数,或者直接使用具备自动重定向功能的第三方库(如 axiosmaxRedirects)。

一个简单的手动重定向实现思路:

function requestWithRedirect(url, callback, maxRedirects = 5) {
  const lib = url.startsWith('https') ? https : http;
  lib.get(url, (res) => {
    if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
      if (maxRedirects <= 0) {
        callback(new Error('重定向次数过多'));
        return;
      }
      // 递归跟随重定向
      requestWithRedirect(res.headers.location, callback, maxRedirects - 1);
    } else {
      callback(null, res);
    }
  }).on('error', callback);
}

注意需要处理相对路径与绝对路径的重定向地址,并使用 url.resolvenew URL 来拼接完整 URL。

连接复用:使用 Agent 管理连接池

http/https 默认会为每个请求创建新的 TCP 连接,并在响应完成后保持连接一段时间(keep-alive)。通过设置 http.Agent 可以更精细地控制连接池的行为:

const keepAliveAgent = new https.Agent({
  keepAlive: true,           // 启用连接复用
  maxSockets: 10,            // 同一主机最大连接数
  maxFreeSockets: 5,         // 空闲连接池上限
  timeout: 60000,            // 空闲连接回收时间
});

const options = {
  hostname: 'api.example.com',
  agent: keepAliveAgent,
};

https.get(options, (res) => { /* ... */ });

对于需要短时间内向同一服务发起大量请求的场景,连接复用能显著提升性能并降低端口耗尽风险。

与第三方库的对比选择

原生 http/https 模块足够底层、零依赖,适合一些极简场景或编写底层工具。但在生产项目中,开发者通常会选择更高层次的 HTTP 客户端库:

  • axios:支持 Promise、自动 JSON 解析、请求/响应拦截器、取消请求、自动重定向。
  • node-fetch:将浏览器 Fetch API 引入 Node.js,符合 Web 标准,适合前后端代码统一。
  • got:专为 Node.js 设计,支持超时、重试、进度事件、HTTP/2。
  • superagent:链式 API,支持浏览器和 Node.js。

这些库普遍提供了:

  • 更简洁的 API
  • 内置超时与重试机制
  • 自动解析 JSON
  • 请求/响应拦截与转换
  • 完善的错误处理

例如用 axios 完成上述 POST 请求只需几行:

const axios = require('axios');

axios.post('https://jsonplaceholder.typicode.com/posts', {
  title: 'foo',
  body: 'bar',
  userId: 1,
})
.then(response => console.log(response.data))
.catch(error => console.error(error.message));

尽管封装层次不同,理解原生模块的工作原理仍然有价值:当库无法满足特殊需求(比如自定义协议、底层流控、资源精细管理),或者需要避免依赖膨胀时,你可以直接使用 http/https 写出高度可控的客户端代码。

实际开发中的注意点

  1. 始终处理 error 事件:请求对象、响应流都应监听错误,避免未捕获异常导致进程退出。
  2. 限制请求体大小:服务端返回的响应可能会非常大,应当在 res.on('data') 中累积数据前做好长度检查,防止内存溢出。
  3. 设置合理的超时:网络环境不可靠,永远不要假设请求一定会完成。
  4. 关闭不必要的连接:如果不再需要某个 Agent 或请求,应及时清理,避免占用文件描述符。
  5. 区分 HTTP 和 HTTPS 模块:两者 API 几乎相同,但 http.request 无法请求 HTTPS URL(会报错),反之亦然。可以使用 url.startsWith('https') 动态选择模块。

通过 http/https 原生的客户端请求能力,你可以构建轻量级的代理、爬虫、接口测试工具,也为深入理解 Node.js 的网络通信机制打下了基础。在后续的章节中,我们将进一步讨论文件上传、流式处理以及安全相关的编程实践。