siegel
Version:
Web application development ecosystem
112 lines (111 loc) • 4.5 kB
JavaScript
import http from 'http';
import https from 'https';
import isEmptyObject from '../../../common/is/empty_obj/index.js';
import populateURLParams from '../../../common/populate_url_params/index.js';
const createHttpHeader = (line, headers) => (Object.keys(headers)
.reduce((head, key) => {
const value = headers[key];
if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
head.push(`${key}: ${value[i]}`);
}
}
else
head.push(`${key}: ${value}`);
return head;
}, [line])
.join('\r\n') + '\r\n\r\n');
function updateSocket(socket, head) {
socket.setTimeout(0);
socket.setNoDelay(true);
socket.setKeepAlive(true, 0);
head?.length && socket.unshift(head);
}
function getProxyRequestOptions(proxyParams, clientReq, isWS) {
const { host, port, changeOrigin, postProcessReq, secure } = proxyParams;
const { url, method, headers } = clientReq;
let finalPath;
if (isWS)
finalPath = url;
else {
const { query, params, path } = clientReq;
const proxyQuery = proxyParams.query || query;
const proxyPath = proxyParams.path || path;
finalPath = isEmptyObject(proxyQuery)
? proxyPath
: `${proxyPath}?${new URLSearchParams(proxyQuery)}`;
params && (finalPath = populateURLParams(finalPath, params));
}
const proxyReqOptions = {
host, method, headers,
port: !port && secure ? 443 : port,
path: finalPath
};
if (changeOrigin) {
proxyReqOptions.headers.host = host;
port && (proxyReqOptions.headers.host += `:${port}`);
}
postProcessReq?.(clientReq, proxyReqOptions);
return proxyReqOptions;
}
const PROXY_WS_SUBSCRIPTIONS = {};
const proxy = proxyParams => {
const { secure, ws, wsEndpoints, host, port } = proxyParams;
const client = secure ? https : http;
const proxyId = `${host}:${port}`;
const wsEndpointsSet = wsEndpoints && new Set(wsEndpoints);
const proxyRequest = (clientReq, clientRes) => {
const { body, socket } = clientReq;
if (ws && !PROXY_WS_SUBSCRIPTIONS[proxyId]) {
socket.server
.addListener('upgrade', (req, socket, head) => {
const { url, method, headers } = req;
let isAllowed = true;
if (wsEndpointsSet) {
const endpointIndexOfQuery = url.indexOf('?');
const endpoint = endpointIndexOfQuery > -1
? url.substring(0, endpointIndexOfQuery)
: url;
isAllowed = wsEndpointsSet?.has(endpoint);
}
if (isAllowed) {
if (method !== 'GET' || headers.upgrade?.toLowerCase() != 'websocket') {
socket.destroy();
return;
}
updateSocket(socket, head);
client.request(getProxyRequestOptions(proxyParams, req, true))
.on('upgrade', (pRes, pSocket, pHead) => {
updateSocket(pSocket, pHead);
socket.write(createHttpHeader('HTTP/1.1 101 Switching Protocols', pRes.headers));
pSocket.pipe(socket).pipe(pSocket);
})
.on('response', res => {
const { headers, httpVersion, statusCode, statusMessage } = res;
socket.write(createHttpHeader(`HTTP/${httpVersion} ${statusCode} ${statusMessage}`, headers));
res.pipe(socket);
})
.on('error', console.error)
.end();
}
});
PROXY_WS_SUBSCRIPTIONS[proxyId] = true;
}
const proxyReq = client.request(getProxyRequestOptions(proxyParams, clientReq), proxyRes => {
const { statusCode, headers } = proxyRes;
if (statusCode !== 200) {
console.error(clientReq.path, statusCode);
proxyRes.resume();
}
clientRes.writeHead(statusCode, headers);
proxyRes.pipe(clientRes);
});
body && proxyReq.write(body.constructor == Object
? JSON.stringify(body)
: body);
proxyReq.on('error', console.error);
clientReq.pipe(proxyReq);
};
return proxyRequest;
};
export default proxy;