Update ReverseProxy.js

Centralize request and response post processing
This commit is contained in:
2025-10-24 14:00:24 -07:00
parent 6b89480377
commit f1b76db430

View File

@@ -21,7 +21,6 @@ class ReverseProxy {
console.log(`Database opened with '${routeCount}' routes`); console.log(`Database opened with '${routeCount}' routes`);
// Bind to ports // Bind to ports
const bindedProcessReq = this.#ProcessRequest.bind(this);
const httpPort = 80, httpsPort = 443; const httpPort = 80, httpsPort = 443;
try { try {
this.HttpEndpoint = await this.#CreateHttpEndpoint(httpPort); this.HttpEndpoint = await this.#CreateHttpEndpoint(httpPort);
@@ -40,22 +39,7 @@ class ReverseProxy {
this.HttpEndpoint.on("upgrade", this.#HandleWebSocketUpgrade.bind(this)); this.HttpEndpoint.on("upgrade", this.#HandleWebSocketUpgrade.bind(this));
this.HttpsEndpoint.on("upgrade", this.#HandleWebSocketUpgrade.bind(this)); this.HttpsEndpoint.on("upgrade", this.#HandleWebSocketUpgrade.bind(this));
// This is for logging proxy results // This is for logging proxy results
this.ProxyService.on("proxyRes", (proxyResponse, request, response) => { this.ProxyService.on("proxyRes", this.#PostProcessResponse.bind(this));
// Check if there is an auth/access error
let internalStatus = response.statusCode;
if (response.statusCode == 511) {
if (IsNullOrUndefined(request.bailHandler)) {
response.writeHead(404, { "Content-Type": "text/plain" });
response.end(`You do not have the permissions to access '${host}', it's endpoint does not exist or the origin may be down`);
console.log(`[${internalStatus} as 404] ${request.method} ${hostname ?? request.headers?.host}${request.url}`);
proxyResponse.destroy();
return;
}
return request.bailHandler(internalStatus);
}
// Else let the response go through
console.log(`[${proxyResponse.statusCode}] ${request.method} '${request.headers?.host ?? ''}${request.url}'`);
});
// Start listening // Start listening
// except we already started listening when initializing the http/s endpoints // except we already started listening when initializing the http/s endpoints
@@ -63,7 +47,7 @@ class ReverseProxy {
} }
async #CreateHttpEndpoint(port = 80, { cancellationToken } = {}) { async #CreateHttpEndpoint(port = 80, { cancellationToken } = {}) {
let endpoint = CreateHttpListener(this.#ProcessRequest.bind(this)); let endpoint = CreateHttpListener(this.#HandleRequest.bind(this));
const raceConditions = [ const raceConditions = [
once(endpoint, "listening"), once(endpoint, "listening"),
once(endpoint, "error").then(([err]) => Promise.reject(err)), once(endpoint, "error").then(([err]) => Promise.reject(err)),
@@ -95,7 +79,7 @@ class ReverseProxy {
let endpoint = CreateHttpsListener({ let endpoint = CreateHttpsListener({
key: readFileSync(keyPath), key: readFileSync(keyPath),
cert: readFileSync(certPath) cert: readFileSync(certPath)
}, this.#ProcessRequest.bind(this)); }, this.#HandleRequest.bind(this));
// Subscribing to these events once before calling .listen // Subscribing to these events once before calling .listen
// to make sure they get called, even though they should be // to make sure they get called, even though they should be
@@ -147,7 +131,7 @@ class ReverseProxy {
return urlStr.substring(pathStartIndex, queryStartIndex); return urlStr.substring(pathStartIndex, queryStartIndex);
} }
#ProcessRequest(request, response) { #HandleRequest(request, response) {
if (ReverseProxy.IsInternalAddress(request.socket.remoteAddress)) { if (ReverseProxy.IsInternalAddress(request.socket.remoteAddress)) {
switch (ReverseProxy.GetPath(request.url)) { switch (ReverseProxy.GetPath(request.url)) {
case "/ready": case "/ready":
@@ -160,42 +144,78 @@ class ReverseProxy {
return; return;
} }
} }
// Regular
const { host } = request.headers;
const resolvedHost = this.TryResolveInternalHost(host);
const { hostname } = new URL(resolvedHost);
const _bail = (internalStatus = 404) => { const _bail = (internalStatus = 404) => {
response.writeHead(404, { "Content-Type": "text/plain" }); response.writeHead(404, { "Content-Type": "text/plain" });
response.end(`You do not have the permissions to access '${host}', it's endpoint does not exist or the origin may be down`); response.end(`You do not have the permissions to access '${request.originalHost}', it's endpoint does not exist or the origin may be down`);
console.log(`[${internalStatus} as 404] ${request.method} ${hostname ?? request.headers?.host}${request.url}`); console.log(`[${internalStatus} as 404] ${request.method} ${request.headers?.host ?? ''}${request.url}`);
}; };
if (IsNullOrUndefined(resolvedHost)) request = this.#PostProcessRequest(request, _bail);
return _bail(404); if (IsNullOrUndefined(request)) return;
try {
request.headers.host = hostname; this.ProxyService.web(request, response, { target: request.resolvedHost, xfwd: true }, proxyException => {
this.ProxyService.web(request, response, { target: resolvedHost, xfwd: true }, proxyException => { return _bail(502); // Bad Gateway: Error related to proxying
return _bail(502); // Bad Gateway: Error related to proxying });
});
} catch (proxyError) { return _bail(500); }
} }
#HandleWebSocketUpgrade(request, socket, head) { #HandleWebSocketUpgrade(request, socket, head) {
const { host } = request.headers;
const resolvedHost = this.TryResolveInternalHost(host);
const { hostname } = new URL(resolvedHost);
const _bail = (internalStatus = 404) => { const _bail = (internalStatus = 404) => {
console.log(`[${internalStatus} as 404] ${request.method}=>WS ${req.headers?.host ?? ''}${req.url}`); console.log(`[${internalStatus} as 404] ${request.method}=>WS ${request.headers?.host ?? ''}${req.url}`);
socket.write(`HTTP/1.1 404 Not Found\r\nYou do not have the permissions to access '${host}', it's endpoint does not exist or the origin may be down\r\n`); socket.write(`HTTP/1.1 404 Not Found\r\nYou do not have the permissions to access '${request.originalHost}', it's endpoint does not exist or the origin may be down\r\n`);
socket.destroy(); socket.destroy();
}; };
if (IsNullOrUndefined(resolvedHost)) request = this.#PostProcessRequest(request, _bail);
return _bail(404); if (IsNullOrUndefined(request)) return;
this.ProxyService.ws(request, socket, head, { target: request.resolvedHost, xfwd: true }, proxyException => {
return _bail(502); // Bad Gateway: Error related to proxying
});
}
#PostProcessRequest(request, bailHandler = undefined) {
const host = request.headers.host.trim();
if (IsNullOrUndefined(host) || host.length == 0) return bailHandler(412); // Preconditions Failed: Missing 'Host' header
const resolvedHost = this.TryResolveInternalHost(host);
if (IsNullOrUndefined(resolvedHost)) return bailHandler(404); // Not found: No server to proxy/forward to was found
let resolvedHostname;
try { try {
request.headers.host = hostname; // In the future I could make a path differ, so that I can forward
this.ProxyService.ws(Request, socket, head, { target: resolvedHost, xfwd: true }, proxyException => { // https://domain.com[/my/path] => http://internalHostname/sub[/my/path]
return _bail(502); resolvedHostname = new URL(resolvedHost).hostname.trim();
});
} catch (proxyError) { return _bail(500); } // Appropriate to add here, since throwing will cause a 500,
// since its the source of our data that caused the issue
if (IsNullOrUndefined(resolvedHostname)
|| resolvedHostname.length == 0
) throw new TypeError(`Malformed internal hostname for '${host}'`);
} catch { return bailHandler(500); } // Internal Server Error: Because data on server-side is messed up
// Lets do some forgery!! We want the internal server to know the hostname that it was accessed with,
// port is irrelevent in this case, so don't include it
request.originalHost = host;
request.resolvedHost = resolvedHost;
request.headers.host = resolvedHostname;
// Attach the bailHandler to the request, so proxyRes can call it in case it needs to
request.bailHandler = bailHandler;
// Request is ready to proxy, send it out
return request;
}
#PostProcessResponse(proxyResponse, request, response) {
// Check if there is an auth/access error
let internalStatus = response.statusCode;
if (response.statusCode == 511) {
if (IsNullOrUndefined(request.bailHandler)) {
response.writeHead(404, { "Content-Type": "text/plain" });
response.end(`You do not have the permissions to access '${host}', it's endpoint does not exist or the origin may be down`);
console.log(`[${internalStatus} as 404] ${request.method} ${hostname ?? request.headers?.host}${request.url}`);
proxyResponse.destroy();
return;
}
return request.bailHandler(internalStatus);
}
// Else let the response go through
console.log(`[${proxyResponse.statusCode}] ${request.method} '${request.headers?.host ?? ''}${request.url}'`);
} }
TryResolveInternalHost(publicHost = "") { TryResolveInternalHost(publicHost = "") {