Files
dify/knowledge-fs/tools/swagger/server.mjs
Jyong 4ee43b8afc chore: migrate knowledge-fs source tree
Import the committed KnowledgeFS snapshot dc4072ee302317145612087ce7440851dc329fd0 under knowledge-fs/ without its Git history, local IDE settings, or build artifacts.
2026-07-20 04:54:20 -04:00

139 lines
4.4 KiB
JavaScript

#!/usr/bin/env node
// Standalone, dependency-free Swagger UI for the Knowledge Gateway.
//
// It is a thin same-origin reverse proxy: it serves a Swagger UI shell at `/`
// (assets loaded from the jsDelivr CDN) and forwards every other request to the
// running gateway. Same-origin delivery means the browser fetches `/openapi.json`
// and runs "Try it out" against the proxy host, so it works even though the
// gateway exposes no CORS headers.
//
// This tool lives outside the pnpm workspace on purpose: it is dev-only, ships no
// runtime dependency, and must never become part of the API surface.
//
// Usage:
// pnpm swagger # serve on :8088, proxy -> :8788
// KFS_API=http://localhost:9000 \
// KFS_SWAGGER_PORT=9090 pnpm swagger # override target / port
import http from "node:http";
import { pathToFileURL } from "node:url";
const DEFAULT_API_BASE = process.env.KFS_API ?? "http://localhost:8788";
const DEFAULT_PORT = Number.parseInt(process.env.KFS_SWAGGER_PORT ?? "8088", 10);
// Pinned Swagger UI release. 5.x is required to render the gateway's
// OpenAPI 3.1 document (3.0-only builds fail with "Unable to render").
const SWAGGER_UI_VERSION = "5.30.2";
const CDN_BASE = `https://cdn.jsdelivr.net/npm/swagger-ui-dist@${SWAGGER_UI_VERSION}`;
// Per-connection headers that must not be relayed across a proxy hop.
const HOP_BY_HOP = new Set([
"connection",
"keep-alive",
"transfer-encoding",
"content-length",
"host",
"te",
"trailer",
"upgrade",
]);
export function resolveRoute(pathname) {
if (pathname === "/" || pathname === "") {
return { kind: "index" };
}
return { kind: "proxy" };
}
export function upstreamUrl(apiBase, requestUrl) {
return new URL(requestUrl, apiBase).toString();
}
export function renderIndexHtml({ apiBase = DEFAULT_API_BASE, cdnBase = CDN_BASE } = {}) {
return `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>KnowledgeFS API — Swagger UI</title>
<link rel="stylesheet" href="${cdnBase}/swagger-ui.css" />
<style>
body { margin: 0; background: #fafafa; }
.topbar { background: #1b1b1b; color: #fff; padding: 10px 16px; font: 600 14px/1.4 system-ui, sans-serif; }
.topbar small { color: #9aa0a6; font-weight: 400; }
</style>
</head>
<body>
<div class="topbar">KnowledgeFS API <small>— proxied to live gateway at ${apiBase}</small></div>
<div id="swagger-ui"></div>
<script src="${cdnBase}/swagger-ui-bundle.js" crossorigin></script>
<script>
window.onload = () => {
window.ui = SwaggerUIBundle({
url: "/openapi.json",
dom_id: "#swagger-ui",
deepLinking: true,
presets: [SwaggerUIBundle.presets.apis],
layout: "BaseLayout",
tryItOutEnabled: true,
});
};
</script>
</body>
</html>
`;
}
function filterHeaders(headers) {
const out = {};
for (const [key, value] of Object.entries(headers)) {
if (value !== undefined && !HOP_BY_HOP.has(key.toLowerCase())) {
out[key] = value;
}
}
return out;
}
function proxy(req, res, apiBase) {
const target = new URL(upstreamUrl(apiBase, req.url ?? "/"));
const upstream = http.request(
target,
{ method: req.method, headers: filterHeaders(req.headers) },
(up) => {
res.writeHead(up.statusCode ?? 502, filterHeaders(up.headers));
up.pipe(res);
},
);
upstream.on("error", (err) => {
res.writeHead(502, { "content-type": "text/plain" });
res.end(`gateway unreachable: ${err.message}`);
});
req.pipe(upstream);
}
export function createSwaggerServer({ apiBase = DEFAULT_API_BASE } = {}) {
return http.createServer((req, res) => {
const { pathname } = new URL(req.url ?? "/", "http://localhost");
const route = resolveRoute(pathname);
if (route.kind === "index" && req.method === "GET") {
const body = renderIndexHtml({ apiBase });
res.writeHead(200, {
"content-type": "text/html; charset=utf-8",
"content-length": Buffer.byteLength(body),
});
res.end(body);
return;
}
proxy(req, res, apiBase);
});
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const server = createSwaggerServer({ apiBase: DEFAULT_API_BASE });
server.listen(DEFAULT_PORT, () => {
console.log(
`Swagger UI on http://localhost:${DEFAULT_PORT} (proxying -> ${DEFAULT_API_BASE})`,
);
});
}