You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
注册

在NestJS中使用http-proxy-middleware作为中间件时每次请求创建新实例导致内存溢出的问题排查与解决

在NestJS中使用http-proxy-middleware作为中间件时每次请求创建新实例导致内存溢出的问题排查与解决

你遇到的这个内存溢出问题,核心可能是中间件实例的重复创建或者http-proxy-middleware的实例未被复用导致的内存泄漏,结合你的代码和尝试过的方案,我们一步步来排查和解决:

一、先确认:中间件真的每次请求都被实例化吗?

NestJS中,默认情况下@Injectable()装饰的中间件是单例模式,只会在应用启动时创建一次实例,除非你显式设置了Scope.REQUEST作用域。

你可以先在中间件的构造函数里加一行日志验证:

constructor(private readonly exampleService: ExampleService) {
  console.log('MyProxyMiddleware instance created');
  this.proxy = createProxyMiddleware(this.options);
}

启动应用后,多次请求目标路由,如果控制台只打印一次日志,说明中间件是单例,问题出在proxy实例的内存泄漏或其他逻辑;如果每次请求都打印,说明中间件被设置为请求作用域,需要调整。

二、如果中间件确实每次请求实例化:修复作用域

检查你的MyProxyMiddleware类的@Injectable()装饰器,是否不小心加了Scope.REQUEST

// 错误:REQUEST作用域会每次请求创建新实例
@Injectable({ scope: Scope.REQUEST })
export class MyProxyMiddleware implements NestMiddleware { ... }

改成默认的单例模式(去掉scope配置):

@Injectable()
export class MyProxyMiddleware implements NestMiddleware { ... }

如果是模块中注册中间件时的配置问题,确保没有用useClass配合请求作用域的设置。

三、复用http-proxy-middleware实例的正确姿势

你已经尝试把createProxyMiddleware移到构造函数,这是正确的方向,但可能因为类方法的绑定方式异步重写逻辑的处理不当导致内存泄漏,我们可以优化代码:

优化1:简化事件处理函数,避免不必要的this绑定

你的onProxyReqonProxyRes用箭头函数绑定了类的this,但其实这些逻辑可以抽成独立函数,避免类实例的引用被proxy长期持有:

@Injectable()
export class MyProxyMiddleware implements NestMiddleware {
  private proxy: RequestHandler;

  constructor(private readonly exampleService: ExampleService) {
    // 直接传入独立函数,无需绑定this
    this.proxy = createProxyMiddleware({
      ...this.options,
      onProxyReq: setRequestHeaders,
      onProxyRes: appendResponseHeaders,
    });
  }

  private readonly options = {
    // 补充你的proxy基础配置,比如target: 'https://你的目标服务地址'
    pathRewrite: async (path) => await this.rewrite(path),
  };

  async rewrite(path: string): Promise<string> {
    try {
      const resolved = await this.exampleService.resolve();
      // 这里写你的路径重写逻辑,返回新路径
      return path.replace(/需要替换的路径部分/, resolved);
    } catch (e) {
      throw e;
    }
  }

  async use(req, res, next: () => void) {
    // 等待路径重写完成再调用proxy
    req.url = await this.rewrite(req.url);
    this.proxy(req, res, next);
  }
}

// 抽成独立函数,避免类实例引用被长期持有
function setRequestHeaders(proxyReq, req) {
  Object.keys(req.headers).forEach((key) => {
    proxyReq.setHeader(key, req.headers[key]);
  });
}

function appendResponseHeaders(proxyRes, req, res) {
  Object.keys(proxyRes.headers).forEach((key) => {
    res.append(key, proxyRes.headers[key]);
  });
}

优化2:改用函数式中间件,彻底控制单例

如果类中间件的方式还是有问题,你可以改用函数式中间件,自己完全控制proxy实例的创建时机(只创建一次):

// proxy.middleware.ts
import { RequestHandler, createProxyMiddleware } from 'http-proxy-middleware';
import { ExampleService } from './example.service';

// 全局变量存储proxy实例,确保只创建一次
let proxyInstance: RequestHandler | null = null;

export function createMyProxyMiddleware(exampleService: ExampleService): RequestHandler {
  if (!proxyInstance) {
    proxyInstance = createProxyMiddleware({
      // 补充你的proxy基础配置,比如target: 'https://你的目标服务地址'
      pathRewrite: async (path) => {
        const resolved = await exampleService.resolve();
        // 路径重写逻辑
        return path.replace(/需要替换的路径部分/, resolved);
      },
      onProxyReq: (proxyReq, req) => {
        Object.keys(req.headers).forEach((key) => {
          proxyReq.setHeader(key, req.headers[key]);
        });
      },
      onProxyRes: (proxyRes, req, res) => {
        Object.keys(proxyRes.headers).forEach((key) => {
          res.append(key, proxyRes.headers[key]);
        });
      },
    });
  }
  return proxyInstance;
}

然后在app.module.ts中使用:

import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { createMyProxyMiddleware } from './proxy.middleware';
import { ExampleService } from './example.service';

@Module({
  providers: [ExampleService],
})
export class AppModule implements NestModule {
  constructor(private readonly exampleService: ExampleService) {}

  configure(consumer: MiddlewareConsumer) {
    // 只创建一次proxy实例
    const proxy = createMyProxyMiddleware(this.exampleService);
    consumer.apply((req, res, next) => proxy(req, res, next)).forRoutes({
      path: '/environments/*/documents/*/text*',
      method: RequestMethod.ALL,
    });
  }
}

这种方式完全绕开了NestJS的中间件实例管理,确保proxy只被初始化一次,从根源避免重复创建导致的内存问题。

四、其他可能的内存泄漏点排查

  1. 异步逻辑未正确处理:你的rewrite方法是async的,如果在use方法中没有正确await,会导致未处理的Promise堆积,必须确保重写完成后再调用proxy;
  2. 第三方依赖版本问题:检查http-proxy-middleware的版本,建议升级到最新稳定版(比如v2.x或v3.x),旧版本可能存在已知的内存泄漏bug;
  3. 请求/响应对象引用残留:确保onProxyReq/onProxyRes中没有长期持有req/res对象的引用(比如不要把它们存在全局变量中)。

你还可以用Node.js的调试工具定位具体泄漏点:启动应用时添加--inspect参数:

node --inspect dist/main.js

然后打开Chrome浏览器输入chrome://inspect,连接到你的Node进程,在Memory面板中多次抓取堆快照,对比快照中的对象数量,就能精准找到持续增长的对象类型。

备注:内容来源于stack exchange,提问作者An0ncoder

火山引擎 最新活动