OBS/Kostenpflichtige Module/RESTServer/Datei-Download
- A Preise aktualisieren
- C Personen übertragen
- E Kategorien verwalten
- G Kataloge verwalten
- I Merkliste übertragen
- K Varianten übertragen
- L Artikelvarianten übertragen
- M Referenzarten übertragen
- N Lagerbestände verwalten
- U Bestellungen einlesen
- V leere Passworte füllen
- W Update-Informationen zurücksetzen
- X Konfiguration
- Z Protokoll
Datei-Download
Ein Endpunkt kann statt eines JSON-Körpers eine Datei ausliefern - PDF, CSV, Bilder, eine APK. Das Skript entscheidet das mit einem einzigen Aufruf, alles Weitere macht der Server: Content-Type, Dateiname, Bereichsanfragen und, bei temporären Dateien, das Aufräumen.
Anders als beim Datei-Upload braucht es dafür keine Freischaltung am Endpunkt. Ein Download entsteht dadurch, dass das Skript ihn erzeugt, nicht dadurch, dass ein Client ihn anfragt.
Die zwei Aufrufe
| Aufruf | Für | Nach dem Senden | Wiederaufsetzbar |
|---|---|---|---|
oWriter.SendFile(cPfad, cName, cTyp) |
Dateien, die es schon gibt und weiter geben wird | bleibt liegen | ja (206/Range) |
oWriter.SendTempFile(cPfad, cName, cTyp) |
Ergebnisse, die je Abruf entstehen | wird gelöscht | nein |
procedure Get(oReader: TxRestReader; oWriter: TxRestWriter);
var cUid : string;
cPfad: string;
cName: string;
begin
cUid := oReader.Path('uid');
if (not BelegGehoertZuMandant(oReader.Claim('tenant'), cUid)) then begin
oWriter.Error(404, 'NOT_FOUND', 'Beleg nicht gefunden');
exit;
end;
cPfad := BelegPfad(cUid);
cName := 'Rechnung_' + cUid + '.pdf';
oWriter.SendFile(cPfad, cName, 'application/pdf');
end;und ruft SendFile, ist das ein Strukturfehler wie eine
unbalancierte Verschachtelung: der Server antwortet 500 und nennt die Stelle
im Protokoll. Error() nach SendFile verwirft die Datei
Was der Server dazu tut
| Kopfzeile | Wert |
|---|---|
| Content-Type | der übergebene Typ; ohne Angabe application/octet-stream
|
| Content-Disposition | attachment mit dem übergebenen Namen, nach RFC 5987 kodiert
|
| Content-Length | Länge der Datei bzw. des angeforderten Bereichs |
| Accept-Ranges | bytes bei SendFile, none bei SendTempFile
|
| ETag, Last-Modified | nur bei SendFile, aus Grösse und Änderungszeitpunkt
|
Umlaute im Dateinamen sind unbedenklich. Header-Werte müssen ASCII sein,
deshalb schreibt der Server zwei Angaben: filename= mit einem
ausgeschriebenen Rückfallnamen (Rechnung_Müller.pdf wird zu
Rechnung_Mueller.pdf) und filename*= mit dem echten Namen
UTF-8-kodiert. Jeder heutige Browser nimmt den zweiten.
Der Pfad wird nicht geprüft. Ein Skript kann über Base.GFile
ohnehin jede Datei des Servers lesen; eine Schranke nur an dieser Stelle würde
Sicherheit vortäuschen, die es nicht gibt.
Endpunkt /public/{name}, der {name} an ein
Basisverzeichnis hängt, liefert bei ..%2F..%2Fobs.ini die
Konfiguration aus. Entweder eine feste Zuordnung im Skript (apk -> fester
Wiederaufnahme abgebrochener Downloads
Bei SendFile wertet der Server den Kopf Range aus.
Unterstützt wird ein Bereich je Anfrage, in den drei Formen, die Clients
tatsächlich senden:
| Range | Bedeutung |
|---|---|
bytes=0-1023 |
fester Bereich |
bytes=500- |
ab Offset bis zum Ende |
bytes=-500 |
die letzten 500 Bytes |
Der Server antwortet dann mit 206 Partial Content und
Content-Range: bytes <von>-<bis>/<gesamt>.
| Fall | Antwort |
|---|---|
Kein Range-Kopf |
200 mit der ganzen Datei |
| Mehrere Bereiche (Komma) | 200 mit der ganzen Datei - die Spezifikation erlaubt das ausdrücklich |
| Bereich hinter dem Dateiende | 416 Range Not Satisfiable mit Content-Range: bytes */<gesamt>
|
If-Range stimmt nicht mehr |
200 mit der ganzen Datei |
Warum If-Range zählt: Setzt ein Client einen Download fort und
die Datei hat sich zwischenzeitlich geändert, entstünde aus zwei Ständen eine
Datei, die erst beim Öffnen auffällt. Schickt der Client seinen ETag in
If-Range mit und passt der nicht mehr, liefert der Server lieber alles
neu.
Lücke: Die Datei ist nach dem Senden gelöscht, ein zweiter Abruf fände sie nicht
mehr vor - und eine Neuerzeugung lieferte andere Bytes, sobald sich die Daten
geändert haben. Der Server meldet deshalb Accept-Ranges: none, damit
Öffentliche Dateien ohne API-Key
Für Artefakte, die für jeden bestimmt sind - eine Test-APK, ein Handbuch - kann
der Endpunkt ohne apikey-Header erreichbar sein. Dafür genügt
Konfiguration, siehe
Zugänge:
- Zugang mit
ra_apikey = *,ra_jwt = 0,ra_cors_originleer - In
RESTSRV_ACCESSnur diesen einen Endpunkt freigeben
Die zweite Zeile ist die eigentliche Grenze: Für jeden anderen Endpunkt antwortet der Server 403, auch wenn der Zugang existiert.
Endpunkt weder Mandant noch Rolle - es gibt nichts, woran er eine Berechtigung festmachen könnte ausser dem, was in der URL steht. Kundenbelege gehören deshalb
über den authentifizierten Weg, nicht hierher.Für eine Android-APK kommt hinzu: Content-Type
application/vnd.android.package-archive, am Gerät muss "Unbekannte
Apps installieren" für den Browser freigegeben sein, und die Signatur sollte über
alle Testversionen konstant bleiben - sonst gibt es beim späteren Wechsel in den
Play Store auf jedem Testgerät einen Installationskonflikt.
Grenzen
| Was | Stand |
|---|---|
| HEAD | wird nicht unterstützt (405). Browser brauchen es nicht; relevant nur für wget --continue, curl -C - und Download-Manager, die meist auf GET zurückfallen.
|
| Mehrere Bereiche je Anfrage | nicht unterstützt (ganze Datei mit 200) |
| Liegengebliebene Temp-Dateien | Bricht der Server mitten im Senden ab, bleibt eine SendTempFile-Datei stehen. Das temporäre Verzeichnis wird von aussen geleert.
|
| Idempotenz | Eine Dateiantwort wird nicht im Idempotenz-Store festgeschrieben - sie liesse sich nicht wiedergeben. Der Idempotency-Key bleibt danach benutzbar.
|