Summary: switch to httptoolkit Reviewers: kuba-orlik Reviewed By: kuba-orlik Differential Revision: https://hub.sealcode.org/D1578
189 lines
5.2 KiB
TypeScript
189 lines
5.2 KiB
TypeScript
import { getLocal } from "mockttp";
|
|
import * as WebSock from "ws";
|
|
|
|
const mockServer = getLocal();
|
|
|
|
const wait_for_open = (socket: WebSock) => {
|
|
if (socket.readyState === socket.OPEN) return;
|
|
return new Promise((resolve, reject) => {
|
|
const maxNumberOfAttempts = 20;
|
|
const intervalTime = 100; //ms
|
|
|
|
let currentAttempt = 0;
|
|
const interval = setInterval(() => {
|
|
if (currentAttempt > maxNumberOfAttempts - 1) {
|
|
clearInterval(interval);
|
|
reject(new Error("Maximum number of attempts exceeded"));
|
|
} else if (socket.readyState === socket.OPEN) {
|
|
clearInterval(interval);
|
|
resolve(undefined);
|
|
} else if (socket.readyState == socket.CLOSED) {
|
|
console.log(socket.url);
|
|
reject(new Error("The server socket closed"));
|
|
}
|
|
currentAttempt++;
|
|
}, intervalTime);
|
|
});
|
|
};
|
|
|
|
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
function waitforhost(
|
|
url: string,
|
|
expected_status = 200,
|
|
interval = 1000,
|
|
attempts = 10
|
|
): Promise<void> {
|
|
let count = 1;
|
|
|
|
return new Promise(async (resolve, reject) => {
|
|
while (count < attempts) {
|
|
await sleep(interval);
|
|
|
|
try {
|
|
const response = await fetch(url);
|
|
if (response.status === expected_status) {
|
|
resolve();
|
|
break;
|
|
} else {
|
|
count++;
|
|
}
|
|
} catch {
|
|
count++;
|
|
console.log(`Still down, trying ${count} of ${attempts}`);
|
|
}
|
|
}
|
|
|
|
reject(new Error(`Server is down: ${count} attempts tried`));
|
|
});
|
|
}
|
|
|
|
(async () => {
|
|
const proxy_port = process.env.PROXY_PORT;
|
|
const server_port = process.env.SERVER_PORT;
|
|
const loopback_port = process.env.LOOPBACK_PORT; // internal only
|
|
const monitoring_api_port = process.env.MONITORING_API_PORT;
|
|
if (!proxy_port || !server_port || !monitoring_api_port || !loopback_port) {
|
|
throw Error("Configuration env variables not set");
|
|
}
|
|
|
|
// Expect 403 forbidden
|
|
await waitforhost("http://127.0.0.1:" + server_port, 403);
|
|
mockServer.start(+proxy_port);
|
|
|
|
let cached_resp: undefined | string = undefined;
|
|
|
|
const cors_headers = {
|
|
"access-control-allow-origin": "*",
|
|
"access-control-allow-methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
"access-control-allow-headers": "Content-Type, Authorization",
|
|
"access-control-max-age": "86400",
|
|
"access-control-allow-credentials": "true",
|
|
Origin: "http://127.0.0.1:8080", // Trick the server into thinking that it is accessed through localhost
|
|
};
|
|
await mockServer.forOptions().thenReply(200, "", cors_headers);
|
|
|
|
await mockServer.forPost("/start").thenPassThrough({
|
|
beforeRequest: async (req) => {
|
|
if (cached_resp) {
|
|
return {
|
|
response: { body: cached_resp, headers: cors_headers },
|
|
};
|
|
}
|
|
// forward the request to the real server
|
|
return {
|
|
url: req.url,
|
|
headers: req.headers,
|
|
body: await req.body.getDecodedBuffer(),
|
|
};
|
|
},
|
|
beforeResponse: async (resp, _req) => {
|
|
// cache the response
|
|
cached_resp = (await resp.body.getText()) as string;
|
|
// set the cors headers, overwriting the prohibitive ones
|
|
return { headers: { ...resp.headers, ...cors_headers } };
|
|
},
|
|
});
|
|
|
|
await mockServer.forPost("/stop").thenReply(200);
|
|
|
|
// forward everything to the real server
|
|
await mockServer.forUnmatchedRequest().thenPassThrough({
|
|
beforeRequest: async (req) => {
|
|
return { url: req.url };
|
|
},
|
|
beforeResponse: async (resp, _req) => {
|
|
return { headers: { ...resp.headers, ...cors_headers } };
|
|
},
|
|
});
|
|
|
|
await mockServer
|
|
.forAnyWebSocket()
|
|
.thenForwardTo("ws://127.0.0.1:" + loopback_port);
|
|
|
|
let wss = new WebSock.Server({ port: +loopback_port });
|
|
|
|
// this is append only
|
|
let httptoolkit_server_messages: String[] = [];
|
|
|
|
let api_server = new WebSock.Server({ port: +monitoring_api_port });
|
|
|
|
api_server.on("connection", (ws) => {
|
|
let curr_idx_to_send = 0;
|
|
const interval = setInterval(() => {
|
|
while (httptoolkit_server_messages[curr_idx_to_send] != undefined) {
|
|
if (ws.readyState != ws.OPEN) {
|
|
clearInterval(interval);
|
|
ws.close();
|
|
break;
|
|
}
|
|
ws.send(httptoolkit_server_messages[curr_idx_to_send]);
|
|
curr_idx_to_send++;
|
|
}
|
|
}, 100);
|
|
});
|
|
wss.on("connection", (ws, req) => {
|
|
let server_session_url = "ws://127.0.0.1:" + server_port + req.url;
|
|
let httptoolkit_server_session: WebSock;
|
|
console.log("ws connection:", ws.protocol, typeof ws.protocol);
|
|
if (ws.protocol) {
|
|
httptoolkit_server_session = new WebSock(
|
|
server_session_url,
|
|
ws.protocol,
|
|
{ joinDuplicateHeaders: true, headers: req.headers }
|
|
);
|
|
} else {
|
|
httptoolkit_server_session = new WebSock(server_session_url, {
|
|
joinDuplicateHeaders: true,
|
|
headers: req.headers,
|
|
});
|
|
}
|
|
httptoolkit_server_session.onerror = (_) => {
|
|
ws.close();
|
|
httptoolkit_server_session.close();
|
|
return;
|
|
};
|
|
httptoolkit_server_session.onopen = () => {
|
|
console.log("ws connected to the server");
|
|
};
|
|
|
|
httptoolkit_server_session.onmessage = (msg) => {
|
|
httptoolkit_server_messages.push(msg.data.toString());
|
|
ws.send(msg.data);
|
|
};
|
|
|
|
// Listening for messages from the client
|
|
ws.on("message", async (message) => {
|
|
await wait_for_open(httptoolkit_server_session);
|
|
httptoolkit_server_session.send(message.toString());
|
|
});
|
|
|
|
// Handling client disconnection
|
|
ws.on("close", () => {
|
|
httptoolkit_server_session.close();
|
|
});
|
|
});
|
|
|
|
console.log("WS Proxy started");
|
|
console.log(mockServer.proxyEnv);
|
|
})();
|