diff --git a/k8s/06-ingress.yaml b/k8s/06-ingress.yaml index 2896121..49264fb 100644 --- a/k8s/06-ingress.yaml +++ b/k8s/06-ingress.yaml @@ -19,7 +19,7 @@ metadata: cert-manager.io/cluster-issuer: letsencrypt-prod nginx.ingress.kubernetes.io/ssl-redirect: "true" # ingress-nginx laesst ohne diese Annotation standardmaessig nur 1 MB pro - # Request durch — weit unter dem Bild- (8 MB, lib/uploads.ts + # Request durch — weit unter dem Bild- (25 MB, lib/uploads.ts # MAX_UPLOAD_BYTES) und erst recht dem Video-Limit (100 MB, lib/uploads.ts # MAX_UPLOAD_VIDEO_BYTES) der Admin-Uploads. Ohne diese Zeile bricht # nginx jeden Upload jenseits von 1 MB vor der App ab (Verbindungsabbruch diff --git a/src/app/uploads/[...path]/route.ts b/src/app/uploads/[...path]/route.ts index e039f54..eee2213 100644 --- a/src/app/uploads/[...path]/route.ts +++ b/src/app/uploads/[...path]/route.ts @@ -1,6 +1,8 @@ import { NextResponse } from "next/server"; -import { readFile } from "node:fs/promises"; -import { contentTypeForExtension, resolveUploadPath } from "@/lib/uploads"; +import { createReadStream } from "node:fs"; +import { stat } from "node:fs/promises"; +import { Readable } from "node:stream"; +import { contentTypeForExtension, parseRangeHeader, resolveUploadPath, uploadETag } from "@/lib/uploads"; // node:fs braucht die Node-Runtime (nicht Edge). export const runtime = "nodejs"; @@ -10,28 +12,60 @@ export const runtime = "nodejs"; // unter einem einmal ausgelieferten Pfad aendert sich also nie mehr. const CACHE_CONTROL = "public, max-age=31536000, immutable"; +function notFound(): NextResponse { + return NextResponse.json({ error: "Nicht gefunden." }, { status: 404 }); +} + /** - * Liefert hochgeladene Bilder (Inhalte-/Produkt-/Erntenfotos) aus, die unter - * UPLOAD_DIR liegen. Next.js' statischer public/-Ordner wird zur Build-Zeit - * fest ins Docker-Image gebacken und kann keine zur Laufzeit hinzugefuegten - * Dateien ausliefern — dieser dynamische Route-Handler streamt sie - * stattdessen direkt von der Platte (in Produktion ein SMB-Mount, siehe - * UPLOAD_DIR in lib/uploads.ts). + * Header, die jede Antwort dieser Route traegt — auch 304 und 416, damit + * Caches und Browser in allen Faellen dieselbe Aussage ueber Cachebarkeit, + * Typ und Sniffing-Verbot sehen. + */ +function baseHeaders(contentType: string, etag: string): Record { + return { + "Content-Type": contentType, + "Content-Disposition": "inline", + // Verhindert, dass der Browser den deklarierten Content-Type anhand + // des Inhalts "korrigiert" (MIME-Sniffing) — zusaetzliche Absicherung + // neben der festen Content-Type-Zuordnung unten. + "X-Content-Type-Options": "nosniff", + "Cache-Control": CACHE_CONTROL, + ETag: etag, + // Sagt dem Browser ueberhaupt erst, dass er Teilbereiche anfordern darf + // — ohne diesen Header versucht z.B. ein