Terminplaner: Zaehler je IP getrennt und Abgelaufene zuerst, Notantwort beim Schreiben, Pruefung aller Antworten auf interne IDs

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-03 08:17:43 +00:00
1 parent 2b0c05b8e9
commit ae1886c030
4 files changed
+226 -31

No files matched your search

+55 -14
View File
@@ -143,6 +143,43 @@ function schreibe(req, res, ergebnis, cors = { erlaubt: null }) {
return res.end(inhalt)
}
/**
* Letzte Absicherung, wenn schon das Schreiben der Antwort scheitert (z. B. nicht serialisierbarer Wert): ein schlichtes 503
* mit fester Meldung, sofern noch nichts gesendet wurde; sonst wird die Antwort beendet. Eine Anfrage bleibt nie haengen und
* der async Handler lehnt nie unbehandelt ab (Express 4 faengt das nicht).
*/
function notantwort(res, err) {
try {
console.error(`[publicTermine] Antwort nicht geschrieben: ${err?.name ?? 'Fehler'}: ${String(err?.message ?? '').slice(0, 200)}`)
} catch {
// das Log darf die Notantwort nicht verhindern
}
try {
if (res.headersSent) return void res.end()
const inhalt = JSON.stringify(NICHT_ERREICHBAR)
res.statusCode = 503
res.setHeader('Content-Type', 'application/json; charset=utf-8')
res.setHeader('Cache-Control', 'no-store')
res.setHeader('Content-Length', String(inhalt.length)) // nur ASCII
return void res.end(inhalt)
} catch {
try {
res.destroy()
} catch {
// nichts mehr zu tun
}
}
}
/** schreibe, aber mit der Notantwort statt eines Fehlers (fuer Stellen ausserhalb von route). */
function schreibeSicher(req, res, ergebnis, cors) {
try {
schreibe(req, res, ergebnis, cors)
} catch (err) {
notantwort(res, err)
}
}
/** { aktiv: false }: dieselbe Antwort fuer unbekannte, inaktive und abgeschaltete Funktionen (GET kurz im Browser-Cache). */
const aktivFalse = (req) => ({ body: { aktiv: false }, cache: req.method === 'GET' ? 'public, max-age=15' : 'no-store' })
@@ -154,18 +191,22 @@ const warte = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
* schneller als config.termine.minAntwortMs (ab Beginn der Anfrage, auch beim Lesen des Rumpfes).
*/
const route = (fn, { mindestdauer = false } = {}) => async (req, res) => {
const cors = { erlaubt: null }
let ergebnis
try {
ergebnis = await fn(req, uhr(), cors)
const cors = { erlaubt: null }
let ergebnis
try {
ergebnis = await fn(req, uhr(), cors)
} catch (err) {
ergebnis = fehlerErgebnis(err)
}
if (mindestdauer) {
const rest = config.termine.minAntwortMs - (performance.now() - req.termineStart)
if (rest > 0) await warte(rest)
}
schreibe(req, res, ergebnis, cors)
} catch (err) {
ergebnis = fehlerErgebnis(err)
notantwort(res, err)
}
if (mindestdauer) {
const rest = config.termine.minAntwortMs - (performance.now() - req.termineStart)
if (rest > 0) await warte(rest)
}
schreibe(req, res, ergebnis, cors)
}
// ---------------------------------------------------------------- Herkunft
@@ -210,7 +251,7 @@ router.use((req, res, next) => {
// Zu grosse Rumpfe gar nicht erst lesen; die Verbindung wird danach geschlossen (der Rumpf bleibt ungelesen).
if (Number(req.get('content-length')) > BODY_MAX) {
res.set('Connection', 'close')
return schreibe(req, res, { status: 413, body: ZU_GROSS })
return schreibeSicher(req, res, { status: 413, body: ZU_GROSS })
}
return next()
})
@@ -530,13 +571,13 @@ router.options(
// ---------------------------------------------------------------- Rest: unbekannte Pfade, Fehler des Parsers
router.use((req, res) => schreibe(req, res, { status: 404, body: NICHT_GEFUNDEN }))
router.use((req, res) => schreibeSicher(req, res, { status: 404, body: NICHT_GEFUNDEN }))
// Fehlerbehandlung des Routers (Parser): 413 zu gross, 400 kaputt oder nicht lesbar, sonst 503; nie der Text des Fehlers
router.use((err, req, res, _next) => {
if (err?.type === 'entity.too.large' || err?.status === 413) return schreibe(req, res, { status: 413, body: ZU_GROSS })
if (Number.isInteger(err?.status) && err.status >= 400 && err.status < 500) return schreibe(req, res, { status: 400, body: UNGUELTIG })
return schreibe(req, res, fehlerErgebnis(err))
if (err?.type === 'entity.too.large' || err?.status === 413) return schreibeSicher(req, res, { status: 413, body: ZU_GROSS })
if (Number.isInteger(err?.status) && err.status >= 400 && err.status < 500) return schreibeSicher(req, res, { status: 400, body: UNGUELTIG })
return schreibeSicher(req, res, fehlerErgebnis(err))
})
export default router