Skip to content

Memory leak detected on the EventEmitter with http-proxy-middleware.js where server.on('close') listener is added for non websocket usage #909

Open
@xen-HendryZheng

Description

@xen-HendryZheng

Checks

  • I understand project setup issues should be asked on StackOverflow or in GitHub Discussions.
    I updated to latest http-proxy-middleware.

Describe the bug (be clear and concise)

I had an issue with my project where the memory is steadily increasing. When I turned on the profiling through chrome inspector, I get a warning of this
image
And when I checked and console.log the req.socket where the code of the library is creating the listener at, on every single request being made which result into invoking createHttpProxyMiddleware, it will always add an event listener to server.onClose, and the object was never cleared by the Garbage Collector.

Please see screenshot below
image
I've created a runner which triggered 50request at a time, and it created 50 close events without being cleared.

Step-by-step reproduction instructions

1. Turn on node --inspect on your nodejs application
2. Open chrome://inspect and make sure you are on debug mode
3. Fire 50 or more request, you shall see a warning which I've described above.

Expected behavior (be clear and concise)

Since request.connection has been deprected, hence request.socket which used in this line should only applicable for HPM with mode ws: true. For ws: false which is non websocket, should not need this kind of listener.

How is http-proxy-middleware used in your project?

****-***-*******@1.0.0 ***/***/****
├─┬ ***/***/****
│ └── http-proxy-middleware@2.0.6 deduped
└── http-proxy-middleware@2.0.6

What http-proxy-middleware configuration are you using?

this.proxy = createProxyMiddleware({
            target: this.url,
            ws: false,
            pathRewrite: () => (prefix ? `${prefix}${path}` : `${path}`),
            onProxyReq: (proxyReq: ClientRequest, req: Request) => {
                proxyReq.setHeader('service-id', ServiceIdSnapApiGateway);
                if (this.xApiKey) {
                    proxyReq.setHeader('x-api-key', this.xApiKey);
                }
                if (req.body) {
                    const contentType: string = proxyReq.getHeader('Content-Type') as string;

                    let bodyData;
                    if (contentType && contentType.includes('application/json')) {
                        bodyData = JSON.stringify(req.body);
                    } else if (contentType && contentType.includes('application/x-www-form-urlencoded')) {
                        bodyData = queryString.stringify(req.body);
                    }

                    if (bodyData) {
                        proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
                        proxyReq.write(bodyData);
                    }
                }
            },
            onProxyRes: extendProxyRes,
            changeOrigin: true,
            logLevel: 'silent'
        });

What OS/version and node/version are you seeing the problem?

Node 18.16.0
macOS Ventura v13.2.1
Memory 8gb
Chip Apple M1

Additional context (optional)

No response

Activity

changed the title [-]Memory leak detected on the EventEmitter wutg http-proxy-middleware.js where server.on('close') listener is added[/-] [+]Memory leak detected on the EventEmitter with http-proxy-middleware.js where server.on('close') listener is added[/+] on May 1, 2023
changed the title [-]Memory leak detected on the EventEmitter with http-proxy-middleware.js where server.on('close') listener is added[/-] [+]Memory leak detected on the EventEmitter with http-proxy-middleware.js where server.on('close') listener is added for non websocket usage[/+] on Jun 15, 2023
taozhi8833998

taozhi8833998 commented on Jul 6, 2023

@taozhi8833998

got the same issue

chimurai

chimurai commented on Jul 6, 2023

@chimurai
Owner

Try to create the proxy once, instead of on every request.

Similar issue: #108 (comment)

Could you share what server are you using?

RadekKpc

RadekKpc commented on Sep 20, 2023

@RadekKpc

Is anyone working on that?

tak1n

tak1n commented on Sep 26, 2023

@tak1n

We are facing the same issue on a NestJS Application trying to leverage http-proxy-middleware through a Nest Middleware.

// proxy.middleware.ts
import {
  Inject,
  NestMiddleware,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { createProxyMiddleware } from 'http-proxy-middleware';
import { ConfigurationService } from '../configuration/configuration.service';
import { randomUUID } from 'node:crypto';

export class ProxyMiddleware implements NestMiddleware {
  constructor(
    private configService: ConfigurationService,
  ) {}

  async use(req: Request, res: Response, next: () => void): Promise<any> {
    const upstreamInfo = <method to get jwt etc>;
    const correlationId = randomUUID();

    const proxy = createProxyMiddleware({
      target: this.configService.getProxyTarget(),
      headers: {
        host: this.configService.getUpstreamHost(),
        authorization: `Bearer ${upstreamInfo.jwt}`,
        'x-organization-id': upstreamInfo.organizationId,
        'x-correlation-id': correlationId,
      },
      changeOrigin: false,
      xfwd: true,
    });

    proxy(req, res, next);
  }
}

// proxy.module.ts
import {
  MiddlewareConsumer,
  Module,
  NestModule,
  RequestMethod,
} from '@nestjs/common';
import { ConfigurationModule } from '../configuration/configuration.module';
import { ProxyMiddleware } from './proxy.middleware';

@Module({
  providers: [],
  imports: [
    ConfigurationModule,
  ],
})
export class ProxyModule implements NestModule {
  configure(consumer: MiddlewareConsumer): any {
    consumer
      .apply(ProxyMiddleware)
      .exclude(
        { path: 'ready', method: RequestMethod.GET },
        { path: 'live', method: RequestMethod.GET },
      )
      .forRoutes('');
  }
}

Try to create the proxy once, instead of on every request.

Is there a way to create the proxy instance only once but still be able to dynamically set headers for the proxied request? I know of onProxyReq to manipulate requests, but having a hard time figuring out a way to use that with a Nest Middleware that uses dependencies for various required functionalities (issue JWT, etc.).

tak1n

tak1n commented on Sep 27, 2023

@tak1n

Leaving this here for anyone facing the same issue with a NestJS application. The solution was like suggested by @chimurai to only call createProxyMiddleware once.

// proxy.middleware.ts
import {
  Inject,
  NestMiddleware,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { RequestHandler, createProxyMiddleware } from 'http-proxy-middleware';
import { ConfigurationService } from '../configuration/configuration.service';
import { randomUUID } from 'node:crypto';

export class ProxyMiddleware implements NestMiddleware {
  private proxy: RequestHandler;

  constructor(
    private configService: ConfigurationService,
  ) {
    this.proxy = createProxyMiddleware({
      target: this.configService.getProxyTarget(),
      headers: {
        host: this.configService.getUpstreamHost(),
      },
      changeOrigin: false,
      xfwd: true,
    });
  }

  async use(req: Request, res: Response, next: () => void): Promise<any> {
    const upstreamInfo = <method to get jwt etc>;
    const correlationId = randomUUID();

    req.headers = {
      ...req.headers,
      authorization: `Bearer ${upstreamInfo.jwt}`,
      'x-organization-id': upstreamInfo.organizationId,
      'x-correlation-id': correlationId,
    }

    this.proxy(req, res, next);
  }
}
abhishek73magar

abhishek73magar commented on Mar 6, 2025

@abhishek73magar

I just increase default Max Listeners for event emitter and this warning is despair from my terminal

More details about Memory Leak with EventEmitter
https://www.dhiwise.com/post/best-practices-for-handling-maxlistenersexceededwarning

import EventEmitter from "events"
EventEmitter.defaultMaxListeners = 20

i think event-emitter have maximum 12 deafult MaxListeners i need to increase the max Listeners size

node:628163) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 close listeners added to [Server]. Use emitter.setMaxListeners() to increase limit (Use node --trace-warnings ...to show where the warning was created) (node:628163) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 upgrade listeners added to [Server]. Use emitter.setMaxListeners() to increase limit

import express, { Application, NextFunction, Request, Response } from 'express'
import { createProxyMiddleware, Options } from 'http-proxy-middleware'
import cors from 'cors'
import cookieParser from 'cookie-parser'
import setHeaders from 'libs/set-headers'
import { PORT } from 'config/config'
import { ClientRequest, Server } from 'http'
import routes from 'config/routes'
import accessConfig from 'libs/access-config'
import EventEmitter from 'events'

EventEmitter.defaultMaxListeners = 20
const onProxyReq = (proxyRes: ClientRequest, req: Request, res: Response) => {
  // console.log('proxyRes', proxyRes)

  // for(oldKey of proxyRes.headers) {

  // }
}

const middleware = (req: Request, res: Response, next: NextFunction) => {
  // console.log('hey')
  return next();
}

const start = (): Promise<Server> => {
  return new Promise((resolve, reject) => {
    const app: Application = express();
    app.use(cors({
      origin: ['http://localhost:3000', 'http://localhost:3001'],
      credentials: true,
    }))

    app.use(cookieParser())
    app.use(setHeaders)

    routes.slice(0, 13).forEach(({ route, target, pathRewrite, openAccess }) => {
      let rewrite_path: Record<string, string> | undefined = undefined
      // if(pathRewrite){
      //   rewrite_path = Object.entries(pathRewrite).reduce<Record<string, string>>((prev, [key, value]) => {
      //     prev[key] = value ? value : ''
      //     return prev;
      //   }, {})
      // }
      if(pathRewrite) rewrite_path = { [pathRewrite[0]]: pathRewrite[1] }
      
      console.log(`[HPM] Proxy created: ${route} -> ${target}`)
      const proxyMiddleware = createProxyMiddleware({
        target: target,
        pathFilter: route,
        changeOrigin: true,
        pathRewrite: rewrite_path,
        proxyTimeout: 1000 * 60, // 1 minute
        ws: true,

        // logger: console,
        on: {
          proxyReq: onProxyReq,
          open: () => {
            // console.log(`[HPM] Proxy created: ${route} -> ${target}`)
          },
          start: () => {
            console.log(`[HPM] Proxy created: ${route} -> ${target}`)
          },
          
        }
      })
      app.use(accessConfig(openAccess) as (req: Request, res: Response, next: NextFunction) => void, proxyMiddleware)
    })
    

    // process.setMaxListeners(1000)
    const server = app.listen(PORT, () => resolve(server))
  })
}

export { start }
Pswzy

Pswzy commented on Mar 24, 2025

@Pswzy

This issue generally occurs in Koa because Koa's middleware is initialized on every request. The instance created by createProxyMiddleware continuously listens for the server to close, which prevents the reference from being released, thereby causing a memory leak.
The solution is to avoid using createProxyMiddleware inside the middleware. Instead, execute createProxyMiddleware in advance so that each middleware references the result of the execution of createProxyMiddleware.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      Participants

      @chimurai@tak1n@taozhi8833998@Pswzy@RadekKpc

      Issue actions

        Memory leak detected on the EventEmitter with http-proxy-middleware.js where server.on('close') listener is added for non websocket usage · Issue #909 · chimurai/http-proxy-middleware