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

chimurai / http-proxy-middleware / 4768666030

pending completion
4768666030

push

github

GitHub
refactor: minor type improvements (#895)

161 of 167 branches covered (96.41%)

369 of 378 relevant lines covered (97.62%)

24.63 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 net from 'net';
2
import type * as http from 'http';
3
import type * as https from 'https';
4
import type { RequestHandler, Options, Filter } 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

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

21
  constructor(options: Options<TReq, TRes>) {
22
    verifyConfig<TReq, TRes>(options);
71✔
23
    this.proxyOptions = options;
71✔
24

25
    debug(`create proxy server`);
71✔
26
    this.proxy = httpProxy.createProxyServer({});
71✔
27

28
    this.registerPlugins(this.proxy, this.proxyOptions);
71✔
29

30
    this.pathRewriter = PathRewriter.createPathRewriter(this.proxyOptions.pathRewrite); // returns undefined when "pathRewrite" is not provided
71✔
31

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

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

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

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

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

79
  private registerPlugins(proxy: httpProxy<TReq, TRes>, options: Options<TReq, TRes>) {
80
    const plugins = getPlugins<TReq, TRes>(options);
71✔
81
    plugins.forEach((plugin) => {
71✔
82
      debug(`register plugin: "${getFunctionName(plugin)}"`);
281✔
83
      plugin(proxy, options);
281✔
84
    });
85
  }
86

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

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

105
  /**
106
   * Determine whether request should be proxied.
107
   */
108
  private shouldProxy = (
71✔
109
    pathFilter: Filter<TReq> | undefined,
110
    req: http.IncomingMessage
111
  ): boolean => {
112
    return matchPathFilter(pathFilter, req.url, req);
57✔
113
  };
114

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

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

135
    // Apply in order:
136
    // 1. option.router
137
    // 2. option.pathRewrite
138
    await this.applyRouter(req, newProxyOptions);
52✔
139
    await this.applyPathRewrite(req, this.pathRewriter);
51✔
140

141
    return newProxyOptions;
51✔
142
  };
143

144
  // Modify option.target when router present.
145
  private applyRouter = async (req: http.IncomingMessage, options: Options<TReq, TRes>) => {
71✔
146
    let newTarget;
147

148
    if (options.router) {
52✔
149
      newTarget = await Router.getTarget(req, options);
10✔
150

151
      if (newTarget) {
9✔
152
        debug('router new target: "%s"', newTarget);
7✔
153
        options.target = newTarget;
7✔
154
      }
155
    }
156
  };
157

158
  // rewrite path
159
  private applyPathRewrite = async (req: http.IncomingMessage, pathRewriter) => {
71✔
160
    if (pathRewriter) {
51✔
161
      const path = await pathRewriter(req.url, req);
10✔
162

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