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

chimurai / http-proxy-middleware / 14318273124

07 Apr 2025 07:56PM CUT coverage: 98.061% (-0.3%) from 98.329%
14318273124

Pull #1090

github

web-flow
Merge 03d00a49a into 1e9233909
Pull Request #1090: fix(fixRequestBody): prevent multiple .write() calls

145 of 158 branches covered (91.77%)

3 of 4 new or added lines in 1 file covered. (75.0%)

354 of 361 relevant lines covered (98.06%)

33.7 hits per line

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

96.25
/src/http-proxy-middleware.ts
1
import type * as https from 'https';
2
import type * as express from 'express';
3
import type { Filter, Request, RequestHandler, Response, Options } from './types';
4
import * as httpProxy from 'http-proxy';
7✔
5
import { createConfig, Config } from './config-factory';
7✔
6
import * as contextMatcher from './context-matcher';
7✔
7
import * as handlers from './_handlers';
7✔
8
import { getArrow, getInstance } from './logger';
7✔
9
import * as PathRewriter from './path-rewriter';
7✔
10
import * as Router from './router';
7✔
11
export class HttpProxyMiddleware {
7✔
12
  private logger = getInstance();
57✔
13
  private config: Config;
14
  private wsInternalSubscribed = false;
57✔
15
  private serverOnCloseSubscribed = false;
57✔
16
  private proxyOptions: Options;
17
  private proxy: httpProxy;
18
  private pathRewriter;
19

20
  constructor(context: Filter | Options, opts?: Options) {
21
    this.config = createConfig(context, opts);
57✔
22
    this.proxyOptions = this.config.options;
57✔
23

24
    // create proxy
25
    this.proxy = httpProxy.createProxyServer({});
57✔
26
    this.logger.info(`[HPM] Proxy created: ${this.config.context}  -> ${this.proxyOptions.target}`);
57✔
27

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

30
    // attach handler to http-proxy events
31
    handlers.init(this.proxy, this.proxyOptions);
57✔
32

33
    // log errors for debug purpose
34
    this.proxy.on('error', this.logError);
57✔
35

36
    // https://github.com/chimurai/http-proxy-middleware/issues/19
37
    // expose function to upgrade externally
38
    (this.middleware as any).upgrade = (req, socket, head) => {
57✔
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 (
57✔
47
    req: Request,
48
    res: Response,
49
    next: express.NextFunction
50
  ) => {
51
    if (this.shouldProxy(this.config.context, req)) {
46✔
52
      try {
40✔
53
        const activeProxyOptions = await this.prepareProxyRequest(req);
40✔
54
        this.proxy.web(req, res, activeProxyOptions);
39✔
55
      } catch (err) {
56
        next(err);
1✔
57
      }
58
    } else {
59
      next();
6✔
60
    }
61

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

72
    if (server && !this.serverOnCloseSubscribed) {
46✔
73
      server.on('close', () => {
44✔
74
        this.logger.info('[HPM] server close signal received: closing proxy server');
44✔
75
        this.proxy.close();
44✔
76
      });
77
      this.serverOnCloseSubscribed = true;
44✔
78
    }
79

80
    if (this.proxyOptions.ws === true) {
46✔
81
      // use initial request to access the server object to subscribe to http upgrade event
82
      this.catchUpgradeRequest(server);
2✔
83
    }
84
  };
85

86
  private catchUpgradeRequest = (server: https.Server) => {
57✔
87
    if (!this.wsInternalSubscribed) {
2✔
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) => {
57✔
96
    if (this.shouldProxy(this.config.context, req)) {
4✔
97
      const activeProxyOptions = await this.prepareProxyRequest(req);
4✔
98
      this.proxy.ws(req, socket, head, activeProxyOptions);
4✔
99
      this.logger.info('[HPM] Upgrading to WebSocket');
4✔
100
    }
101
  };
102

103
  /**
104
   * Determine whether request should be proxied.
105
   *
106
   * @private
107
   * @param  {String} context [description]
108
   * @param  {Object} req     [description]
109
   * @return {Boolean}
110
   */
111
  private shouldProxy = (context, req: Request): boolean => {
57✔
112
    try {
50✔
113
      const path = req.originalUrl || req.url;
50✔
114
      return contextMatcher.match(context, path, req);
50✔
115
    } catch (error) {
116
      this.logger.error(error);
1✔
117
      return false;
1✔
118
    }
119
  };
120

121
  /**
122
   * Apply option.router and option.pathRewrite
123
   * Order matters:
124
   *    Router uses original path for routing;
125
   *    NOT the modified path, after it has been rewritten by pathRewrite
126
   * @param {Object} req
127
   * @return {Object} proxy options
128
   */
129
  private prepareProxyRequest = async (req: Request) => {
57✔
130
    // https://github.com/chimurai/http-proxy-middleware/issues/17
131
    // https://github.com/chimurai/http-proxy-middleware/issues/94
132
    req.url = req.originalUrl || req.url;
44✔
133

134
    // store uri before it gets rewritten for logging
135
    const originalPath = req.url;
44✔
136
    const newProxyOptions = Object.assign({}, this.proxyOptions);
44✔
137

138
    // Apply in order:
139
    // 1. option.router
140
    // 2. option.pathRewrite
141
    await this.applyRouter(req, newProxyOptions);
44✔
142
    await this.applyPathRewrite(req, this.pathRewriter);
43✔
143

144
    // debug logging for both http(s) and websockets
145
    if (this.proxyOptions.logLevel === 'debug') {
43!
146
      const arrow = getArrow(
×
147
        originalPath,
148
        req.url,
149
        this.proxyOptions.target,
150
        newProxyOptions.target
151
      );
152
      this.logger.debug(
×
153
        '[HPM] %s %s %s %s',
154
        req.method,
155
        originalPath,
156
        arrow,
157
        newProxyOptions.target
158
      );
159
    }
160

161
    return newProxyOptions;
43✔
162
  };
163

164
  // Modify option.target when router present.
165
  private applyRouter = async (req: Request, options) => {
57✔
166
    let newTarget;
167

168
    if (options.router) {
44✔
169
      newTarget = await Router.getTarget(req, options);
9✔
170

171
      if (newTarget) {
8✔
172
        this.logger.debug('[HPM] Router new target: %s -> "%s"', options.target, newTarget);
7✔
173
        options.target = newTarget;
7✔
174
      }
175
    }
176
  };
177

178
  // rewrite path
179
  private applyPathRewrite = async (req: Request, pathRewriter) => {
57✔
180
    if (pathRewriter) {
43✔
181
      const path = await pathRewriter(req.url, req);
10✔
182

183
      if (typeof path === 'string') {
10!
184
        req.url = path;
10✔
185
      } else {
186
        this.logger.info('[HPM] pathRewrite: No rewritten path found. (%s)', req.url);
×
187
      }
188
    }
189
  };
190

191
  private logError = (err, req: Request, res: Response, target?) => {
57✔
192
    const hostname = req.headers?.host || req.hostname || req.host; // (websocket) || (node0.10 || node 4/5)
3!
193
    const requestHref = `${hostname}${req.url}`;
3✔
194
    const targetHref = `${target?.href}`; // target is undefined when websocket errors
3!
195

196
    const errorMessage = '[HPM] Error occurred while proxying request %s to %s [%s] (%s)';
3✔
197
    const errReference = 'https://nodejs.org/api/errors.html#errors_common_system_errors'; // link to Node Common Systems Errors page
3✔
198

199
    this.logger.error(errorMessage, requestHref, targetHref, err.code || err, errReference);
3!
200
  };
201
}
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