使用 NodeJS 拦截 HTTP 请求

2025-06-07

使用 NodeJS 拦截 HTTP 请求

简介

作为工作项目的一部分,我需要开发一种方法来拦截和存储任何给定后端应用程序(在本例中为微服务)的 HTTP 流量。这本来是一个相当简单的任务,但我们的后端由许多服务(和许多代码库)组成。因此,该解决方案必须尽可能无缝衔接,以便能够轻松集成到任何服务中。

TLDR;

使用@mswjs/interceptors可以直接拦截后端应用程序上的 HTTP 流量。

拦截HTTP流量

对于我的用例,我能想到的捕获 HTTP 流量的方法有两种:

  1. 创建一个包装 HTTP 客户端库(如 Axios)的库
  2. 以某种方式拦截所有 HTTP 流量

理想情况下,我会选择方案 1,因为它最简单。可惜的是,我负责的项目包含许多由不同团队负责的微服务。因此,每个人回头重构代码以使用这个新库都会很困难。

因此,我唯一的选择实际上就是选项 2

第一次尝试

我的第一次尝试还算成功,但远非完美。在尝试直接通过底层http模块拦截流量后,我选择了一种更高级的解决方案。我的想法是对Axios 的请求方法进行monkey patch,以便在请求发送之前和响应接收之后注入我自己的逻辑。

function _instrumentAxios(axiosInstance: AxiosInstance) {
   axiosInstance.request = _instrumentHttpRequest(axiosInstance.request, axiosInstance);
   axiosInstance.get = _instrumentHttpRequest(axiosInstance.get, axiosInstance, "get");
   axiosInstance.post = _instrumentHttpRequest(axiosInstance.post, axiosInstance, "post");
   axiosInstance.put = _instrumentHttpRequest(axiosInstance.put, axiosInstance, "put");
   axiosInstance.patch = _instrumentHttpRequest(axiosInstance.patch, axiosInstance, "patch");
   axiosInstance.delete = _instrumentHttpRequest(axiosInstance.delete, axiosInstance, "delete");
   axiosInstance.options = _instrumentHttpRequest(axiosInstance.options, axiosInstance, "options");
}

function _instrumentHttpRequest(originalFunction: Function, thisArgument: any, method?: string) {
   return async function() {
      const {method: cleanedMethod, url, config: requestConfig, data} = _parseAxiosArguments(arguments, method);

      const requestEvent: HttpRequestEvent = {
         url,
         method: cleanedMethod,
         body: data,
         headers: requestConfig?.headers,
      };


      // Intentionally not waiting for a response to avoid adding any latency with this instrumentation
      doSomethingWithRequest(requestEvent);

      const res = await originalFunction.apply(thisArgument, arguments);


      const responseEvent: HttpResponseEvent = {
         url,
         method: cleanedMethod,
         body: res.data,
         headers: res.headers,
         statusCode: res.status,
      };

      doSomethingWithResponse(responseEvent);

      return res;
   };
}
Enter fullscreen mode Exit fullscreen mode

这种方法效果很好,但是我在阅读 Axios 文档时偶然发现了一种更简洁的方法。

第二次尝试

令我惊讶的是,Axios 实际上提供了一个用于拦截请求和响应的 API!

import {createInterceptor, InterceptorApi, IsomorphicRequest, IsomorphicResponse} from "@mswjs/interceptors";
import {interceptXMLHttpRequest} from "@mswjs/interceptors/lib/interceptors/XMLHttpRequest";
import {interceptClientRequest} from "@mswjs/interceptors/lib/interceptors/ClientRequest";

function _instrumentAxios(axiosInstance: AxiosInstance) {
   axiosInstance.interceptors.request.use(_instrumentHttpRequest);
   axiosInstance.interceptors.response.use(_instrumentHttpResponse);
}

function _instrumentHttpRequest(requestConfig: AxiosRequestConfig): AxiosRequestConfig {
   const method = String(requestConfig.method);
   const headers = requestConfig.headers && {
      ...requestConfig.headers.common,
      ...requestConfig.headers[method],
   };

   const requestEvent: HttpRequestEvent = {
      headers,
      method,
      url: String(requestConfig.url),
      body: requestConfig.data,
   };


   // Intentionally not waiting for a response to avoid adding any latency with this instrumentation
   doSomethingWithRequest(requestEvent);

   return requestConfig;
}

function _instrumentHttpResponse(response: AxiosResponse): AxiosResponse {
   const responseEvent: HttpResponseEvent = {
      url: String(response.request?.url),
      method: String(response.request?.method),
      body: response.data,
      headers: response.headers,
      statusCode: response.status,
   };


   // Intentionally not waiting for a response to avoid adding any latency with this instrumentation
   doSomethingWithResponse(responseEvent);

   return response;
}
Enter fullscreen mode Exit fullscreen mode

啊!好多了。然而,这种方法还有另一个麻烦,第一次尝试时也遇到了:必须为每个 Axios 实例设置拦截;这会降低开发者的体验。我最初以为大家都用的是默认的 Axios 实例。然而,事实证明,也可以通过 创建新实例axios.create()。所以,回到绘图板 😔

最终解决方案

在尝试处理底层http模块之前,我决定先寻找一些现有的解决方案。经过一段时间的摸索,我偶然发现了这个库@mswjs/interceptors。这个库文档非常完善,并且对 TypeScript 友好。

function _instrumentHTTPTraffic() {
    const interceptor = createInterceptor({
      resolver: () => {}, // Required even if not used
      modules: [interceptXMLHttpRequest, interceptClientRequest],
   });

   interceptor.on("request", _handleHttpRequest);

   interceptor.on("response", _handleHttpResponse);

   interceptor.apply();
}

function _handleHttpRequest(request: IsomorphicRequest): void {
   const url = request.url.toString();
   const method = String(request.method);
   const headers = request.headers.raw();

   const requestEvent: HttpRequestEvent = {
      headers,
      method,
      url: request.url.toString(),
      body: request.body,
   };


   // Intentionally not waiting for a response to avoid adding any latency with this instrumentation
   doSomethingWithRequest(requestEvent);
}

function _handleHttpResponse(request: IsomorphicRequest, response: IsomorphicResponse): void {
   const url = request.url.toString();
   const headers = request.headers.raw();


   const responseEvent: HttpResponseEvent = {
      url: request.url.toString(),
      method: request.method,
      body: response.body,
      headers: response.headers.raw(),
      statusCode: response.status,
   };

   // Intentionally not waiting for a response to avoid adding any latency with this instrumentation
   doSomethingWithResponse(responseEvent);
}

Enter fullscreen mode Exit fullscreen mode

卡维亚茨

虽然最终的解决方案更加通用,并且与所使用的客户端 HTTP 库无关,但仍存在一些缺点:

  • 由于所有流经应用的 HTTP 流量都会被拦截,因此需要一些逻辑来判断哪些请求需要忽略。例如,像 NewRelic 这样的监测工具会定期发送请求来捕获元数据。如果处理不当,这可能会增加很多干扰。
  • 依赖其他库。这是否严重取决于拦截的用途。对于大多数项目来说,可能不是什么大问题。
文章来源:https://dev.to/henryjw/intercepting-http-requests-with-nodejs-21ba
PREV
Docker 初学者指南 — 如何使用 docker-compose 创建客户端/服务器端
NEXT
6 个灵光乍现的时刻