mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-06 20:51:36 +02:00
support skip tls per request
This commit is contained in:
@@ -0,0 +1,68 @@
|
|||||||
|
import http from "node:http";
|
||||||
|
import https from "node:https";
|
||||||
|
import { Readable } from "node:stream";
|
||||||
|
|
||||||
|
type UpstreamFetchInit = {
|
||||||
|
method: string;
|
||||||
|
headers: Record<string, string>;
|
||||||
|
body?: string;
|
||||||
|
skipTlsVerification?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const insecureHttpsAgent = new https.Agent({
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
keepAlive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
export function aiGatewayUpstreamFetch(
|
||||||
|
url: string,
|
||||||
|
init: UpstreamFetchInit
|
||||||
|
): Promise<Response> {
|
||||||
|
const parsed = new URL(url);
|
||||||
|
const isHttps = parsed.protocol === "https:";
|
||||||
|
const lib = isHttps ? https : http;
|
||||||
|
const agent =
|
||||||
|
isHttps && init.skipTlsVerification ? insecureHttpsAgent : undefined;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const req = lib.request(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
method: init.method,
|
||||||
|
headers: init.headers,
|
||||||
|
agent
|
||||||
|
},
|
||||||
|
(res) => {
|
||||||
|
const headers = new Headers();
|
||||||
|
for (const [key, value] of Object.entries(res.headers)) {
|
||||||
|
if (value === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
for (const entry of value) {
|
||||||
|
headers.append(key, entry);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
headers.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = Readable.toWeb(res) as ReadableStream<Uint8Array>;
|
||||||
|
resolve(
|
||||||
|
new Response(body, {
|
||||||
|
status: res.statusCode ?? 502,
|
||||||
|
statusText: res.statusMessage,
|
||||||
|
headers
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
req.on("error", reject);
|
||||||
|
|
||||||
|
if (init.body !== undefined) {
|
||||||
|
req.write(init.body);
|
||||||
|
}
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@ import { localCache } from "@server/lib/cache";
|
|||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import type { ModelAccessMode } from "@server/lib/aiInferenceResource";
|
import type { ModelAccessMode } from "@server/lib/aiInferenceResource";
|
||||||
|
import { aiGatewayUpstreamFetch } from "@server/lib/aiGatewayUpstreamFetch";
|
||||||
|
|
||||||
// Short-lived local caches so a burst of requests from the same IP/user
|
// Short-lived local caches so a burst of requests from the same IP/user
|
||||||
// doesn't hit the database on every single request. None of this is
|
// doesn't hit the database on every single request. None of this is
|
||||||
@@ -544,15 +545,6 @@ export async function handleAiGatewayProxy(
|
|||||||
);
|
);
|
||||||
applyAiProviderAuthHeaders(headers, authType, apiKey);
|
applyAiProviderAuthHeaders(headers, authType, apiKey);
|
||||||
|
|
||||||
// No dedicated per-request TLS agent is wired up (no extra deps for
|
|
||||||
// this v1 gateway) - toggle the process-wide Node TLS check instead.
|
|
||||||
// Known limitation: this is not safe under concurrent requests mixing
|
|
||||||
// skipTlsVerification providers with strict ones.
|
|
||||||
const restoreTlsReject = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
|
|
||||||
if (provider.skipTlsVerification) {
|
|
||||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = JSON.stringify(req.body);
|
const body = JSON.stringify(req.body);
|
||||||
|
|
||||||
logger.debug("AI gateway upstream request", {
|
logger.debug("AI gateway upstream request", {
|
||||||
@@ -560,15 +552,17 @@ export async function handleAiGatewayProxy(
|
|||||||
url: targetUrl,
|
url: targetUrl,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers,
|
headers,
|
||||||
body: req.body
|
body: req.body,
|
||||||
|
skipTlsVerification: provider.skipTlsVerification
|
||||||
});
|
});
|
||||||
|
|
||||||
let upstreamRes: globalThis.Response;
|
let upstreamRes: globalThis.Response;
|
||||||
try {
|
try {
|
||||||
upstreamRes = await fetch(targetUrl, {
|
upstreamRes = await aiGatewayUpstreamFetch(targetUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers,
|
headers,
|
||||||
body
|
body,
|
||||||
|
skipTlsVerification: provider.skipTlsVerification
|
||||||
});
|
});
|
||||||
} catch (fetchError) {
|
} catch (fetchError) {
|
||||||
logger.error({
|
logger.error({
|
||||||
@@ -581,14 +575,6 @@ export async function handleAiGatewayProxy(
|
|||||||
: undefined
|
: undefined
|
||||||
});
|
});
|
||||||
throw fetchError;
|
throw fetchError;
|
||||||
} finally {
|
|
||||||
if (provider.skipTlsVerification) {
|
|
||||||
if (restoreTlsReject === undefined) {
|
|
||||||
delete process.env.NODE_TLS_REJECT_UNAUTHORIZED;
|
|
||||||
} else {
|
|
||||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = restoreTlsReject;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentType = upstreamRes.headers.get("content-type") || "";
|
const contentType = upstreamRes.headers.get("content-type") || "";
|
||||||
|
|||||||
Reference in New Issue
Block a user