webentwicklung-frage-antwort-db.com.de

REST API - Datei (dh Bilder) Verarbeitung - Best Practices

Wir entwickeln Server mit REST API, die JSON akzeptiert und darauf antwortet. Das Problem ist, wenn Sie Bilder vom Client auf den Server hochladen müssen.

Beachten Sie auch, dass es sich um einen Anwendungsfall handelt, bei dem eine Entität (ein Benutzer) Dateien (carPhoto, licensePhoto) und andere Eigenschaften (Name, E-Mail ...) haben kann. Wenn Sie jedoch einen neuen Benutzer erstellen, senden Sie diese Bilder nicht werden sie nach dem Registrierungsprozess hinzugefügt.


Die Lösungen, die mir bekannt sind, weisen jedoch jeweils einige Mängel auf

1. Verwenden Sie multipart/form-data anstelle von JSON

gut : POST und PUT-Anforderungen sind so restvoll wie möglich, sie können Texteingaben zusammen mit einer Datei enthalten.

Nachteile : Es ist kein JSON mehr, das viel einfacher zu testen, zu debuggen usw. ist, verglichen mit mehrteiligen/Formulardaten

2. Aktualisierung einzelner Dateien zulassen

Bei der POST-Anforderung zum Erstellen eines neuen Benutzers können keine Bilder hinzugefügt werden (was in unserem Anwendungsfall, wie ich zu Beginn sagte, in Ordnung ist). Das Hochladen von Bildern erfolgt per PUT-Anforderung als mehrteilige/Formulardaten an beispielsweise/users/4/carPhoto

gut : Alles (außer dem Hochladen der Datei selbst) bleibt in JSON, es ist einfach zu testen und zu debuggen (Sie können vollständige JSON-Anforderungen protokollieren, ohne Angst zu haben) ihre Länge)

Nachteile : Es ist nicht intuitiv, Sie können POST oder PUT alle Variablen der Entität auf einmal und auch diese Adresse /users/4/carPhoto kann eher als Sammlung betrachtet werden (Standardanwendungsfall für REST API sieht so aus /users/4/shipments). Normalerweise können (und wollen) Sie nicht jede Variable der Entität GET/PUT, zum Beispiel users/4/name. Sie können den Namen mit GET erhalten und ihn mit PUT bei users/4 ändern. Wenn etwas nach der ID steht, handelt es sich normalerweise um eine andere Sammlung, z. B. users/4/reviews

. Verwenden Sie Base64

Senden Sie es als JSON, aber verschlüsseln Sie Dateien mit Base64.

gut : Wie bei der ersten Lösung ist es ein möglichst RESTvoller Service.

Nachteile : Auch hier ist das Testen und Debuggen viel schlimmer (der Körper kann Megabyte an Daten haben), die Größe nimmt zu und auch die Verarbeitungszeit nimmt zu in beiden - Client und Server


Ich würde wirklich gerne die Lösung Nr. 2, aber es hat seine Nachteile ... Jeder kann mir einen besseren Einblick in die "Was ist die beste" Lösung geben?

Mein Ziel ist es, RESTful-Services mit möglichst vielen Standards anzubieten, während ich es so einfach wie möglich halten möchte.

108
libik

OP hier (Ich beantworte diese Frage nach zwei Jahren. Der Beitrag von Daniel Cerecedo war nicht schlecht, aber die Webservices entwickeln sich sehr schnell.)

Nach dreijähriger Vollzeit-Softwareentwicklung (mit Schwerpunkt auf Softwarearchitektur, Projektmanagement und Microservice-Architektur) Ich wähle definitiv den zweiten Weg (aber mit einem allgemeinen Endpunkt) als den besten.

Wenn Sie einen speziellen Endpunkt für Bilder haben, haben Sie viel mehr Einfluss auf die Verarbeitung dieser Bilder.

Wir haben die gleiche REST API (Node.js) für mobile Apps (iOS/Android) und Frontend (mit React). Dies ist 2017, daher möchten Sie keine Bilder speichern lokal möchten Sie sie in den Cloud-Speicher hochladen (Google Cloud, S3, Cloudinary, ...), daher möchten Sie eine allgemeine Behandlung über sie.

Unser typischer Ablauf ist, dass sobald Sie ein Bild auswählen, es im Hintergrund hochgeladen wird (normalerweise POST on/images endpoint), und Ihnen die ID nach dem Hochladen zurückgibt. Dies ist wirklich benutzerfreundlich Da der Benutzer ein Bild auswählt und dann normalerweise mit einigen anderen Feldern (z. B. Adresse, Name, ...) fortfährt, ist das Bild normalerweise bereits hochgeladen, wenn er auf "Senden" klickt. Er wartet nicht und sieht zu, wie der Bildschirm sagt msgstr "Upload läuft ...".

Gleiches gilt für das Abrufen von Bildern. Insbesondere dank Mobiltelefonen und begrenzter mobiler Daten möchten Sie keine Originalbilder senden, Sie möchten Bilder mit geänderter Größe senden, damit sie nicht so viel Bandbreite beanspruchen (und um Ihre mobilen Apps schneller zu machen, möchten Sie häufig nicht Um die Größe zu ändern, möchten Sie das Bild, das perfekt in Ihre Ansicht passt. Aus diesem Grund verwenden gute Apps so etwas wie Cloudinary (oder wir haben einen eigenen Image-Server für die Größenänderung).

Wenn die Daten nicht privat sind, senden Sie sie einfach an die URL der App/des Frontends zurück und laden sie direkt aus dem Cloud-Speicher herunter. Dies spart enorm Bandbreite und Verarbeitungszeit für Ihren Server. In unseren größeren Apps werden jeden Monat viele Terabyte heruntergeladen. Sie möchten dies nicht direkt auf jedem Ihrer REST API-Server erledigen, der sich auf die CRUD-Operation konzentriert. Sie möchten erledigen Sie das an einem Ort (unser Imageserver, der Caching usw. hat) oder lassen Sie Cloud-Dienste alles erledigen.


Nachteile: Die einzigen "Nachteile", an die Sie denken sollten, sind "nicht zugewiesene Bilder". Der Benutzer wählt Bilder aus und füllt weitere Felder aus. Dann sagt er "nah" und schaltet die App oder den Tab aus. In der Zwischenzeit haben Sie das Bild erfolgreich hochgeladen. Das heißt, Sie haben ein Bild hochgeladen, das nirgendwo zugeordnet ist.

Es gibt verschiedene Möglichkeiten, damit umzugehen. Am einfachsten ist "Es ist mir egal", was relevant ist, wenn dies nicht sehr oft vorkommt oder Sie sogar den Wunsch haben, jedes Bild zu speichern, das Ihnen ein Benutzer sendet (aus irgendeinem Grund), und Sie möchten keines Streichung.

Eine andere ist ebenfalls einfach: Sie haben CRON und zwar jede Woche, und Sie löschen alle nicht zugewiesenen Bilder, die älter als eine Woche sind.

90
libik

Es gibt mehrere Entscheidungen zu treffen :

  1. Der erste über Ressourcenpfad :

    • Modellieren Sie das Bild als eigenständige Ressource:

      • In Benutzer verschachtelt (/ Benutzer /: ID/Bild): Die Beziehung zwischen dem Benutzer und dem Bild wird implizit hergestellt

      • Im Wurzelpfad (/ image):

        • Der Kunde ist dafür verantwortlich, die Beziehung zwischen dem Bild und dem Benutzer herzustellen, oder

        • Wenn ein Sicherheitskontext mit der Anforderung POST= zur Erstellung eines Images bereitgestellt wird, kann der Server implizit eine Beziehung zwischen dem authentifizierten Benutzer und dem Image herstellen.

    • Betten Sie das Bild als Teil des Benutzers ein

  2. Die zweite Entscheidung betrifft die Darstellung der Bildressource :

    • Als Base 64 kodierte JSON-Payload
    • Als mehrteilige Nutzlast

Dies wäre mein Entscheidungsweg:

  • Ich bevorzuge normalerweise das Design gegenüber der Leistung, es sei denn, es gibt ein starkes Argument dafür. Dadurch wird das System wartungsfreundlicher und kann von Integratoren leichter verstanden werden.
  • Mein erster Gedanke ist also, eine Base64-Darstellung der Image-Ressource zu wählen, da Sie damit alles in JSON behalten können. Wenn Sie diese Option auswählen, können Sie den Ressourcenpfad nach Ihren Wünschen modellieren.
    • Wenn die Beziehung zwischen Benutzer und Bild 1 zu 1 ist, würde ich es vorziehen, das Bild als Attribut zu modellieren, insbesondere wenn beide Datensätze gleichzeitig aktualisiert werden. In jedem anderen Fall können Sie das Bild nach Belieben als Attribut modellieren, es über PUT oder PATCH aktualisieren oder als separate Ressource.
  • Wenn Sie sich für mehrteilige Nutzdaten entscheiden, muss das Image als eigenständige Ressource modelliert werden, damit andere Ressourcen, in unserem Fall die Benutzerressource, nicht von der Entscheidung betroffen sind, eine Binärdarstellung für das Image zu verwenden.

Dann kommt die Frage: Hat die Auswahl von Base64 gegenüber Multipart einen Einfluss auf die Leistung? . Wir könnten uns vorstellen, dass der Datenaustausch im mehrteiligen Format effizienter sein sollte. Aber dieser Artikel zeigt, wie wenig sich beide Darstellungen in Bezug auf die Größe unterscheiden.

Meine Wahl Base64:

  • Konsequente Designentscheidung
  • Vernachlässigbare Leistungseinbußen
  • Da Browser Daten-URIs (Base64-codierte Bilder) verstehen, müssen diese nicht transformiert werden, wenn der Client ein Browser ist
  • Ich werde nicht darüber abstimmen, ob es ein Attribut oder eine eigenständige Ressource sein soll, es hängt von Ihrer Problemdomäne (die ich nicht kenne) und Ihren persönlichen Vorlieben ab.
77
Daniel Cerecedo

Ihre zweite Lösung ist wahrscheinlich die richtigste. Sie sollten die HTTP-Spezifikation und die Mimetypen so verwenden, wie sie beabsichtigt waren, und die Datei über multipart/form-data Hochladen. Um die Beziehungen zu handhaben, würde ich diesen Prozess verwenden (wenn ich bedenke, dass ich nichts über Ihre Annahmen oder Ihr Systemdesign weiß):

  1. POST bis /users, um die Benutzerentität zu erstellen.
  2. POST das Bild auf /images und stellen Sie sicher, dass ein Location Header zurückgegeben wird, in den das Bild gemäß der HTTP-Spezifikation abgerufen werden kann.
  3. PATCH bis /users/carPhoto und weisen Sie ihm die ID des Fotos zu, die in der Überschrift Location von Schritt 2 angegeben ist.
9
mam8cc

Es gibt keine einfache Lösung. Jeder Weg hat seine Vor- und Nachteile. Aber der kanonische Weg benutzt die erste Option: multipart/form-data. Wie W3 Empfehlungsleitfaden sagt

Der Inhaltstyp "Multipart/Formulardaten" sollte zum Senden von Formularen verwendet werden, die Dateien, Nicht-ASCII-Daten und Binärdaten enthalten.

Wir senden eigentlich keine Formulare, aber das implizite Prinzip gilt immer noch. Die Verwendung von base64 als Binärdarstellung ist nicht korrekt, da Sie das falsche Tool verwenden, um Ihr Ziel zu erreichen. Andererseits zwingt die zweite Option Ihre API-Clients, mehr Arbeit zu leisten, um Ihren API-Service zu nutzen. Sie sollten die harte Arbeit auf der Serverseite erledigen, um eine benutzerfreundliche API bereitzustellen. Die erste Option ist nicht einfach zu debuggen, aber wenn Sie es tun, wird es wahrscheinlich nie geändert.

Mit multipart/form-data Sie halten an der REST/http-Philosophie fest. Sie können eine Antwort auf eine ähnliche Frage anzeigen hier .

Wenn Sie die Alternativen mischen, können Sie auch Multipart-/Formulardaten verwenden. Anstatt jedoch jeden Wert einzeln zu senden, können Sie einen Wert namens payload mit der darin enthaltenen json-Payload senden. (Ich habe diesen Ansatz mit ASP.NET WebAPI 2 ausprobiert und funktioniert einwandfrei).

2