From 39435490c5b1e32d7add6445c6c711f2045327e3 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 22 May 2026 09:03:44 +0000 Subject: [PATCH] fix: support Cloud Web internal API port --- scripts/dev-artifact-publish.mjs | 60 ++++++++++++++++++++------- web/hwlab-cloud-web/scripts/check.mjs | 4 ++ 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/scripts/dev-artifact-publish.mjs b/scripts/dev-artifact-publish.mjs index d9e36b43..7915d2c4 100644 --- a/scripts/dev-artifact-publish.mjs +++ b/scripts/dev-artifact-publish.mjs @@ -299,6 +299,7 @@ async function resolveServices(serviceIds, catalog) { function runtimeScriptBase64() { const source = String.raw` import { createServer } from "node:http"; +import http from "node:http"; import { spawn } from "node:child_process"; import { readFile, stat } from "node:fs/promises"; import path from "node:path"; @@ -364,6 +365,36 @@ function copyHeaders(headers) { return result; } +function requestUpstream(target, request, body) { + return new Promise((resolve, reject) => { + const upstream = http.request( + target, + { + method: request.method, + headers: { + accept: request.headers.accept || "application/json", + "content-type": request.headers["content-type"] || "application/json", + "content-length": Buffer.byteLength(body) + } + }, + (upstreamResponse) => { + const chunks = []; + upstreamResponse.on("data", (chunk) => chunks.push(chunk)); + upstreamResponse.on("end", () => { + resolve({ + statusCode: upstreamResponse.statusCode || 502, + headers: upstreamResponse.headers, + body: Buffer.concat(chunks) + }); + }); + } + ); + upstream.on("error", reject); + if (body) upstream.write(body); + upstream.end(); + }); +} + async function proxyCloudApi(request, response, url) { if (!cloudApiBaseUrl) { sendJson(response, 503, { @@ -401,20 +432,21 @@ async function proxyCloudApi(request, response, url) { } const target = new URL(url.pathname + url.search, cloudApiBaseUrl); - const upstream = await fetch(target, { - method: request.method, - headers: { - accept: request.headers.accept || "application/json", - "content-type": request.headers["content-type"] || "application/json" - }, - body: request.method === "GET" ? undefined : body - }); - const payload = Buffer.from(await upstream.arrayBuffer()); - response.writeHead(upstream.status, { - ...copyHeaders(Object.fromEntries(upstream.headers.entries())), - "content-length": payload.length - }); - response.end(payload); + try { + const upstream = await requestUpstream(target, request, request.method === "GET" ? "" : body); + response.writeHead(upstream.statusCode, { + ...copyHeaders(upstream.headers), + "content-length": upstream.body.length + }); + response.end(upstream.body); + } catch (error) { + sendJson(response, 502, { + error: "cloud_api_proxy_failed", + serviceId, + target: target.href.replace(/\/\/[^/@]*@/u, "//***@"), + reason: error.message + }); + } } async function serveCloudWeb() { diff --git a/web/hwlab-cloud-web/scripts/check.mjs b/web/hwlab-cloud-web/scripts/check.mjs index d4198b73..abaf7756 100644 --- a/web/hwlab-cloud-web/scripts/check.mjs +++ b/web/hwlab-cloud-web/scripts/check.mjs @@ -51,11 +51,15 @@ assert.match(app, /callRpc\("audit\.event\.query"/); assert.match(app, /callRpc\("evidence\.record\.query"/); assert.match(artifactPublisher, /HWLAB_API_BASE_URL/); assert.match(artifactPublisher, /readOnlyRpcMethods/); +assert.match(artifactPublisher, /requestUpstream/); +assert.match(artifactPublisher, /from "node:http"/); +assert.doesNotMatch(artifactPublisher, /await fetch\(target/); assert.match(artifactPublisher, /"system\.health"/); assert.match(artifactPublisher, /"cloud\.adapter\.describe"/); assert.match(artifactPublisher, /"audit\.event\.query"/); assert.match(artifactPublisher, /"evidence\.record\.query"/); assert.match(artifactPublisher, /readonly_rpc_required/); +assert.match(artifactPublisher, /cloud_api_proxy_failed/); assert.match(app, /degraded/); assert.match(app, /runtime\.serviceRoute\.join\(" -> "\)/); assert.doesNotMatch(app, /callRpc\("hardware\./);