Zum Inhalt springen

OBS/Kostenpflichtige Module/RESTServer/Datei-Upload: Unterschied zwischen den Versionen

Aus OBS Wiki
Rademacker (Diskussion | Beiträge)
Keine Bearbeitungszusammenfassung
MINERVA-Rademacker (Diskussion | Beiträge)
Reader-Aufrufe statt _OBS_UPLOAD_-Felder, Idempotency-Key-Pflicht bei Uploads ergaenzt [Volltext ersetzt]
 
Zeile 20: Zeile 20:
'''verschiebt sie nicht selbst'''. Übernimmt das Skript sie nicht an ihren
'''verschiebt sie nicht selbst'''. Übernimmt das Skript sie nicht an ihren
Zielort, bleibt sie dort liegen.}}
Zielort, bleibt sie dort liegen.}}
{{Achtung|Ein Upload läuft über ''POST'' und braucht deshalb - wie jeder
schreibende Aufruf - den Header <code>Idempotency-Key</code>. Fehlt er, antwortet
der Server mit '''400''' <code>IDEMPOTENCY_KEY_MISSING</code>, und zwar bevor eine
Datei entgegengenommen wird. Beim resumable Upload gehört er an '''jedes'''
Teilstück; wirksam wird er beim letzten, weil erst dort eine Antwort entsteht, die
sich speichern lässt. Siehe
[[OBS/Kostenpflichtige Module/RESTServer/Endpunkte|Endpunkte]], Abschnitt
Idempotenz.}}


== Einfacher Upload (multipart/form-data) ==
== Einfacher Upload (multipart/form-data) ==
Zeile 120: Zeile 129:
Der Grund ist nicht Bequemlichkeit. Bei einem chunked Upload bliebe offen, ob <code>Content-Range</code> die gepackten oder die rohen Bytes zählt, und die SHA256-Prüfsumme bezöge sich nach einem Entpacken auf eine andere Datei, als der Client gesendet hat. Etablierte Protokolle - tus, S3 Multipart, Google Resumable Upload, Azure Blob - behandeln Kompression aus demselben Grund als Eigenschaft der '''gespeicherten Datei''' und nicht der Übertragung.
Der Grund ist nicht Bequemlichkeit. Bei einem chunked Upload bliebe offen, ob <code>Content-Range</code> die gepackten oder die rohen Bytes zählt, und die SHA256-Prüfsumme bezöge sich nach einem Entpacken auf eine andere Datei, als der Client gesendet hat. Etablierte Protokolle - tus, S3 Multipart, Google Resumable Upload, Azure Blob - behandeln Kompression aus demselben Grund als Eigenschaft der '''gespeicherten Datei''' und nicht der Übertragung.


Wer gepackt hochladen will, packt die Datei '''selbst''' und lädt sie als <code>export.csv.gz</code> hoch. Offsets, Prüfsumme und Wiederaufnahme stimmen dann, und das Endpunkt-Skript bekommt Name und Typ wie gewohnt über <code>_OBS_UPLOAD_NAME</code> und <code>_OBS_UPLOAD_CONTENTTYPE</code>.
Wer gepackt hochladen will, packt die Datei '''selbst''' und lädt sie als <code>export.csv.gz</code> hoch. Offsets, Prüfsumme und Wiederaufnahme stimmen dann, und das Endpunkt-Skript bekommt Name und Typ wie gewohnt über <code>oReader.UploadName()</code> und <code>oReader.UploadType()</code>.

Aktuelle Version vom 25. September 2026, 10:28 Uhr

Datei-Upload

Ein Endpunkt nimmt Dateien nur entgegen, wenn er dafür freigeschaltet ist (re_upload = 1, siehe Endpunkte). Dann gilt nicht das JSON-Body-Limit von 10 MB, sondern die pro Endpunkt konfigurierte Grösse (re_upload_size, Standard 25 MB; Überschreitung -> 413).

Diese Seite beschreibt die Übertragung - was ein Client senden muss und was der Server antwortet. Was das Skript davon sieht, sind vier Aufrufe (oReader.UploadPath(), UploadName(), UploadType(), UploadSha()) und steht unter Scripting. Ein vollständiges Beispiel zeigt Beispiel 6.

HINWEIS: Der Server legt die Datei in einem temporären Verzeichnis ab und

verschiebt sie nicht selbst. Übernimmt das Skript sie nicht an ihren

Zielort, bleibt sie dort liegen.
ACHTUNG: Ein Upload läuft über POST und braucht deshalb - wie jeder

schreibende Aufruf - den Header Idempotency-Key. Fehlt er, antwortet der Server mit 400 IDEMPOTENCY_KEY_MISSING, und zwar bevor eine Datei entgegengenommen wird. Beim resumable Upload gehört er an jedes Teilstück; wirksam wird er beim letzten, weil erst dort eine Antwort entsteht, die sich speichern lässt. Siehe Endpunkte, Abschnitt

Idempotenz.

Einfacher Upload (multipart/form-data)

Eine Datei pro Request. Name und Content-Type stammen aus der Content-Disposition der Datei-Partie (filename=). Der Server speichert die erste Datei-Partie und ruft das Skript auf.

Die übrigen Partien des Formulars - alles ohne filename= - liest das Skript über oReader.Form('<name>'). Der Wert ist UTF-8 und auf 1024 Zeichen begrenzt, der Name auf Buchstaben, Ziffern und Unterstrich reduziert. Zusatzangaben lassen sich damit im selben Request mitschicken, ohne sie an die URL zu hängen:

procedure Post(oReader: TxRestReader; oWriter: TxRestWriter);
var cPfad : string;
    cName : string;
    cTyp  : string;
    cBemerk: string;
begin
    cPfad   := oReader.UploadPath();
    cName   := oReader.UploadName();
    cTyp    := oReader.UploadType();
    cBemerk := oReader.Form('bemerkung');   // Formularfeld derselben Uebertragung

    // ... Datei aus cPfad ins DMS / Zielverzeichnis uebernehmen ...

    oWriter.Str('status'   , 'ok');
    oWriter.Str('dateiname', cName);
    oWriter.Status(201);
end;

Resumable/Chunked Upload (Content-Range)

Für grosse Dateien überträgt der Client die Datei in Teilstücken (Chunks) mit dem Header Content-Range: bytes START-END/TOTAL. Der Server hängt die Chunks sequentiell an eine Session-Datei an und behandelt unvollständige Uploads selbst - das Endpunkt-Skript läuft erst beim vollständigen Upload, und dann liefern oReader.UploadPath(), UploadName() und UploadType() dasselbe wie beim einfachen Upload. Für die Teilstücke läuft es nicht.

Name und Content-Type sind im Content-Range-Protokoll nicht enthalten; der Client liefert sie deshalb im ersten Chunk über den Header Upload-Metadata (Format wie bei tus: kommagetrennte Paare schlüssel base64wert, Werte Base64-kodiert):

Upload-Metadata: filename ZmlsZS5wZGY=,filetype YXBwbGljYXRpb24vcGRm

Erkannt werden die Schlüssel filename (Pflicht) und filetype (optional). Fehlt filename im ersten Chunk, wird der Upload mit 400 abgelehnt, ohne dass eine Datei angelegt wird. Folge-Chunks und Statusabfragen müssen die Metadaten nicht erneut senden.

Ablauf (vom Client gesteuert, der Server antwortet jeweils):

Request Server-Antwort
Erster Chunk Content-Range: bytes 0-1048575/5000000 + Upload-Metadata 308 Resume Incomplete + Header Upload-Id, Upload-Offset, Range
weitere Chunks (jeweils Upload-Id mitsenden) 308 mit aktualisiertem Upload-Offset
letzter Chunk (Bereich erreicht TOTAL) normale Skript-Antwort (z.B. 200/201)
Statusabfrage Content-Range: bytes */5000000 aktueller Upload-Offset, ohne anzuhängen (Resume)
Header Richtung Bedeutung
Content-Range Request bytes START-END/TOTAL; bytes */TOTAL nur als Statusabfrage
Upload-Metadata Request tus-Metadaten im ersten Chunk: filename (Pflicht), filetype (optional), Base64-kodiert
Upload-Id Request/Response Session-Kennung. Fehlt sie im ersten Request, erzeugt der Server eine und gibt sie zurück; alle Folge-Chunks müssen sie mitsenden.
Upload-Offset Response Anzahl der bereits gespeicherten Bytes (= Startoffset des nächsten Chunks)
Range Response Bereits gespeicherter Bereich (bytes=0-N)

Der Server hängt einen Chunk nur an, wenn START dem bereits gespeicherten Stand entspricht. Bei Abweichung (doppelter Chunk, Lücke) wird nicht angehängt, sondern der aktuelle Upload-Offset zurückgemeldet - der Client setzt ab dort neu auf. Eine separate Statusabfrage ist für ein Resume daher nicht zwingend nötig.

Überschreitet ein Chunk bzw. die Gesamtgrösse das Limit (re_upload_size), antwortet der Server mit 413 Payload Too Large.

HINWEIS: Der Upload-Status liegt prozesslokal im Speicher. Nach einem Neustart des Servers muss ein unvollständiger Upload neu begonnen werden.

Komprimierte Uploads

Ein Upload-Körper mit dem Header Content-Encoding wird mit 415 Unsupported Media Type abgewiesen - bei beiden Verfahren, einfach wie chunked.

Der Grund ist nicht Bequemlichkeit. Bei einem chunked Upload bliebe offen, ob Content-Range die gepackten oder die rohen Bytes zählt, und die SHA256-Prüfsumme bezöge sich nach einem Entpacken auf eine andere Datei, als der Client gesendet hat. Etablierte Protokolle - tus, S3 Multipart, Google Resumable Upload, Azure Blob - behandeln Kompression aus demselben Grund als Eigenschaft der gespeicherten Datei und nicht der Übertragung.

Wer gepackt hochladen will, packt die Datei selbst und lädt sie als export.csv.gz hoch. Offsets, Prüfsumme und Wiederaufnahme stimmen dann, und das Endpunkt-Skript bekommt Name und Typ wie gewohnt über oReader.UploadName() und oReader.UploadType().