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

chimurai / http-proxy-middleware / 14581171176

21 Apr 2025 08:50PM CUT coverage: 97.129%. Remained the same
14581171176

Pull #1112

github

web-flow
Merge 1e8b517eb into d1fb42501
Pull Request #1112: build(codespaces): add devcontainer.json

137 of 143 branches covered (95.8%)

406 of 418 relevant lines covered (97.13%)

24.65 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 http from 'http';
2
import * as httpProxy from 'http-proxy';
12✔
3
import type * as https from 'https';
4
import type * as net from 'net';
5

6
import { verifyConfig } from './configuration';
12✔
7
import { Debug as debug } from './debug';
12✔
8
import { getPlugins } from './get-plugins';
12✔
9
import { getLogger } from './logger';
12✔
10
import { matchPathFilter } from './path-filter';
12✔
11
import * as PathRewriter from './path-rewriter';
12✔
12
import * as Router from './router';
12✔
13
import type { Filter, Logger, Options, RequestHandler } from './types';
14
import { getFunctionName } from './utils/function';
12✔
15

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

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

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

32
    this.registerPlugins(this.proxy, this.proxyOptions);
75✔
33

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

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

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

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

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

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

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

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

101
  private handleUpgrade = async (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => {
75✔
102
    try {
4✔
103
      if (this.shouldProxy(this.proxyOptions.pathFilter, req)) {
4✔
104
        const activeProxyOptions = await this.prepareProxyRequest(req);
4✔
105
        this.proxy.ws(req, socket, head, activeProxyOptions);
3✔
106
        debug('server upgrade event received. Proxying WebSocket');
3✔
107
      }
108
    } catch (err) {
109
      // This error does not include the URL as the fourth argument as we won't
110
      // have the URL if `this.prepareProxyRequest` throws an error.
111
      this.proxy.emit('error', err, req, socket);
1✔
112
    }
113
  };
114

115
  /**
116
   * Determine whether request should be proxied.
117
   */
118
  private shouldProxy = (
75✔
119
    pathFilter: Filter<TReq> | undefined,
120
    req: http.IncomingMessage,
121
  ): boolean => {
122
    try {
59✔
123
      return matchPathFilter(pathFilter, req.url, req);
59✔
124
    } catch (err) {
125
      debug('Error: matchPathFilter() called with request url: ', `"${req.url}"`);
1✔
126
      this.logger.error(err);
1✔
127
      return false;
1✔
128
    }
129
  };
130

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

149
    const newProxyOptions = Object.assign({}, this.proxyOptions);
53✔
150

151
    // Apply in order:
152
    // 1. option.router
153
    // 2. option.pathRewrite
154
    await this.applyRouter(req, newProxyOptions);
53✔
155
    await this.applyPathRewrite(req, this.pathRewriter);
51✔
156

157
    return newProxyOptions;
51✔
158
  };
159

160
  // Modify option.target when router present.
161
  private applyRouter = async (req: http.IncomingMessage, options: Options<TReq, TRes>) => {
75✔
162
    let newTarget;
163

164
    if (options.router) {
53✔
165
      newTarget = await Router.getTarget(req, options);
13✔
166

167
      if (newTarget) {
11✔
168
        debug('router new target: "%s"', newTarget);
9✔
169
        options.target = newTarget;
9✔
170
      }
171
    }
172
  };
173

174
  // rewrite path
175
  private applyPathRewrite = async (req: http.IncomingMessage, pathRewriter) => {
75✔
176
    if (pathRewriter) {
51✔
177
      const path = await pathRewriter(req.url, req);
10✔
178

179
      if (typeof path === 'string') {
10✔
180
        debug('pathRewrite new path: %s', req.url);
9✔
181
        req.url = path;
9✔
182
      } else {
183
        debug('pathRewrite: no rewritten path found: %s', req.url);
1✔
184
      }
185
    }
186
  };
187
}
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