http-proxy-3
Version:
Modern rewrite of http-proxy
236 lines (235 loc) • 9.94 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProxyServer = void 0;
const http = __importStar(require("node:http"));
const http2 = __importStar(require("node:http2"));
const web_incoming_1 = require("./passes/web-incoming");
const ws_incoming_1 = require("./passes/ws-incoming");
const node_events_1 = require("node:events");
const debug_1 = __importDefault(require("debug"));
const common_1 = require("./common");
const log = (0, debug_1.default)("http-proxy-3");
class ProxyServer extends node_events_1.EventEmitter {
/**
* Creates the proxy server with specified options.
* @param options - Config object passed to the proxy
*/
constructor(options = {}) {
super();
// createRightProxy - Returns a function that when called creates the loader for
// either `ws` or `web`'s passes.
this.createRightProxy = (type) => {
log("createRightProxy", { type });
return (options) => {
return (...args /* req, res, [head], [opts] */) => {
const req = args[0];
log("proxy: ", { type, path: req.url });
const res = args[1];
const passes = type === "ws" ? this.wsPasses : this.webPasses;
if (type == "ws") {
// socket -- proxy websocket errors to our error handler;
// see https://github.com/sagemathinc/http-proxy-3/issues/5
// NOTE: as mentioned below, res is the socket in this case.
// One of the passes does add an error handler, but there's no
// guarantee we even get to that pass before something bad happens,
// and there's no way for a user of http-proxy-3 to get ahold
// of this res object and attach their own error handler until
// after the passes. So we better attach one ASAP right here:
res.on("error", (err) => {
this.emit("error", err, req, res);
});
}
let counter = args.length - 1;
let head;
let cb;
// optional args parse begin
if (typeof args[counter] === "function") {
cb = args[counter];
counter--;
}
let requestOptions;
if (!(args[counter] instanceof Buffer) && args[counter] !== res) {
// Copy global options, and overwrite with request options
requestOptions = { ...options, ...args[counter] };
counter--;
}
else {
requestOptions = { ...options };
}
if (args[counter] instanceof Buffer) {
head = args[counter];
}
for (const e of ["target", "forward"]) {
if (typeof requestOptions[e] === "string") {
requestOptions[e] = (0, common_1.toURL)(requestOptions[e]);
}
}
if (!requestOptions.target && !requestOptions.forward) {
this.emit("error", new Error("Must set target or forward"), req, res);
return;
}
for (const pass of passes) {
/**
* Call of passes functions
* pass(req, res, options, head)
*
* In WebSockets case, the `res` variable
* refer to the connection socket
* pass(req, socket, options, head)
*/
if (pass(req, res, requestOptions, head, this, cb)) {
// passes can return a truthy value to halt the loop
break;
}
}
};
};
};
this.onError = (err) => {
// Force people to handle their own errors
if (this.listeners("error").length === 1) {
throw err;
}
};
/**
* A function that wraps the object in a webserver, for your convenience
* @param port - Port to listen on
* @param hostname - The hostname to listen on
*/
this.listen = (port, hostname) => {
log("listen", { port, hostname });
const requestListener = (req, res) => {
this.web(req, res);
};
this._server = this.options.ssl
? http2.createSecureServer({ ...this.options.ssl, allowHTTP1: true }, requestListener)
: http.createServer(requestListener);
if (this.options.ws) {
this._server.on("upgrade", (req, socket, head) => {
this.ws(req, socket, head);
});
}
this._server.listen(port, hostname);
return this;
};
// if the proxy started its own http server, this is the address of that server.
this.address = () => {
return this._server?.address();
};
/**
* A function that closes the inner webserver and stops listening on given port
*/
this.close = (cb) => {
if (this._server == null) {
cb?.();
return;
}
// Wrap cb anb nullify server after all open connections are closed.
this._server.close((err) => {
this._server = null;
cb?.(err);
});
};
this.before = (type, passName, cb) => {
if (type !== "ws" && type !== "web") {
throw new Error("type must be `web` or `ws`");
}
const passes = (type === "ws" ? this.wsPasses : this.webPasses);
let i = false;
passes.forEach((v, idx) => {
if (v.name === passName) {
i = idx;
}
});
if (i === false) {
throw new Error("No such pass");
}
passes.splice(i, 0, cb);
};
this.after = (type, passName, cb) => {
if (type !== "ws" && type !== "web") {
throw new Error("type must be `web` or `ws`");
}
const passes = (type === "ws" ? this.wsPasses : this.webPasses);
let i = false;
passes.forEach((v, idx) => {
if (v.name === passName) {
i = idx;
}
});
if (i === false) {
throw new Error("No such pass");
}
passes.splice(i++, 0, cb);
};
log("creating a ProxyServer", options);
options.prependPath = options.prependPath !== false;
this.options = options;
this.web = this.createRightProxy("web")(options);
this.ws = this.createRightProxy("ws")(options);
this.webPasses = Object.values(web_incoming_1.WEB_PASSES);
this.wsPasses = Object.values(ws_incoming_1.WS_PASSES);
this.on("error", this.onError);
}
/**
* Creates the proxy server with specified options.
* @param options Config object passed to the proxy
* @returns Proxy object with handlers for `ws` and `web` requests
*/
static createProxyServer(options) {
return new ProxyServer(options);
}
/**
* Creates the proxy server with specified options.
* @param options Config object passed to the proxy
* @returns Proxy object with handlers for `ws` and `web` requests
*/
static createServer(options) {
return new ProxyServer(options);
}
/**
* Creates the proxy server with specified options.
* @param options Config object passed to the proxy
* @returns Proxy object with handlers for `ws` and `web` requests
*/
static createProxy(options) {
return new ProxyServer(options);
}
}
exports.ProxyServer = ProxyServer;