fix: ein Upload durfte den ganzen Dienst umbringen -- und tat es
BEIM UEBERNEHMEN VON 3670 DATEIEN STARB DER DIENST. Nicht einmal, sondern in
Schleife: acht Neustarts, nach aussen 502 vom Proxy.
TypeError [ERR_INVALID_STATE]: ReadableStream is already closed
at ReadableByteStreamController.close (node:internal/webstreams/...)
DIE URSACHE WAR MEINE EIGENE AENDERUNG von vor einer Stunde. Um PowerShells
Formular-Rumpf zu vertragen, hatte ich `c.req.parseBody()` durch
`c.req.raw.formData()` ersetzt -- und damit Hono den Rumpf weggenommen, den es
selbst verwaltet. Beim Aufraeumen der Antwort schliesst dann jemand einen
Strom, der schon zu ist. Die Ausnahme faellt in einem Microtask an, also
AUSSERHALB jedes try/catch, und Node beendet den Prozess.
Beim Test mit 57 Dateien fiel das nicht auf. Bei 3670 schon.
Jetzt geht der Normalfall wieder ueber Hono; der eigene Weg gilt nur noch fuer
die Antwort mit Anfuehrungszeichen an der Grenzmarkierung, fuer die er gedacht
war.
UND EIN NETZ DARUNTER: process.on('uncaughtException') faengt, was ausserhalb
jedes try/catch anfaellt, und laesst den Dienst weiterlaufen. Die Abwaegung
steht im Code: das ist NICHT in jedem Fall richtig -- der Prozess kann danach
kaputt sein. Hier ueberwiegt das Weiterlaufen, weil dieser Dienst keinen
Zustand im Speicher haelt (alles in SQLite und auf der Platte) und ein Dienst,
der wegen EINER Anfrage fuer alle weg ist, der schlechtere Tausch waere. Laut
wird es trotzdem.
DER SCHADEN WAR REPARIERBAR, und zwar mit dem Werkzeug von heute Nachmittag:
206 Dateien waren durch, 2 lagen auf der Platte OHNE Datensatz (geschrieben,
dann starb der Prozess vor dem Eintrag), dazu eine verwaiste Vorschau. Der
Verwaisten-Finder hat sie gefunden, "aufnehmen" hat sie eingetragen -- keine
Datei verloren.
Und die Fehlermeldung im Uebernahmeskript verschwieg die Ursache: sie meldete
"Conversion from JSON failed ... Unexpected character <", obwohl die Wahrheit
ein 502 war. Jetzt wird der HTTP-Code mitgelesen, bei 502 sauber abgebrochen
und gesagt, dass ein neuer Lauf einfach weitermacht.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+25
-11
@@ -591,19 +591,41 @@ dashRoutes.post('/upload', async (c) => {
|
||||
// eigenen Multipart-Parser, und der waere fuer diesen Dienst
|
||||
// unverhaeltnismaessig. Werkzeuge auf dieser Maschine nehmen deshalb
|
||||
// `curl.exe -F` (siehe tools/uebernehmen.ps1); curl setzt beides korrekt.
|
||||
let form: FormData
|
||||
let dateien: File[]
|
||||
let ordnerRoh = ''
|
||||
let ueberschreiben = true
|
||||
|
||||
try {
|
||||
const typ = c.req.header('content-type') ?? ''
|
||||
if (/boundary="/.test(typ)) {
|
||||
const gerade = typ.replace(/boundary="([^"]+)"/, 'boundary=$1')
|
||||
const rumpf = await c.req.arrayBuffer()
|
||||
form = await new Request('http://formular.invalid/', {
|
||||
const form = await new Request('http://formular.invalid/', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': gerade },
|
||||
body: rumpf,
|
||||
}).formData()
|
||||
dateien = form.getAll('file').filter((f) => typeof f !== 'string') as unknown as File[]
|
||||
ordnerRoh = ((form.get('folder') as string | null) ?? '').trim()
|
||||
ueberschreiben = form.get('overwrite') !== 'false'
|
||||
} else {
|
||||
form = await c.req.raw.formData()
|
||||
// DER NORMALFALL GEHT UEBER HONO, und das ist kein Geschmack:
|
||||
// c.req.raw.formData() direkt aufzurufen nimmt Hono den Rumpf weg,
|
||||
// den es selbst verwaltet. Beim Aufraeumen der Antwort schliesst
|
||||
// dann jemand einen Strom, der schon zu ist -- "ReadableStream is
|
||||
// already closed", eine Ausnahme AUSSERHALB jedes try/catch, und
|
||||
// der Prozess ist weg.
|
||||
//
|
||||
// Genau das ist beim Uebernehmen von 3670 Dateien passiert: der
|
||||
// Dienst lief in eine Neustartschleife, und NPM meldete 502. Beim
|
||||
// Test mit 57 Dateien war es nicht aufgefallen.
|
||||
const roh = await c.req.parseBody({ all: true })
|
||||
const feld = roh['file']
|
||||
dateien = (Array.isArray(feld) ? feld : [feld]).filter(
|
||||
(f) => f && typeof f !== 'string',
|
||||
) as unknown as File[]
|
||||
ordnerRoh = typeof roh['folder'] === 'string' ? roh['folder'].trim() : ''
|
||||
ueberschreiben = roh['overwrite'] !== 'false'
|
||||
}
|
||||
} catch (err) {
|
||||
// Ein kaputter Rumpf ergab bisher einen 500, aufgefangen nur von der
|
||||
@@ -617,14 +639,7 @@ dashRoutes.post('/upload', async (c) => {
|
||||
)
|
||||
}
|
||||
|
||||
// getAll und nicht get: sonst kaeme von fuenf Dateien nur die letzte an.
|
||||
// Ohne DOM-Typen kennt TypeScript hier nur FormDataEntryValue; die
|
||||
// Einengung auf File muss deshalb ausgesprochen werden. Der Test bleibt
|
||||
// derselbe -- was kein Text ist, ist eine Datei.
|
||||
const dateien = form.getAll('file').filter((f) => typeof f !== 'string') as unknown as File[]
|
||||
if (dateien.length === 0) return c.json({ error: 'Keine Datei dabei' }, 400)
|
||||
|
||||
const ordnerRoh = (form.get('folder') as string | null)?.trim() ?? ''
|
||||
let ordner = ''
|
||||
if (ordnerRoh) {
|
||||
try {
|
||||
@@ -637,7 +652,6 @@ dashRoutes.post('/upload', async (c) => {
|
||||
}
|
||||
}
|
||||
|
||||
const ueberschreiben = form.get('overwrite') !== 'false'
|
||||
const ergebnisse: {
|
||||
name: string
|
||||
ok: boolean
|
||||
|
||||
Reference in New Issue
Block a user