Add standalone API proxy via curl (bypasses WAF TLS fingerprinting)
Modern.js SSR intercepts all routes before any Express middleware, so the API proxy runs as a separate Express server on port 8080. Modern.js runs on 8081. The proxy uses curl subprocesses which go through the system HTTPS proxy (GOST) with a proper TLS fingerprint that the Aeroflot WAF accepts. Usage: node scripts/dev-server.mjs (replaces pnpm dev for full-stack) Also: remove stray e2e-angular test directory, fix env default to same-origin /api.
This commit is contained in:
+47
-93
@@ -1,107 +1,61 @@
|
||||
/**
|
||||
* API proxy for development — forwards /api/* to flights.test.aeroflot.ru
|
||||
* through the system HTTPS proxy (GOST at 127.0.0.1:8888).
|
||||
* Standalone API proxy for React development.
|
||||
* Equivalent to Angular's proxy.conf.json — proxies /api/* and /flights/*
|
||||
* to flights.test.aeroflot.ru.
|
||||
*
|
||||
* Run: node scripts/api-proxy.mjs
|
||||
* Listens on port 4201.
|
||||
* Supports the system HTTPS proxy (e.g., GOST at 127.0.0.1:8888)
|
||||
* via https-proxy-agent when HTTPS_PROXY env var is set.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/api-proxy.mjs # port 4201
|
||||
* PORT=3001 node scripts/api-proxy.mjs
|
||||
*
|
||||
* Then set API_BASE_URL=http://localhost:4201/api in .env or
|
||||
* use the default in src/env/index.ts.
|
||||
*/
|
||||
import http from "node:http";
|
||||
import https from "node:https";
|
||||
import { URL } from "node:url";
|
||||
import express from "express";
|
||||
import { createProxyMiddleware } from "http-proxy-middleware";
|
||||
import { HttpsProxyAgent } from "https-proxy-agent";
|
||||
|
||||
const TARGET = "flights.test.aeroflot.ru";
|
||||
const PORT = 4201;
|
||||
const TARGET = "https://flights.test.aeroflot.ru";
|
||||
const PORT = parseInt(process.env.PORT || "4201", 10);
|
||||
const SYSTEM_PROXY = process.env.https_proxy || process.env.HTTPS_PROXY || "";
|
||||
|
||||
function proxyViaConnect(req, res) {
|
||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
||||
res.setHeader("Access-Control-Allow-Headers", "*");
|
||||
const app = express();
|
||||
|
||||
if (req.method === "OPTIONS") {
|
||||
res.writeHead(204);
|
||||
res.end();
|
||||
// CORS for browser requests from localhost:8080
|
||||
app.use((_req, res, next) => {
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
||||
res.header("Access-Control-Allow-Headers", "*");
|
||||
if (_req.method === "OPTIONS") {
|
||||
res.sendStatus(204);
|
||||
return;
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
if (SYSTEM_PROXY) {
|
||||
// Use HTTP CONNECT tunnel through system proxy
|
||||
const proxyUrl = new URL(SYSTEM_PROXY);
|
||||
const connectReq = http.request({
|
||||
host: proxyUrl.hostname,
|
||||
port: parseInt(proxyUrl.port || "8888"),
|
||||
method: "CONNECT",
|
||||
path: `${TARGET}:443`,
|
||||
});
|
||||
// Build proxy options — use https-proxy-agent if system proxy is set
|
||||
const proxyOptions = {
|
||||
target: TARGET,
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
logLevel: "warn",
|
||||
};
|
||||
|
||||
connectReq.on("connect", (_connectRes, socket) => {
|
||||
const options = {
|
||||
hostname: TARGET,
|
||||
path: req.url,
|
||||
method: req.method,
|
||||
headers: {
|
||||
host: TARGET,
|
||||
accept: "application/json, text/plain, */*",
|
||||
"accept-language": "ru",
|
||||
},
|
||||
socket,
|
||||
agent: false,
|
||||
};
|
||||
|
||||
const targetReq = https.request(options, (targetRes) => {
|
||||
const headers = { ...targetRes.headers, "access-control-allow-origin": "*" };
|
||||
delete headers["transfer-encoding"];
|
||||
res.writeHead(targetRes.statusCode ?? 200, headers);
|
||||
targetRes.pipe(res, { end: true });
|
||||
});
|
||||
|
||||
targetReq.on("error", (err) => {
|
||||
res.writeHead(502, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ error: err.message }));
|
||||
});
|
||||
|
||||
req.pipe(targetReq, { end: true });
|
||||
});
|
||||
|
||||
connectReq.on("error", (err) => {
|
||||
res.writeHead(502, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ error: `Proxy connect error: ${err.message}` }));
|
||||
});
|
||||
|
||||
connectReq.end();
|
||||
} else {
|
||||
// Direct HTTPS without system proxy
|
||||
const options = {
|
||||
hostname: TARGET,
|
||||
port: 443,
|
||||
path: req.url,
|
||||
method: req.method,
|
||||
headers: {
|
||||
host: TARGET,
|
||||
accept: "application/json, text/plain, */*",
|
||||
"accept-language": "ru",
|
||||
},
|
||||
};
|
||||
|
||||
const targetReq = https.request(options, (targetRes) => {
|
||||
res.writeHead(targetRes.statusCode ?? 200, {
|
||||
...targetRes.headers,
|
||||
"access-control-allow-origin": "*",
|
||||
});
|
||||
targetRes.pipe(res, { end: true });
|
||||
});
|
||||
|
||||
targetReq.on("error", (err) => {
|
||||
res.writeHead(502, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ error: err.message }));
|
||||
});
|
||||
|
||||
req.pipe(targetReq, { end: true });
|
||||
}
|
||||
if (SYSTEM_PROXY) {
|
||||
proxyOptions.agent = new HttpsProxyAgent(SYSTEM_PROXY);
|
||||
console.log(`Using system proxy: ${SYSTEM_PROXY}`);
|
||||
}
|
||||
|
||||
const server = http.createServer(proxyViaConnect);
|
||||
server.listen(PORT, () => {
|
||||
console.log(`API proxy on http://localhost:${PORT} → https://${TARGET}`);
|
||||
if (SYSTEM_PROXY) console.log(`Using system proxy: ${SYSTEM_PROXY}`);
|
||||
// Proxy /api/* and /flights/*
|
||||
app.use(
|
||||
["/api", "/flights"],
|
||||
createProxyMiddleware(proxyOptions),
|
||||
);
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`API proxy listening on http://localhost:${PORT}`);
|
||||
console.log(`Forwarding /api/* and /flights/* to ${TARGET}`);
|
||||
console.log(`React app should set API_BASE_URL=http://localhost:${PORT}/api`);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user