Andrii Dokhniak 18541a6647 Switch to httptoolkit
Summary: switch to httptoolkit

Reviewers: kuba-orlik

Reviewed By: kuba-orlik

Differential Revision: https://hub.sealcode.org/D1578
2025-04-07 21:19:15 +02:00

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);
})();