Farhan Beg commited on
Commit Β·
1f29b0d
1
Parent(s): be5c4d4
fix(proxy): buffer request bodies to avoid chunked Transfer-Encoding, add HERMES_WEBUI_TRUST_FORWARDED_HOST=1
Browse filesTwo fixes for WebUI behind the Node.js reverse proxy:
1. HTTP 501 Unsupported method error: Python BaseHTTPRequestHandler
cannot decode chunked request bodies (Transfer-Encoding: chunked).
Node.js http.request may use chunked encoding when piping req
streams. Fix: buffer the full request body and send it with an
explicit Content-Length header.
2. Cross-origin CSRF mismatch: hermes-webui no longer trusts
X-Forwarded-Host by default. The proxy sets this header, so
browser Origin vs Host checks fail. Fix: set
HERMES_WEBUI_TRUST_FORWARDED_HOST=1 in Dockerfile ENV.
- Dockerfile +1 -0
- health-server.js +79 -1
Dockerfile
CHANGED
|
@@ -180,6 +180,7 @@ ENV HERMES_HOME=/opt/data \
|
|
| 180 |
HUGGINGMES_APP_DIR=/opt/huggingmes \
|
| 181 |
HERMES_WEBUI_REPO=/opt/hermes-webui \
|
| 182 |
HERMES_AGENT_VERSION=${HERMES_AGENT_VERSION} \
|
|
|
|
| 183 |
PYTHONUNBUFFERED=1 \
|
| 184 |
HF_HUB_ENABLE_HF_TRANSFER=1 \
|
| 185 |
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium
|
|
|
|
| 180 |
HUGGINGMES_APP_DIR=/opt/huggingmes \
|
| 181 |
HERMES_WEBUI_REPO=/opt/hermes-webui \
|
| 182 |
HERMES_AGENT_VERSION=${HERMES_AGENT_VERSION} \
|
| 183 |
+
HERMES_WEBUI_TRUST_FORWARDED_HOST=1 \
|
| 184 |
PYTHONUNBUFFERED=1 \
|
| 185 |
HF_HUB_ENABLE_HF_TRANSFER=1 \
|
| 186 |
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium
|
health-server.js
CHANGED
|
@@ -303,6 +303,62 @@ function proxyRequest(
|
|
| 303 |
"x-forwarded-proto": req.headers["x-forwarded-proto"] || "https",
|
| 304 |
};
|
| 305 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
const proxy = http.request(
|
| 307 |
{
|
| 308 |
hostname: GATEWAY_HOST,
|
|
@@ -438,7 +494,29 @@ function proxyDashboard(req, res) {
|
|
| 438 |
res.end(JSON.stringify({ error: "proxy_error", message: error.message }));
|
| 439 |
});
|
| 440 |
|
| 441 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 442 |
}
|
| 443 |
|
| 444 |
/* ββ Status JSON + HuggingMes status page βββββββββββββββββββββββββββ */
|
|
|
|
| 303 |
"x-forwarded-proto": req.headers["x-forwarded-proto"] || "https",
|
| 304 |
};
|
| 305 |
|
| 306 |
+
// Python's BaseHTTPRequestHandler (used by hermes-webui and the dashboard)
|
| 307 |
+
// cannot decode chunked request bodies β read_body() only reads via
|
| 308 |
+
// Content-Length, and leftover chunk framing corrupts subsequent requests
|
| 309 |
+
// on keep-alive connections (HTTP 501 with junk prepended to the method).
|
| 310 |
+
// Buffer the full body and send it with an explicit Content-Length header
|
| 311 |
+
// so Node.js never uses Transfer-Encoding: chunked.
|
| 312 |
+
const hasBody = req.method === "POST" || req.method === "PUT" || req.method === "PATCH";
|
| 313 |
+
if (hasBody) {
|
| 314 |
+
const chunks = [];
|
| 315 |
+
let size = 0;
|
| 316 |
+
const limit = 20 * 1024 * 1024;
|
| 317 |
+
req.on("data", (chunk) => {
|
| 318 |
+
chunks.push(chunk);
|
| 319 |
+
size += chunk.length;
|
| 320 |
+
if (size > limit) {
|
| 321 |
+
req.destroy();
|
| 322 |
+
if (!res.headersSent) {
|
| 323 |
+
res.writeHead(413, { "content-type": "application/json" });
|
| 324 |
+
res.end(JSON.stringify({ error: "payload_too_large" }));
|
| 325 |
+
}
|
| 326 |
+
}
|
| 327 |
+
});
|
| 328 |
+
req.on("end", () => {
|
| 329 |
+
delete headers["transfer-encoding"];
|
| 330 |
+
headers["content-length"] = String(size);
|
| 331 |
+
const proxy = http.request(
|
| 332 |
+
{
|
| 333 |
+
hostname: GATEWAY_HOST,
|
| 334 |
+
port: targetPort,
|
| 335 |
+
method: req.method,
|
| 336 |
+
path: targetPath,
|
| 337 |
+
headers,
|
| 338 |
+
},
|
| 339 |
+
(upstream) => {
|
| 340 |
+
res.writeHead(upstream.statusCode || 502, upstream.headers);
|
| 341 |
+
upstream.pipe(res);
|
| 342 |
+
},
|
| 343 |
+
);
|
| 344 |
+
proxy.on("error", (error) => {
|
| 345 |
+
if (!res.headersSent) {
|
| 346 |
+
res.writeHead(502, { "content-type": "application/json" });
|
| 347 |
+
res.end(JSON.stringify({ error: "proxy_error", message: error.message }));
|
| 348 |
+
}
|
| 349 |
+
});
|
| 350 |
+
if (size > 0) proxy.write(Buffer.concat(chunks));
|
| 351 |
+
proxy.end();
|
| 352 |
+
});
|
| 353 |
+
req.on("error", (error) => {
|
| 354 |
+
if (!res.headersSent) {
|
| 355 |
+
res.writeHead(502, { "content-type": "application/json" });
|
| 356 |
+
res.end(JSON.stringify({ error: "proxy_error", message: error.message }));
|
| 357 |
+
}
|
| 358 |
+
});
|
| 359 |
+
return;
|
| 360 |
+
}
|
| 361 |
+
|
| 362 |
const proxy = http.request(
|
| 363 |
{
|
| 364 |
hostname: GATEWAY_HOST,
|
|
|
|
| 494 |
res.end(JSON.stringify({ error: "proxy_error", message: error.message }));
|
| 495 |
});
|
| 496 |
|
| 497 |
+
// Buffer body before forwarding β same chunked-encoding fix as proxyRequest.
|
| 498 |
+
const hasBody = req.method === "POST" || req.method === "PUT" || req.method === "PATCH";
|
| 499 |
+
if (hasBody) {
|
| 500 |
+
const bodyChunks = [];
|
| 501 |
+
let bodySize = 0;
|
| 502 |
+
req.on("data", (chunk) => {
|
| 503 |
+
bodyChunks.push(chunk);
|
| 504 |
+
bodySize += chunk.length;
|
| 505 |
+
});
|
| 506 |
+
req.on("end", () => {
|
| 507 |
+
delete headers["transfer-encoding"];
|
| 508 |
+
headers["content-length"] = String(bodySize);
|
| 509 |
+
upstream.end(Buffer.concat(bodyChunks));
|
| 510 |
+
});
|
| 511 |
+
req.on("error", (error) => {
|
| 512 |
+
if (!res.headersSent) {
|
| 513 |
+
res.writeHead(502, { "content-type": "application/json" });
|
| 514 |
+
res.end(JSON.stringify({ error: "proxy_error", message: error.message }));
|
| 515 |
+
}
|
| 516 |
+
});
|
| 517 |
+
} else {
|
| 518 |
+
req.pipe(upstream);
|
| 519 |
+
}
|
| 520 |
}
|
| 521 |
|
| 522 |
/* ββ Status JSON + HuggingMes status page βββββββββββββββββββββββββββ */
|