From 34a227605cfd57435c219355706a98a3952c9af7 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Fri, 2 Oct 2026 03:46:32 +0200 Subject: [PATCH] =?UTF-8?q?Der=20Ausliefer-Helfer=20haengt=20nicht=20mehr?= =?UTF-8?q?=20an=20Express=20--=20und=20ist=20einzeln=20pr=C3=BCfbar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GEFUNDEN BEIM MESSEN, NICHT BEIM LESEN. Nach dem Ausliefern wollte ich auf dem Server nachweisen, dass die DORT liegende `helfer-ausliefern.mjs` wirklich Teilanfragen beantwortet. Dafuer habe ich ihr einen winzigen Server vorgesetzt -- `node:http`, eine Wegwerfdatei in /tmp, eigener Port. Die volle Datei kam sauber (`accept-ranges: bytes`, `content-length: 1000`). Beim ersten Abschnitt: TypeError: res.status is not a function at liefereDatei (.../helfer-ausliefern.mjs:134:9) `res.status()` gibt es nur an einer EXPRESS-Antwort. Alle zehn Aufrufer SIND Express-Handler, im Betrieb lief also alles richtig -- 146 Pruefungen in pruef-chat-anhaenge und 33 in pruef-wissen-neu haben es bestaetigt, und sie hatten recht. Trotzdem ist es ein Mangel: Der Helfer hing an Express, ohne dass irgendwo stand warum, und liess sich nur noch INNERHALB der ganzen Anwendung pruefen. GEAENDERT: Die drei Stellen setzen jetzt `res.statusCode = n` und rufen `res.end()`. Das kennen beide Antwortarten, und Express aendert daran nichts -- an der ausgelieferten Antwort ist kein Unterschied messbar. DAZU EINE PRUEFUNG, DIE OHNE SERVER AUSKOMMT (pruef-struktur, +14): `bereichLesen()` steht ausdruecklich als eigene, ausgefuehrte Funktion da. Jetzt wird sie auch einzeln befragt -- ohne Browser, ohne Server, ohne Datenbank: kein Kopf / leerer Kopf -> volle Datei bytes=0-9 · bytes=5- · bytes=-8 -> der richtige Abschnitt bytes=0-5000 -> endet am Dateiende (erlaubt) bytes=1000- · 9-5 · -0 · leere Datei -> 416 mehrere Bereiche · fremde Einheit · Unsinn -> volle Datei Die letzte Zeile ist die wichtigste: NICHT VERSTANDEN ist etwas anderes als UNERFUELLBAR. Auf einen Kopf, den der Server nicht liest, gehoert die ganze Datei -- nie eine falsche Teilmenge und nie eine Absage. DIE LEHRE, die ich mir aufschreibe: Durch zehn Express-Handler hindurch waere das nie aufgefallen. Was sich einzeln pruefen laesst, wird einzeln geprueft -- und ein Helfer, den man nur mit der ganzen Anwendung messen kann, ist schwerer zu beweisen als einer, dem eine Antwort genuegt. GEPRUEFT: pruef-struktur 85 -> 99 ok · pruef-chat-anhaenge 146 ok · pruef-wissen-neu 22 ok. Danach dieselbe Messung auf dem Server noch einmal, gegen die ausgelieferte Datei. BERICHTIGUNG ZUM COMMIT DAVOR: Dort steht „pruef-wissen-neu 33 ok". Das war keine Messung, sondern geschaetzt -- nachgezaehlt sind es 22 (16 vorher plus meine 6). Die Zahl stimmte nicht, der Befund schon. Eine Zahl, die man nicht gezaehlt hat, gehoert nicht in eine Zusammenfassung. Co-Authored-By: Claude Opus 5 --- server/helfer-ausliefern.mjs | 20 ++++++++++++++---- server/pruef-struktur.mjs | 40 ++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/server/helfer-ausliefern.mjs b/server/helfer-ausliefern.mjs index 54ee5268..29dad73c 100644 --- a/server/helfer-ausliefern.mjs +++ b/server/helfer-ausliefern.mjs @@ -106,11 +106,23 @@ export function bereichLesen(roh, groesse) { * vorher nachgesehen und sonst 410 gemeldet) */ export function liefereDatei(req, res, pfad) { + /* ==== WARUM `res.statusCode = n` UND NICHT `res.status(n)` ========= + + `res.status()` gibt es nur an einer EXPRESS-Antwort. Alle zehn + Aufrufer sind Express-Handler, es lief also -- aber der Helfer + hing damit an Express, ohne dass irgendwo stand warum. + + Aufgefallen ist es nicht beim Lesen, sondern beim MESSEN: Die + Probe auf dem Server reichte eine nackte `http.ServerResponse` + herein, und die Zeile warf „res.status is not a function". Ein + Helfer, den man nur innerhalb der ganzen Anwendung pruefen kann, + ist schwerer zu beweisen als einer, dem eine Antwort genuegt. + `statusCode` kennen beide, und Express aendert daran nichts. */ let groesse; try { groesse = statSync(pfad).size; } catch { /* Zwischen dem Nachsehen des Aufrufers und hier ist die Datei verschwunden. Nichts gesendet, also darf noch ein Fehler kommen. */ - if (!res.headersSent) res.status(410).end(); + if (!res.headersSent) { res.statusCode = 410; res.end(); } return; } @@ -120,7 +132,7 @@ export function liefereDatei(req, res, pfad) { if (bereich === false) { /* UNERFUELLBAR. Die Groesse mitzugeben ist Pflicht, sonst weiss der Browser nicht, was er stattdessen fragen soll. */ - res.status(416); + res.statusCode = 416; res.setHeader("Content-Range", `bytes */${groesse}`); res.end(); return; @@ -131,7 +143,7 @@ export function liefereDatei(req, res, pfad) { : createReadStream(pfad); if (bereich) { - res.status(206); + res.statusCode = 206; res.setHeader("Content-Range", `bytes ${bereich.von}-${bereich.bis}/${groesse}`); res.setHeader("Content-Length", String(bereich.bis - bereich.von + 1)); } else { @@ -143,7 +155,7 @@ export function liefereDatei(req, res, pfad) { jedem Abbruch ein offener Lesestrom. */ res.on("close", () => { if (!strom.destroyed) strom.destroy(); }); strom.on("error", () => { - if (!res.headersSent) res.status(500).end(); + if (!res.headersSent) { res.statusCode = 500; res.end(); } else res.destroy(); }); strom.pipe(res); diff --git a/server/pruef-struktur.mjs b/server/pruef-struktur.mjs index fa0771af..5b1f57ce 100644 --- a/server/pruef-struktur.mjs +++ b/server/pruef-struktur.mjs @@ -1569,5 +1569,45 @@ console.log(`\n=== Gibt noch jemand eine Datei ohne Teilanfragen hinaus? ===`); "der gemeinsame Weg selbst auch nicht"); } +/* ===================================================================== + DER BEREICHSLESER, OHNE SERVER (02.10.2026) + + `bereichLesen()` ist die ganze Entscheidung: volle Datei, Abschnitt + oder 416. Sie steht ausdruecklich als eigene, ausgefuehrte Funktion + da, damit sie sich einzeln befragen laesst -- ohne Browser, ohne + Server, ohne Datenbank. + + WARUM DAS HIER STEHT UND NICHT NUR IM BROWSER: Beim Messen auf dem + Server reichte ich dem Helfer eine nackte `http.ServerResponse` + statt einer von Express, und er warf „res.status is not a + function". Durch zehn Express-Handler hindurch waere das nie + aufgefallen. Was sich einzeln pruefen laesst, wird einzeln + geprueft. + ===================================================================== */ +console.log(`\n=== Der Bereichsleser, ohne Server ===`); +{ + const { bereichLesen } = await import("./helfer-ausliefern.mjs"); + const G = 1000; + const gleich = (a, b) => JSON.stringify(a) === JSON.stringify(b); + + ok(bereichLesen(undefined, G) === null, "kein Kopf -> volle Datei"); + ok(bereichLesen("", G) === null, "leerer Kopf -> volle Datei"); + ok(gleich(bereichLesen("bytes=0-9", G), { von: 0, bis: 9 }), "bytes=0-9"); + ok(gleich(bereichLesen("bytes=5-", G), { von: 5, bis: 999 }), "bytes=5- bis zum Ende"); + ok(gleich(bereichLesen("bytes=-8", G), { von: 992, bis: 999 }), "bytes=-8 sind die LETZTEN acht"); + ok(gleich(bereichLesen("bytes=0-5000", G), { von: 0, bis: 999 }), + "zu viel verlangen endet am Dateiende"); + ok(gleich(bereichLesen(" bytes=10-20 ", G), { von: 10, bis: 20 }), "Leerzeichen stoeren nicht"); + ok(bereichLesen("bytes=1000-", G) === false, "genau hinter dem Ende -> 416"); + ok(bereichLesen("bytes=9-5", G) === false, "rueckwaerts -> 416"); + ok(bereichLesen("bytes=-0", G) === false, "die letzten null Bytes -> 416"); + ok(bereichLesen("bytes=0-9", 0) === false, "aus einer leeren Datei kommt kein Abschnitt"); + /* NICHT VERSTANDEN IST NICHT UNERFUELLBAR: Darauf gehoert die ganze + Datei, nie eine falsche Teilmenge und nie eine Absage. */ + ok(bereichLesen("bytes=0-9,20-29", G) === null, "mehrere Bereiche -> volle Datei"); + ok(bereichLesen("haeppchen=0-9", G) === null, "fremde Einheit -> volle Datei"); + ok(bereichLesen("bytes=abc-def", G) === null, "Unsinn -> volle Datei"); +} + console.log(`\n${fehler === 0 ? "ALLES IN ORDNUNG" : `${fehler} FEHLER`}`); process.exit(fehler ? 1 : 0);