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

chimurai / http-proxy-middleware / 4156949968

pending completion
4156949968

push

github

GitHub
docs(README): fix github-actions workflow badge (#874)

158 of 162 branches covered (97.53%)

368 of 376 relevant lines covered (97.87%)

23.29 hits per line

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

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

12
export class HttpProxyMiddleware {
12✔
13
  private wsInternalSubscribed = false;
64✔
14
  private serverOnCloseSubscribed = false;
64✔
15
  private proxyOptions: Options;
16
  private proxy: httpProxy;
17
  private pathRewriter;
18

19
  constructor(options: Options) {
20
    verifyConfig(options);
64✔
21
    this.proxyOptions = options;
64✔
22

23
    debug(`create proxy server`);
64✔
24
    this.proxy = httpProxy.createProxyServer({});
64✔
25

26
    this.registerPlugins(this.proxy, this.proxyOptions);
64✔
27

28
    this.pathRewriter = PathRewriter.createPathRewriter(this.proxyOptions.pathRewrite); // returns undefined when "pathRewrite" is not provided
64✔
29

30
    // https://github.com/chimurai/http-proxy-middleware/issues/19
31
    // expose function to upgrade externally
32
    this.middleware.upgrade = (req, socket, head) => {
64✔
33
      if (!this.wsInternalSubscribed) {
2✔
34
        this.handleUpgrade(req, socket, head);
2✔
35
      }
36
    };
37
  }
38

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

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

63
    if (server && !this.serverOnCloseSubscribed) {
53✔
64
      server.on('close', () => {
51✔
65
        debug('server close signal received: closing proxy server');
51✔
66
        this.proxy.close();
51✔
67
      });
68
      this.serverOnCloseSubscribed = true;
51✔
69
    }
70

71
    if (this.proxyOptions.ws === true) {
53✔
72
      // use initial request to access the server object to subscribe to http upgrade event
73
      this.catchUpgradeRequest(server);
2✔
74
    }
75
  };
76

77
  private registerPlugins(proxy: httpProxy, options: Options) {
78
    const plugins = getPlugins(options);
64✔
79
    plugins.forEach((plugin) => {
64✔
80
      debug(`register plugin: "${getFunctionName(plugin)}"`);
254✔
81
      plugin(proxy, options);
254✔
82
    });
83
  }
84

85
  private catchUpgradeRequest = (server: https.Server) => {
64✔
86
    if (!this.wsInternalSubscribed) {
2✔
87
      debug('subscribing to server upgrade event');
1✔
88
      server.on('upgrade', this.handleUpgrade);
1✔
89
      // prevent duplicate upgrade handling;
90
      // in case external upgrade is also configured
91
      this.wsInternalSubscribed = true;
1✔
92
    }
93
  };
94

95
  private handleUpgrade = async (req: Request, socket, head) => {
64✔
96
    if (this.shouldProxy(this.proxyOptions.pathFilter, req)) {
3✔
97
      const activeProxyOptions = await this.prepareProxyRequest(req);
3✔
98
      this.proxy.ws(req, socket, head, activeProxyOptions);
3✔
99
      debug('server upgrade event received. Proxying WebSocket');
3✔
100
    }
101
  };
102

103
  /**
104
   * Determine whether request should be proxied.
105
   */
106
  private shouldProxy = (pathFilter: Filter, req: Request): boolean => {
64✔
107
    return matchPathFilter(pathFilter, req.url, req);
56✔
108
  };
109

110
  /**
111
   * Apply option.router and option.pathRewrite
112
   * Order matters:
113
   *    Router uses original path for routing;
114
   *    NOT the modified path, after it has been rewritten by pathRewrite
115
   * @param {Object} req
116
   * @return {Object} proxy options
117
   */
118
  private prepareProxyRequest = async (req: Request) => {
64✔
119
    /**
120
     * Incorrect usage confirmed: https://github.com/expressjs/express/issues/4854#issuecomment-1066171160
121
     * Temporary restore req.url patch for {@link src/legacy/create-proxy-middleware.ts legacyCreateProxyMiddleware()}
122
     * FIXME: remove this patch in future release
123
     */
124
    if ((this.middleware as unknown as any).__LEGACY_HTTP_PROXY_MIDDLEWARE__) {
51✔
125
      req.url = (req as unknown as any).originalUrl || req.url;
5!
126
    }
127

128
    const newProxyOptions = Object.assign({}, this.proxyOptions);
51✔
129

130
    // Apply in order:
131
    // 1. option.router
132
    // 2. option.pathRewrite
133
    await this.applyRouter(req, newProxyOptions);
51✔
134
    await this.applyPathRewrite(req, this.pathRewriter);
50✔
135

136
    return newProxyOptions;
50✔
137
  };
138

139
  // Modify option.target when router present.
140
  private applyRouter = async (req: Request, options) => {
64✔
141
    let newTarget;
142

143
    if (options.router) {
51✔
144
      newTarget = await Router.getTarget(req, options);
10✔
145

146
      if (newTarget) {
9✔
147
        debug('router new target: "%s"', newTarget);
7✔
148
        options.target = newTarget;
7✔
149
      }
150
    }
151
  };
152

153
  // rewrite path
154
  private applyPathRewrite = async (req: Request, pathRewriter) => {
64✔
155
    if (pathRewriter) {
50✔
156
      const path = await pathRewriter(req.url, req);
10✔
157

158
      if (typeof path === 'string') {
10✔
159
        debug('pathRewrite new path: %s', req.url);
9✔
160
        req.url = path;
9✔
161
      } else {
162
        debug('pathRewrite: no rewritten path found: %s', req.url);
1✔
163
      }
164
    }
165
  };
166
}
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