CodamAIDocs
Themafertig

Hochladen

Die zwei Wege Multipart und Base64, die passenden Endpunkte und wie Dateiteil und Datei-Objekt über den Namen zusammenfinden.

Ausprägungen
POST /create/uploadPUT /update/{id}/uploadPATCH /update/{id}/uploadBase64 im JSONmehrere Dateien in einem RequestName passt nicht → 400doppelte Namen → abgelehnt

Worum es geht

Eine Datei kommt auf zwei Wegen zu CDMS:

  • Multipart: Die Anfrage besteht aus mehreren Teilen. Ein Teil ist das JSON, die anderen sind die Dateien. Das ist der normale Weg, besonders für größere Dateien.
  • Base64: Die Datei steht als Text im JSON selbst. Das ist bequem für kleine Dateien, etwa aus einem Skript, ist aber rund ein Drittel größer als die Datei.

Bei beiden Wegen gilt dieselbe Regel: Der Dateiteil und das Datei-Objekt in data finden über den Namen zusammen.

Multipart

Die /upload-Endpunkte erwarten multipart/form-data mit zwei Arten von Teilen:

TeilInhaltAnzahl
datadas JSON mit data und response, wie bei einem normalen Create oder Update, mit Content-Type application/jsongenau einer
fileseine Datei; ihr Dateiname muss zu einem name in data passenbeliebig viele, jeder mit einem anderen Namen
Ein Anhang mit Datei anlegen
Anfrage
POST /api/rest/fileasset/create/upload
Content-Type: multipart/form-data; boundary=XYZ

--XYZ
Content-Disposition: form-data; name="data"
Content-Type: application/json

{ "data": { "name": "Vertrag.pdf", "title": "Rahmenvertrag" },
  "response": ["id", "name", "mimeType", "fileSize"] }
--XYZ
Content-Disposition: form-data; name="files"; filename="Vertrag.pdf"
Content-Type: application/pdf

…Bytes der Datei…
--XYZ--
Antwort
{ "data": { "id": "f1…", "name": "Vertrag.pdf",
            "mimeType": "application/pdf", "fileSize": 48213 },
  "meta": { "error": false } }

mimeType und fileSize hat der Server gesetzt. Den Content-Type des Dateiteils wertet er nicht aus, der Dateityp kommt aus der Endung des Namens.

Im Browser baust du so eine Anfrage mit FormData:

Multipart aus dem Browser
Anfrage
const form = new FormData();
form.append("data", new Blob([JSON.stringify({
  data: { name: file.name, title: "Rahmenvertrag" },
  response: ["id", "name"]
})], { type: "application/json" }));
form.append("files", file, file.name);
fetch("/api/rest/fileasset/create/upload",
      { method: "POST", body: form, headers: { Authorization: "Bearer " + token } });
Wichtig
data als Blob mit type application/json anhängen,
die Datei unter dem Teilnamen "files",
und den Content-Type der Anfrage NICHT selbst setzen:
der Browser ergänzt die boundary.

Base64 im JSON

Statt eines Dateiteils schreibst du den Inhalt in das Feld content des Datei-Objekts. Das geht an den normalen JSON-Endpunkten POST /create, PUT und PATCH /update/{id}:

Dieselbe Datei als Base64
Anfrage
POST /api/rest/fileasset/create
{ "data": { "name": "notiz.txt", "title": "Notiz",
            "content": "SGFsbG8gV2VsdA==" },
  "response": ["id", "name", "fileSize"] }
Antwort
{ "data": { "id": "f2…", "name": "notiz.txt", "fileSize": 10 },
  "meta": { "error": false } }

content darf auch als Data-URL kommen, also mit Präfix wie data:text/plain;base64,. CDMS schneidet das Präfix ab. content wird nur zum Hochladen gelesen und nie gespeichert oder zurückgegeben.

Multipart oder Base64?
Multipart
/upload-Endpunkte
  • Bytes unverändert, keine Umrechnung
  • gut für große Dateien und Formulare mit Dateiauswahl
  • 25 MB je Datei, 500 MB je Request, siehe Größengrenzen
Base64
normale JSON-Endpunkte
  • alles in einem JSON, einfach aus Skripten
  • rund ein Drittel größer
  • ungültiges Base64 wird übergangen, dann fehlt die Datei
  • dieselben Größengrenzen wie Multipart

Mehrere Dateien und verschachtelte Dateien

Ein Request kann mehrere Dateien tragen, auch für Datei-Modelle, die als Kinder an einem anderen Objekt hängen. Jede Datei findet ihr Objekt über den Namen, egal wie tief es in data steht:

Akte mit zwei Anhängen
Anfrage
POST /api/rest/dossier/create/upload
data:  { "data": { "title": "Akte 1",
                   "attachments": [ { "name": "first.txt" },
                                    { "name": "second.txt" } ] },
         "response": ["id", { "field": "attachments", "response": ["+"] }] }
files: first.txt   (Dateiteil)
files: second.txt  (Dateiteil)
Antwort
Akte und beide Anhänge werden angelegt,
jeder Anhang mit seinem Inhalt.

Dafür bekommen auch Modelle ohne eigene Datei, wie hier die Akte, die /upload-Varianten. Das Kind braucht wie immer das CREATE-Flag an der Beziehung. Siehe Die vier Fälle beim verschachtelten Schreiben.

Der Ablauf

  1. 1
    Client→CDMS
    schickt data und die Dateiteile
  2. 2
    CDMS
    prüft die Dateiteile: jeder hat einen Namen, kein Name kommt doppelt vor
  3. 3
    CDMS
    legt jeden Teil als Zwischendatei ab, ebenso jedes content aus dem JSON
  4. 4
    CDMS
    geht data durch; für jedes Datei-Objekt: Rolle prüfen, Dateiteil über den name suchen
  5. 5
    CDMS→File storage
    bereitet den Inhalt vor und setzt fileId, fileSize, mimeType, fileVersion
  6. 6
    CDMS→Database
    Hooks, Validierung, Datensatz speichern, zurücklesen, Commit
  7. 7
    CDMS→File storage
    macht den Inhalt wirksam

Aktuell wird der Inhalt erst, wenn die Datenbank festgeschrieben hat. Scheitert die Anfrage vorher, wird er verworfen. Mehr dazu unter Dateien und Transaktion.

Wenn es nicht passt

Dateiteile und Datei-Objekte
SituationOperationErgebnis
Teil passt zum name eines Datei-ObjektsalleInhalt wird gespeichert
neues Datei-Objekt, kein passender Teilanlegen400 file-part-missing|<name>
neues Datei-Objekt ohne nameanlegen400 file-name-missing
bestehendes Datei-Objekt, kein passender Teilersetzen, ändernInhalt bleibt, wie er ist
zwei Teile mit demselben Dateinamenalle400 duplicate-filenames|<name>
Teil ohne Dateinamenalle400 missing-filename
Teil, zu dem kein Datei-Objekt passtalle400 file-part-unused|<namen>

Fallen

Wie es weitergeht

Quellen im Code und in der Wissensdatenbank
  • CDMS/cdms-generator – ApiProcessor (createMultipart, updateMultipart, patchMultipart: @RequestPart data, files), ApiHubProcessor, RestPayloadProcessor (name, content)
  • CDMS/cdms-rest-api – AbstractRestApi.getFileMap (missing-filename, duplicate-filenames), addFilesFromBase64/tryWriteTempFile (content, data:-Präfix), FileUploadException (413, nur Größe)
  • CDMS/cdms-system-layer – AbstractLayer.recursivePrepare (file-part-missing, file-name-missing, saveFile), recursivePatch (Dateizweig)
  • CDMS/cdms-integrationtest – AbstractRecursiveFileDeleteTest (company/create/upload mit logo.png, dossier mit zwei Anhängen), AbstractFileRollbackTest
Suchen