Der Ausliefer-Helfer haengt nicht mehr an Express -- und ist einzeln prüfbar
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 <[email protected]>
This commit is contained in:
@@ -106,11 +106,23 @@ export function bereichLesen(roh, groesse) {
|
|||||||
* vorher nachgesehen und sonst 410 gemeldet)
|
* vorher nachgesehen und sonst 410 gemeldet)
|
||||||
*/
|
*/
|
||||||
export function liefereDatei(req, res, pfad) {
|
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;
|
let groesse;
|
||||||
try { groesse = statSync(pfad).size; } catch {
|
try { groesse = statSync(pfad).size; } catch {
|
||||||
/* Zwischen dem Nachsehen des Aufrufers und hier ist die Datei
|
/* Zwischen dem Nachsehen des Aufrufers und hier ist die Datei
|
||||||
verschwunden. Nichts gesendet, also darf noch ein Fehler kommen. */
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +132,7 @@ export function liefereDatei(req, res, pfad) {
|
|||||||
if (bereich === false) {
|
if (bereich === false) {
|
||||||
/* UNERFUELLBAR. Die Groesse mitzugeben ist Pflicht, sonst weiss
|
/* UNERFUELLBAR. Die Groesse mitzugeben ist Pflicht, sonst weiss
|
||||||
der Browser nicht, was er stattdessen fragen soll. */
|
der Browser nicht, was er stattdessen fragen soll. */
|
||||||
res.status(416);
|
res.statusCode = 416;
|
||||||
res.setHeader("Content-Range", `bytes */${groesse}`);
|
res.setHeader("Content-Range", `bytes */${groesse}`);
|
||||||
res.end();
|
res.end();
|
||||||
return;
|
return;
|
||||||
@@ -131,7 +143,7 @@ export function liefereDatei(req, res, pfad) {
|
|||||||
: createReadStream(pfad);
|
: createReadStream(pfad);
|
||||||
|
|
||||||
if (bereich) {
|
if (bereich) {
|
||||||
res.status(206);
|
res.statusCode = 206;
|
||||||
res.setHeader("Content-Range", `bytes ${bereich.von}-${bereich.bis}/${groesse}`);
|
res.setHeader("Content-Range", `bytes ${bereich.von}-${bereich.bis}/${groesse}`);
|
||||||
res.setHeader("Content-Length", String(bereich.bis - bereich.von + 1));
|
res.setHeader("Content-Length", String(bereich.bis - bereich.von + 1));
|
||||||
} else {
|
} else {
|
||||||
@@ -143,7 +155,7 @@ export function liefereDatei(req, res, pfad) {
|
|||||||
jedem Abbruch ein offener Lesestrom. */
|
jedem Abbruch ein offener Lesestrom. */
|
||||||
res.on("close", () => { if (!strom.destroyed) strom.destroy(); });
|
res.on("close", () => { if (!strom.destroyed) strom.destroy(); });
|
||||||
strom.on("error", () => {
|
strom.on("error", () => {
|
||||||
if (!res.headersSent) res.status(500).end();
|
if (!res.headersSent) { res.statusCode = 500; res.end(); }
|
||||||
else res.destroy();
|
else res.destroy();
|
||||||
});
|
});
|
||||||
strom.pipe(res);
|
strom.pipe(res);
|
||||||
|
|||||||
@@ -1569,5 +1569,45 @@ console.log(`\n=== Gibt noch jemand eine Datei ohne Teilanfragen hinaus? ===`);
|
|||||||
"der gemeinsame Weg selbst auch nicht");
|
"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`}`);
|
console.log(`\n${fehler === 0 ? "ALLES IN ORDNUNG" : `${fehler} FEHLER`}`);
|
||||||
process.exit(fehler ? 1 : 0);
|
process.exit(fehler ? 1 : 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user