Handle dev TrackerHub poll timeouts in proxy
This commit is contained in:
+33
-8
@@ -10,7 +10,7 @@
|
|||||||
* /* → localhost:8081 (Modern.js SSR + HMR)
|
* /* → localhost:8081 (Modern.js SSR + HMR)
|
||||||
*/
|
*/
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { createProxyMiddleware } from "http-proxy-middleware";
|
import { createProxyMiddleware, responseInterceptor } from "http-proxy-middleware";
|
||||||
import { HttpsProxyAgent } from "https-proxy-agent";
|
import { HttpsProxyAgent } from "https-proxy-agent";
|
||||||
import { execFile, spawn } from "node:child_process";
|
import { execFile, spawn } from "node:child_process";
|
||||||
import { resolve } from "node:path";
|
import { resolve } from "node:path";
|
||||||
@@ -159,6 +159,25 @@ function normalizeTrackerCookie(cookie) {
|
|||||||
return [...parts, "Path=/tracker/hub", "SameSite=Lax"].join("; ");
|
return [...parts, "Path=/tracker/hub", "SameSite=Lax"].join("; ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyTrackerCookieHeaders(proxyRes, res) {
|
||||||
|
const setCookie = proxyRes.headers["set-cookie"];
|
||||||
|
if (Array.isArray(setCookie)) {
|
||||||
|
res.setHeader("set-cookie", setCookie.map(normalizeTrackerCookie));
|
||||||
|
} else if (typeof setCookie === "string") {
|
||||||
|
res.setHeader("set-cookie", normalizeTrackerCookie(setCookie));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTrackerLongPollTimeout(proxyRes, req) {
|
||||||
|
const requestUrl = req.originalUrl ?? req.url ?? "";
|
||||||
|
return (
|
||||||
|
req.method === "GET" &&
|
||||||
|
(requestUrl.startsWith("/tracker/hub?id=") ||
|
||||||
|
requestUrl.startsWith("/hub?id=")) &&
|
||||||
|
proxyRes.statusCode === 504
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// --- SignalR TrackerHub proxy ---
|
// --- SignalR TrackerHub proxy ---
|
||||||
// Browser-direct localhost → platform.test.aeroflot.ru fails CORS. Keep the
|
// Browser-direct localhost → platform.test.aeroflot.ru fails CORS. Keep the
|
||||||
// hub same-origin in development and let proxy-helper / gost route the
|
// hub same-origin in development and let proxy-helper / gost route the
|
||||||
@@ -170,15 +189,21 @@ const trackerProxy = createProxyMiddleware({
|
|||||||
ws: true,
|
ws: true,
|
||||||
logLevel: "warn",
|
logLevel: "warn",
|
||||||
pathRewrite: (path) => `/tracker${path}`,
|
pathRewrite: (path) => `/tracker${path}`,
|
||||||
|
selfHandleResponse: true,
|
||||||
on: {
|
on: {
|
||||||
proxyRes(proxyRes) {
|
proxyRes: responseInterceptor(async (buffer, proxyRes, req, res) => {
|
||||||
const setCookie = proxyRes.headers["set-cookie"];
|
applyTrackerCookieHeaders(proxyRes, res);
|
||||||
if (Array.isArray(setCookie)) {
|
|
||||||
proxyRes.headers["set-cookie"] = setCookie.map(normalizeTrackerCookie);
|
if (isTrackerLongPollTimeout(proxyRes, req)) {
|
||||||
} else if (typeof setCookie === "string") {
|
console.warn(`Tracker long poll timed out upstream, returning empty 200 for ${req.originalUrl ?? req.url}`);
|
||||||
proxyRes.headers["set-cookie"] = normalizeTrackerCookie(setCookie);
|
res.statusCode = 200;
|
||||||
|
res.statusMessage = "OK";
|
||||||
|
res.setHeader("content-type", "text/plain");
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
return buffer;
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
...(SYSTEM_PROXY ? { agent: new HttpsProxyAgent(SYSTEM_PROXY) } : {}),
|
...(SYSTEM_PROXY ? { agent: new HttpsProxyAgent(SYSTEM_PROXY) } : {}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -224,8 +224,6 @@ export function _resetSharedConnections(): void {
|
|||||||
|
|
||||||
// ---- Default builder (dynamic import) ----
|
// ---- Default builder (dynamic import) ----
|
||||||
|
|
||||||
const DEV_TRACKER_LONG_POLL_TIMEOUT_MS = 10000;
|
|
||||||
|
|
||||||
export function _shouldUseLongPollingForDevTrackerProxy(url: string): boolean {
|
export function _shouldUseLongPollingForDevTrackerProxy(url: string): boolean {
|
||||||
try {
|
try {
|
||||||
const parsed = new URL(url, "http://localhost");
|
const parsed = new URL(url, "http://localhost");
|
||||||
@@ -243,39 +241,15 @@ async function defaultBuildConnection(
|
|||||||
url: string,
|
url: string,
|
||||||
delays: number[],
|
delays: number[],
|
||||||
): Promise<HubConnectionLike> {
|
): Promise<HubConnectionLike> {
|
||||||
const {
|
const { HubConnectionBuilder, HttpTransportType, LogLevel } = await import(
|
||||||
DefaultHttpClient,
|
"@microsoft/signalr"
|
||||||
HttpClient,
|
);
|
||||||
HubConnectionBuilder,
|
|
||||||
HttpTransportType,
|
|
||||||
LogLevel,
|
|
||||||
NullLogger,
|
|
||||||
} = await import("@microsoft/signalr");
|
|
||||||
const builder = new HubConnectionBuilder();
|
const builder = new HubConnectionBuilder();
|
||||||
const useDevTrackerLongPolling = _shouldUseLongPollingForDevTrackerProxy(url);
|
const withUrlOptions = _shouldUseLongPollingForDevTrackerProxy(url)
|
||||||
const withUrlOptions = useDevTrackerLongPolling
|
? { transport: HttpTransportType.LongPolling }
|
||||||
? {
|
|
||||||
transport: HttpTransportType.LongPolling,
|
|
||||||
httpClient: new (class extends HttpClient {
|
|
||||||
private readonly inner = new DefaultHttpClient(NullLogger.instance);
|
|
||||||
|
|
||||||
send(request: Parameters<InstanceType<typeof HttpClient>["send"]>[0]) {
|
|
||||||
if (request.method !== "GET") {
|
|
||||||
return this.inner.send(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.inner.send({
|
|
||||||
...request,
|
|
||||||
timeout: DEV_TRACKER_LONG_POLL_TIMEOUT_MS,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})(),
|
|
||||||
}
|
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
return (withUrlOptions
|
return (withUrlOptions ? builder.withUrl(url, withUrlOptions) : builder.withUrl(url))
|
||||||
? builder.withUrl(url, withUrlOptions)
|
|
||||||
: builder.withUrl(url))
|
|
||||||
.withAutomaticReconnect(delays)
|
.withAutomaticReconnect(delays)
|
||||||
// Suppress SignalR's internal console logging. The hub is optional
|
// Suppress SignalR's internal console logging. The hub is optional
|
||||||
// (failures degrade to polling) and we already handle status via
|
// (failures degrade to polling) and we already handle status via
|
||||||
|
|||||||
Reference in New Issue
Block a user