http-proxy-middleware.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.HttpProxyMiddleware = void 0;
  4. const httpProxy = require("http-proxy");
  5. const config_factory_1 = require("./config-factory");
  6. const contextMatcher = require("./context-matcher");
  7. const handlers = require("./_handlers");
  8. const logger_1 = require("./logger");
  9. const PathRewriter = require("./path-rewriter");
  10. const Router = require("./router");
  11. class HttpProxyMiddleware {
  12. constructor(context, opts) {
  13. this.logger = (0, logger_1.getInstance)();
  14. this.wsInternalSubscribed = false;
  15. this.serverOnCloseSubscribed = false;
  16. // https://github.com/Microsoft/TypeScript/wiki/'this'-in-TypeScript#red-flags-for-this
  17. this.middleware = async (req, res, next) => {
  18. var _a, _b;
  19. if (this.shouldProxy(this.config.context, req)) {
  20. try {
  21. const activeProxyOptions = await this.prepareProxyRequest(req);
  22. this.proxy.web(req, res, activeProxyOptions);
  23. }
  24. catch (err) {
  25. next(err);
  26. }
  27. }
  28. else {
  29. next();
  30. }
  31. /**
  32. * Get the server object to subscribe to server events;
  33. * 'upgrade' for websocket and 'close' for graceful shutdown
  34. *
  35. * NOTE:
  36. * req.socket: node >= 13
  37. * req.connection: node < 13 (Remove this when node 12/13 support is dropped)
  38. */
  39. const server = (_b = ((_a = req.socket) !== null && _a !== void 0 ? _a : req.connection)) === null || _b === void 0 ? void 0 : _b.server;
  40. if (server && !this.serverOnCloseSubscribed) {
  41. server.on('close', () => {
  42. this.logger.info('[HPM] server close signal received: closing proxy server');
  43. this.proxy.close();
  44. });
  45. this.serverOnCloseSubscribed = true;
  46. }
  47. if (this.proxyOptions.ws === true) {
  48. // use initial request to access the server object to subscribe to http upgrade event
  49. this.catchUpgradeRequest(server);
  50. }
  51. };
  52. this.catchUpgradeRequest = (server) => {
  53. if (!this.wsInternalSubscribed) {
  54. server.on('upgrade', this.handleUpgrade);
  55. // prevent duplicate upgrade handling;
  56. // in case external upgrade is also configured
  57. this.wsInternalSubscribed = true;
  58. }
  59. };
  60. this.handleUpgrade = async (req, socket, head) => {
  61. if (this.shouldProxy(this.config.context, req)) {
  62. const activeProxyOptions = await this.prepareProxyRequest(req);
  63. this.proxy.ws(req, socket, head, activeProxyOptions);
  64. this.logger.info('[HPM] Upgrading to WebSocket');
  65. }
  66. };
  67. /**
  68. * Determine whether request should be proxied.
  69. *
  70. * @private
  71. * @param {String} context [description]
  72. * @param {Object} req [description]
  73. * @return {Boolean}
  74. */
  75. this.shouldProxy = (context, req) => {
  76. try {
  77. const path = req.originalUrl || req.url;
  78. return contextMatcher.match(context, path, req);
  79. }
  80. catch (error) {
  81. this.logger.error(error);
  82. return false;
  83. }
  84. };
  85. /**
  86. * Apply option.router and option.pathRewrite
  87. * Order matters:
  88. * Router uses original path for routing;
  89. * NOT the modified path, after it has been rewritten by pathRewrite
  90. * @param {Object} req
  91. * @return {Object} proxy options
  92. */
  93. this.prepareProxyRequest = async (req) => {
  94. // https://github.com/chimurai/http-proxy-middleware/issues/17
  95. // https://github.com/chimurai/http-proxy-middleware/issues/94
  96. req.url = req.originalUrl || req.url;
  97. // store uri before it gets rewritten for logging
  98. const originalPath = req.url;
  99. const newProxyOptions = Object.assign({}, this.proxyOptions);
  100. // Apply in order:
  101. // 1. option.router
  102. // 2. option.pathRewrite
  103. await this.applyRouter(req, newProxyOptions);
  104. await this.applyPathRewrite(req, this.pathRewriter);
  105. // debug logging for both http(s) and websockets
  106. if (this.proxyOptions.logLevel === 'debug') {
  107. const arrow = (0, logger_1.getArrow)(originalPath, req.url, this.proxyOptions.target, newProxyOptions.target);
  108. this.logger.debug('[HPM] %s %s %s %s', req.method, originalPath, arrow, newProxyOptions.target);
  109. }
  110. return newProxyOptions;
  111. };
  112. // Modify option.target when router present.
  113. this.applyRouter = async (req, options) => {
  114. let newTarget;
  115. if (options.router) {
  116. newTarget = await Router.getTarget(req, options);
  117. if (newTarget) {
  118. this.logger.debug('[HPM] Router new target: %s -> "%s"', options.target, newTarget);
  119. options.target = newTarget;
  120. }
  121. }
  122. };
  123. // rewrite path
  124. this.applyPathRewrite = async (req, pathRewriter) => {
  125. if (pathRewriter) {
  126. const path = await pathRewriter(req.url, req);
  127. if (typeof path === 'string') {
  128. req.url = path;
  129. }
  130. else {
  131. this.logger.info('[HPM] pathRewrite: No rewritten path found. (%s)', req.url);
  132. }
  133. }
  134. };
  135. this.logError = (err, req, res, target) => {
  136. var _a;
  137. const hostname = ((_a = req.headers) === null || _a === void 0 ? void 0 : _a.host) || req.hostname || req.host; // (websocket) || (node0.10 || node 4/5)
  138. const requestHref = `${hostname}${req.url}`;
  139. const targetHref = `${target === null || target === void 0 ? void 0 : target.href}`; // target is undefined when websocket errors
  140. const errorMessage = '[HPM] Error occurred while proxying request %s to %s [%s] (%s)';
  141. const errReference = 'https://nodejs.org/api/errors.html#errors_common_system_errors'; // link to Node Common Systems Errors page
  142. this.logger.error(errorMessage, requestHref, targetHref, err.code || err, errReference);
  143. };
  144. this.config = (0, config_factory_1.createConfig)(context, opts);
  145. this.proxyOptions = this.config.options;
  146. // create proxy
  147. this.proxy = httpProxy.createProxyServer({});
  148. this.logger.info(`[HPM] Proxy created: ${this.config.context} -> ${this.proxyOptions.target}`);
  149. this.pathRewriter = PathRewriter.createPathRewriter(this.proxyOptions.pathRewrite); // returns undefined when "pathRewrite" is not provided
  150. // attach handler to http-proxy events
  151. handlers.init(this.proxy, this.proxyOptions);
  152. // log errors for debug purpose
  153. this.proxy.on('error', this.logError);
  154. // https://github.com/chimurai/http-proxy-middleware/issues/19
  155. // expose function to upgrade externally
  156. this.middleware.upgrade = (req, socket, head) => {
  157. if (!this.wsInternalSubscribed) {
  158. this.handleUpgrade(req, socket, head);
  159. }
  160. };
  161. }
  162. }
  163. exports.HttpProxyMiddleware = HttpProxyMiddleware;