web-dev-qa-db-fra.com

REST API - traitement des fichiers (c'est-à-dire des images) - meilleures pratiques

Nous développons un serveur avec REST API, qui accepte les réponses avec JSON. Le problème est que vous devez télécharger des images du client au serveur.

Notez également que je parle de cas d'utilisation, où entité (utilisateur) peut avoir des fichiers (carPhoto, licensePhoto) et également d'autres propriétés (nom, email, etc.), mais lorsque vous créez un nouvel utilisateur, vous n'envoyez pas ces images. , ils sont ajoutés après le processus d'inscription.


Les solutions que je connais, mais chacune d’entre elles ont des défauts

1. Utilisez multipart/form-data au lieu de JSON

good : POST et les requêtes PUT sont aussi RESTful que possible, elles peuvent contenir des entrées de texte avec le fichier.

contre : Ce n'est plus du JSON, qui est beaucoup plus facile à tester, déboguer, etc., comparé à multipart/form-data

2. Permet de mettre à jour des fichiers séparés

La requête POST pour créer un nouvel utilisateur ne permet pas d'ajouter des images (ce qui est correct dans notre cas d'utilisation comme je l'ai dit au début), le téléchargement des images est effectué par la requête PUT sous forme de multipart/form-data, par exemple,/users/4/carPhoto

good : Tout (sauf le fichier qui se télécharge lui-même) reste en JSON, il est facile de tester et de déboguer (vous pouvez enregistrer des requêtes JSON complètes sans craindre leur longueur)

inconvénients : Ce n'est pas intuitif, vous ne pouvez pas POST ou PUT toutes les variables d'une entité à la fois et cette adresse /users/4/carPhoto peut être considéré davantage comme une collection (le cas d'utilisation standard de REST API ressemble à ceci /users/4/shipments). Généralement, vous ne pouvez pas (et ne voulez pas) GET/PUT chaque variable d’entité, par exemple utilisateurs/4/nom. Vous pouvez obtenir le nom avec GET et le changer avec PUT à utilisateurs/4. S'il y a quelque chose après l'id, il s'agit généralement d'une autre collection, comme users/4/reviews

. Utilisez Base64

Envoyez-le en tant que JSON mais encodez les fichiers avec Base64.

good : Identique à la première solution, il s'agit d'un service aussi reposant que possible.

inconvénients : Une fois encore, le test et le débogage sont bien pires (le corps peut contenir des mégaoctets de données), la taille augmente ainsi que le temps de traitement. dans les deux - client et serveur


Je voudrais vraiment utiliser la solution no. 2, mais il a ses inconvénients ... Tout le monde peut me donner une meilleure idée de la solution "Quel est le meilleur"?

Mon objectif est de disposer de services RESTful avec autant de normes que possible, tout en souhaitant les garder aussi simples que possible.

108
libik

OP ici (Je réponds à cette question après deux ans, le message de Daniel Cerecedo n'était pas mauvais à la fois, mais les services Web se développent très rapidement)

Après trois ans de développement logiciel à temps plein (avec une attention particulière portée à l'architecture logicielle, à la gestion de projet et à l'architecture de microservice), j'ai définitivement choisi la deuxième voie (mais avec un critère final) comme la meilleure.

Si vous avez un point de terminaison spécial pour les images, cela vous donne beaucoup plus de pouvoir pour gérer ces images.

Nous avons la même REST API (Node.js) pour les applications mobiles (iOS/Android) et frontales (à l'aide de React). Nous sommes en 2017. Par conséquent, vous ne souhaitez pas stocker les images localement, vous souhaitez les télécharger pour les stocker dans le cloud (Google Cloud, s3, cloudinary, ...), donc vous souhaitez un traitement général.

Notre flux typique est que, dès que vous sélectionnez une image, elle commence à télécharger en arrière-plan (généralement POST sur/images/points finaux), et vous renvoie l'identifiant après le téléchargement. C’est vraiment convivial, parce que l’utilisateur choisit une image, puis procède généralement avec d’autres champs (adresse, nom, ...). Ainsi, lorsqu’il clique sur le bouton "Envoyer", l’image est généralement déjà téléchargée. Il n'attend pas et regarde l'écran en disant "uploader ...".

La même chose vaut pour obtenir des images. Surtout grâce aux téléphones mobiles et aux données mobiles limitées, vous ne voulez pas envoyer d'images originales, vous voulez envoyer des images redimensionnées, afin qu'elles ne prennent pas trop de bande passante (et pour rendre vos applications mobiles plus rapides, vous ne voulez souvent pas pour le redimensionner, vous voulez une image qui s’intègre parfaitement dans votre vue). Pour cette raison, les bonnes applications utilisent quelque chose comme cloudinary (ou nous avons notre propre serveur d'images pour le redimensionnement).

De plus, si les données ne sont pas privées, vous renvoyez simplement à URL app/frontend et il les télécharge directement à partir du stockage en nuage, ce qui représente une économie considérable de bande passante et de temps de traitement pour votre serveur. Dans nos plus grandes applications, il y a beaucoup de téraoctets téléchargés chaque mois, vous ne voulez pas gérer cela directement sur chacun de votre serveur API REST, qui est axé sur le fonctionnement de CRUD. Vous voulez gérer cela à un endroit (notre serveur d'images, qui dispose de la mise en cache, etc.) ou laisser les services cloud gérer tout cela.


Inconvénients: Le seul "inconvénient" auquel vous devriez penser est "images non attribuées". L'utilisateur sélectionne les images et continue de remplir les autres champs, mais il dit ensuite "nah" et désactive l'application ou l'onglet, mais vous avez réussi à transférer l'image. Cela signifie que vous avez téléchargé une image qui n'est assignée nulle part.

Il y a plusieurs façons de gérer cela. Le plus simple est "Je m'en moque", ce qui est pertinent si cela ne se produit pas très souvent ou si vous avez même envie de stocker chaque image que l'utilisateur vous envoie (pour quelque raison que ce soit) et que vous n'en voulez pas. effacement.

Une autre solution est également simple: vous disposez de CRON et c’est-à-dire chaque semaine et vous supprimez toutes les images non attribuées de plus d’une semaine.

90
libik

Il y a plusieurs décisions à prendre :

  1. Le premier concernant le chemin d'accès aux ressources :

    • Modélisez l'image comme une ressource en soi:

      • Nested in user (/ user /: id/image): la relation entre l'utilisateur et l'image est faite implicitement

      • Dans le chemin racine (/ image):

        • Le client est tenu pour responsable d'établir la relation entre l'image et l'utilisateur, ou;

        • Si un contexte de sécurité est fourni avec la demande POST utilisée pour créer une image, le serveur peut établir implicitement une relation entre l'utilisateur authentifié et l'image.

    • Intégrer l'image dans le cadre de l'utilisateur

  2. La seconde décision concerne comment représenter la ressource image :

    • Charge utile JSON codée en base 64
    • En tant que charge utile en plusieurs parties

Ce serait ma piste de décision:

  • Je privilégie généralement le design aux performances, à moins que les arguments ne le justifient pas. Cela rend le système plus facile à gérer et peut être plus facilement compris par les intégrateurs.
  • Ma première idée est donc de choisir une représentation de la ressource image en Base64, car elle vous permet de tout conserver en JSON. Si vous avez choisi cette option, vous pouvez modéliser le chemin de la ressource comme bon vous semble.
    • Si la relation entre l'utilisateur et l'image est de 1 à 1, il serait préférable de modéliser l'image en tant qu'attribut, spécialement si les deux ensembles de données sont mis à jour en même temps. Dans tous les autres cas, vous pouvez librement choisir de modéliser l'image soit en tant qu'attribut, en la mettant à jour via PUT ou PATCH, soit en tant que ressource distincte.
  • Si vous choisissez une charge en plusieurs parties, je me sentirais obligé de modéliser l'image sous la forme d'une ressource propre, de sorte que d'autres ressources, dans notre cas, la ressource utilisateur, ne soient pas affectées par la décision d'utiliser une représentation binaire pour l'image.

Vient ensuite la question: Le choix entre base64 et multipartes at-il un impact sur les performances . Nous pourrions penser que l'échange de données en plusieurs parties devrait être plus efficace. Mais cet article montre à quel point les deux représentations diffèrent peu en termes de taille.

Mon choix base64:

  • Décision de conception cohérente
  • Impact négligeable sur les performances
  • Comme les navigateurs comprennent les URI de données (images codées en base64), il n’est pas nécessaire de les transformer si le client est un navigateur.
  • Je ne voterai pas sur le fait de l'avoir comme attribut ou comme ressource autonome, cela dépend de votre domaine de problème (que je ne connais pas) et de vos préférences personnelles.
77
Daniel Cerecedo

Votre deuxième solution est probablement la plus correcte. Vous devez utiliser les spécifications et les types MIME HTTP comme ils étaient prévus et télécharger le fichier via multipart/form-data. En ce qui concerne la gestion des relations, j'utiliserais ce processus (en gardant à l'esprit que je ne connais rien de vos hypothèses ou de la conception du système):

  1. POST à /users pour créer l'entité utilisateur.
  2. POST l'image à /images, en veillant à renvoyer un en-tête Location à l'endroit où l'image peut être récupérée conformément à la spécification HTTP.
  3. PATCH à /users/carPhoto et lui attribuer l'identifiant de la photo indiqué dans l'en-tête Location de l'étape 2.
9
mam8cc

Il n'y a pas de solution facile. Chaque voie a ses avantages et ses inconvénients. Mais la manière canonique utilise la première option: multipart/form-data. Comme guide de recommandation W dit

Le type de contenu "multipart/form-data" doit être utilisé pour soumettre des formulaires contenant des fichiers, des données non-ASCII et des données binaires.

Nous n’envoyons pas de formulaires, vraiment, mais le principe implicite s’applique toujours. L'utilisation de base64 en tant que représentation binaire est incorrecte car vous utilisez un outil incorrect pour atteindre votre objectif. En revanche, la deuxième option oblige vos clients API à effectuer davantage de travaux afin de consommer votre service d'API. Vous devriez faire le travail dur côté serveur afin de fournir une API facile à utiliser. La première option n'est pas facile à déboguer, mais lorsque vous le faites, cela ne change probablement jamais.

En utilisant multipart/form-data, vous adhérez à la philosophie REST/http. Vous pouvez voir une réponse à une question similaire ici .

Une autre option si vous mélangez les alternatives, vous pouvez utiliser multipart/form-data, mais au lieu d’envoyer chaque valeur séparément, vous pouvez envoyer une valeur nommée payload avec la charge json qu’il contient. (J'ai essayé cette approche en utilisant ASP.NET WebAPI 2 et fonctionne bien).

2
Kellerman Rivero