feedback5 R1: startServer and main become wiring — PORT / HOST / TRUST_PROXY and the option picks go to http/config.js, the session stack and the /ws upgrade to http/websocket.js, the request listener and /healthz to http/routes.js (new), the security headers and the clientError 400 to http/common.js, the banner, signals and entry check to http/boot.js

Same order of construction, listeners and headers as before; a scripted fingerprint (export list, 51 HTTP requests'
status / header order / body hash, raw upgrade and malformed-request replies) and the process run (banner, port in
use, SIGINT) match the pre-split server exactly. test/version.test.js reads the banner and /healthz from their new
files; module headers describe each piece.
This commit is contained in:
sganggs committed 2026-10-05 16:08:41 +08:00
1 parent b48a67caec
commit 1b38e30819
11 files changed
+323 -202

No files matched your search

+21 -2
View File
@@ -1,5 +1,15 @@
// server/http/common.js — the helpers every HTTP route shares: URL split, the error page and JSON replies.
// Moved from server/index.js.
// server/http/common.js — what every HTTP answer shares:
//
// * the security headers, set on every response before any route runs (routes.js);
// * splitUrl (raw path + query, also from absolute-form URLs), the bilingual error page (sendError) and JSON replies
// (sendJson) — both `Cache-Control: no-store`, without a body for HEAD;
// * the bare `400 Bad Request` for a request node:http cannot parse (answerClientError).
/** Headers on every HTTP response. @param {import('node:http').ServerResponse} res */
export function setSecurityHeaders(res) {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('Referrer-Policy', 'same-origin');
}
const escapeHtml = (s) => String(s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c]);
@@ -35,3 +45,12 @@ export function splitUrl(url) {
const hashless = (s) => { const h = s.indexOf('#'); return h >= 0 ? s.slice(0, h) : s; };
return q >= 0 ? { rawPath: hashless(u.slice(0, q)), query: hashless(u.slice(q + 1)) } : { rawPath: hashless(u), query: '' };
}
/** node:http 'clientError' listener: a reset socket is dropped, any other unparseable request gets a bare 400. */
export function answerClientError(err, socket) {
if (err && err.code === 'ECONNRESET') { socket.destroy(); return; }
try {
if (socket.writable) socket.end('HTTP/1.1 400 Bad Request\r\nConnection: close\r\nContent-Length: 0\r\n\r\n');
else socket.destroy();
} catch { /* ignore */ }
}