• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

chimurai / http-proxy-middleware / 14021280217

23 Mar 2025 05:21PM CUT coverage: 97.284%. Remained the same
14021280217

Pull #1086

github

web-flow
Merge 5b9c487b7 into 764194f0e
Pull Request #1086: build(patch-package): run patch-package in 'development' only

131 of 136 branches covered (96.32%)

394 of 405 relevant lines covered (97.28%)

24.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/http-proxy-middleware.ts
1
import type * as net from 'net';
2
import type * as http from 'http';
3
import type * as https from 'https';
4
import type { RequestHandler, Options, Filter, Logger } from './types';
5
import * as httpProxy from 'http-proxy';
12✔
6
import { verifyConfig } from './configuration';
12✔
7
import { getPlugins } from './get-plugins';
12✔
8
import { matchPathFilter } from './path-filter';
12✔
9
import * as PathRewriter from './path-rewriter';
12✔
10
import * as Router from './router';
12✔
11
import { Debug as debug } from './debug';
12✔
12
import { getFunctionName } from './utils/function';
12✔
13
import { getLogger } from './logger';
12✔
14

15
export class HttpProxyMiddleware<TReq, TRes> {
12✔
16
  private wsInternalSubscribed = false;
73✔
17
  private serverOnCloseSubscribed = false;
73✔
18
  private proxyOptions: Options<TReq, TRes>;
19
  private proxy: httpProxy<TReq, TRes>;
20
  private pathRewriter;
21
  private logger: Logger;
22

23
  constructor(options: Options<TReq, TRes>) {
24
    verifyConfig<TReq, TRes>(options);
73✔
25
    this.proxyOptions = options;
73✔
26
    this.logger = getLogger(options as unknown as Options);
73✔
27

28
    debug(`create proxy server`);
73✔
29
    this.proxy = httpProxy.createProxyServer({});
73✔
30

31
    this.registerPlugins(this.proxy, this.proxyOptions);
73✔
32

33
    this.pathRewriter = PathRewriter.createPathRewriter(this.proxyOptions.pathRewrite); // returns undefined when "pathRewrite" is not provided
73✔
34

35
    // https://github.com/chimurai/http-proxy-middleware/issues/19
36
    // expose function to upgrade externally
37
    this.middleware.upgrade = (req, socket, head) => {
73✔
38
      if (!this.wsInternalSubscribed) {
2✔
39
        this.handleUpgrade(req, socket, head);
2✔
40
      }
41
    };
42
  }
43

44
  // https://github.com/Microsoft/TypeScript/wiki/'this'-in-TypeScript#red-flags-for-this
45
  public middleware: RequestHandler = (async (req, res, next?) => {
73✔
46
    if (this.shouldProxy(this.proxyOptions.pathFilter, req)) {
55✔
47
      try {
49✔
48
        const activeProxyOptions = await this.prepareProxyRequest(req);
49✔
49
        debug(`proxy request to target: %O`, activeProxyOptions.target);
48✔
50
        this.proxy.web(req, res, activeProxyOptions);
48✔
51
      } catch (err) {
52
        next?.(err);
2✔
53
      }
54
    } else {
55
      next?.();
6✔
56
    }
57

58
    /**
59
     * Get the server object to subscribe to server events;
60
     * 'upgrade' for websocket and 'close' for graceful shutdown
61
     *
62
     * NOTE:
63
     * req.socket: node >= 13
64
     * req.connection: node < 13 (Remove this when node 12/13 support is dropped)
65
     */
66
    const server: https.Server = ((req.socket ?? req.connection) as any)?.server;
55✔
67

68
    if (server && !this.serverOnCloseSubscribed) {
55✔
69
      server.on('close', () => {
53✔
70
        debug('server close signal received: closing proxy server');
53✔
71
        this.proxy.close();
53✔
72
      });
73
      this.serverOnCloseSubscribed = true;
53✔
74
    }
75

76
    if (this.proxyOptions.ws === true) {
55✔
77
      // use initial request to access the server object to subscribe to http upgrade event
78
      this.catchUpgradeRequest(server);
2✔
79
    }
80
  }) as RequestHandler;
81

82
  private registerPlugins(proxy: httpProxy<TReq, TRes>, options: Options<TReq, TRes>) {
83
    const plugins = getPlugins<TReq, TRes>(options);
73✔
84
    plugins.forEach((plugin) => {
73✔
85
      debug(`register plugin: "${getFunctionName(plugin)}"`);
288✔
86
      plugin(proxy, options);
288✔
87
    });
88
  }
89

90
  private catchUpgradeRequest = (server: https.Server) => {
73✔
91
    if (!this.wsInternalSubscribed) {
2✔
92
      debug('subscribing to server upgrade event');
1✔
93
      server.on('upgrade', this.handleUpgrade);
1✔
94
      // prevent duplicate upgrade handling;
95
      // in case external upgrade is also configured
96
      this.wsInternalSubscribed = true;
1✔
97
    }
98
  };
99

100
  private handleUpgrade = async (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => {
73✔
101
    if (this.shouldProxy(this.proxyOptions.pathFilter, req)) {
3✔
102
      const activeProxyOptions = await this.prepareProxyRequest(req);
3✔
103
      this.proxy.ws(req, socket, head, activeProxyOptions);
3✔
104
      debug('server upgrade event received. Proxying WebSocket');
3✔
105
    }
106
  };
107

108
  /**
109
   * Determine whether request should be proxied.
110
   */
111
  private shouldProxy = (
73✔
112
    pathFilter: Filter<TReq> | undefined,
113
    req: http.IncomingMessage,
114
  ): boolean => {
115
    try {
58✔
116
      return matchPathFilter(pathFilter, req.url, req);
58✔
117
    } catch (err) {
118
      debug('Error: matchPathFilter() called with request url: ', `"${req.url}"`);
1✔
119
      this.logger.error(err);
1✔
120
      return false;
1✔
121
    }
122
  };
123

124
  /**
125
   * Apply option.router and option.pathRewrite
126
   * Order matters:
127
   *    Router uses original path for routing;
128
   *    NOT the modified path, after it has been rewritten by pathRewrite
129
   * @param {Object} req
130
   * @return {Object} proxy options
131
   */
132
  private prepareProxyRequest = async (req: http.IncomingMessage) => {
73✔
133
    /**
134
     * Incorrect usage confirmed: https://github.com/expressjs/express/issues/4854#issuecomment-1066171160
135
     * Temporary restore req.url patch for {@link src/legacy/create-proxy-middleware.ts legacyCreateProxyMiddleware()}
136
     * FIXME: remove this patch in future release
137
     */
138
    if ((this.middleware as unknown as any).__LEGACY_HTTP_PROXY_MIDDLEWARE__) {
52✔
139
      req.url = (req as unknown as any).originalUrl || req.url;
5!
140
    }
141

142
    const newProxyOptions = Object.assign({}, this.proxyOptions);
52✔
143

144
    // Apply in order:
145
    // 1. option.router
146
    // 2. option.pathRewrite
147
    await this.applyRouter(req, newProxyOptions);
52✔
148
    await this.applyPathRewrite(req, this.pathRewriter);
51✔
149

150
    return newProxyOptions;
51✔
151
  };
152

153
  // Modify option.target when router present.
154
  private applyRouter = async (req: http.IncomingMessage, options: Options<TReq, TRes>) => {
73✔
155
    let newTarget;
156

157
    if (options.router) {
52✔
158
      newTarget = await Router.getTarget(req, options);
11✔
159

160
      if (newTarget) {
10✔
161
        debug('router new target: "%s"', newTarget);
8✔
162
        options.target = newTarget;
8✔
163
      }
164
    }
165
  };
166

167
  // rewrite path
168
  private applyPathRewrite = async (req: http.IncomingMessage, pathRewriter) => {
73✔
169
    if (pathRewriter) {
51✔
170
      const path = await pathRewriter(req.url, req);
10✔
171

172
      if (typeof path === 'string') {
10✔
173
        debug('pathRewrite new path: %s', req.url);
9✔
174
        req.url = path;
9✔
175
      } else {
176
        debug('pathRewrite: no rewritten path found: %s', req.url);
1✔
177
      }
178
    }
179
  };
180
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc