http-proxy-middleware.js 8.1 KB

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