web-dev-qa-db-fra.com

Comment une API REST doit-elle gérer les requêtes PUT vers des ressources partiellement modifiables?

Supposons qu'une API REST, en réponse à une demande HTTP GET, renvoie des données supplémentaires dans un sous-objet owner:

{
  id: 'xyz',
  ... some other data ...
  owner: {
    name: 'Jo Bloggs',
    role: 'Programmer'
  }
}

De toute évidence, nous ne voulons pas que quiconque puisse PUT revenir

{
  id: 'xyz',
  ... some other data ...
  owner: {
    name: 'Jo Bloggs',
    role: 'CEO'
  }
}

et que cela réussisse. En effet, nous n'allons probablement même pas implémenter un moyen pour que cela réussisse même potentiellement, dans ce cas.

Mais cette question ne concerne pas uniquement les sous-objets: que faire en général des données qui ne doivent pas être modifiables dans une requête PUT?

Doit-il être nécessaire de ne pas figurer dans la demande PUT?

Doit-il être rejeté en silence?

Doit-il être vérifié et s'il diffère de l'ancienne valeur de cet attribut, renvoyer un code d'erreur HTTP dans la réponse?

Ou devrions-nous utiliser les correctifs JSON RFC 6902 au lieu d'envoyer l'intégralité du JSON?

50
Robin Green

Il n'y a aucune règle, ni dans la spécification W3C ni dans les règles non officielles de REST, qui dit qu'un PUT doit utiliser le même schéma/modèle que son GET correspondant.

C'est bien s'ils sont similaires, mais il n'est pas rare que PUT fasse les choses légèrement différemment. Par exemple, j'ai vu beaucoup d'API qui incluent une sorte d'ID dans le contenu renvoyé par un GET, pour plus de commodité. Mais avec un PUT, cet ID est déterminé exclusivement par l'URI et n'a aucune signification dans le contenu. Tout identifiant trouvé dans le corps sera ignoré en silence.

REST et le web en général sont fortement liés au principe de robustesse : "Soyez prudent dans ce que vous faites [envoyez], soyez libéral dans ce que vous acceptez." Si vous êtes philosophiquement d'accord avec cela, alors la solution est évidente: Ignorez toutes les données invalides dans les requêtes PUT. Cela s'applique à la fois aux données immuables, comme dans votre exemple, et aux absurdités réelles, par exemple champs inconnus.

PATCH est potentiellement une autre option, mais vous ne devez pas implémenter PATCH sauf si vous allez réellement prendre en charge des mises à jour partielles. PATCH signifie ne mettez à jour que les attributs spécifiques que j'inclus dans le contenu; cela ne signifie pas remplace l'entité entière mais exclut certains champs spécifiques. Ce dont vous parlez n'est pas vraiment une mise à jour partielle, c'est une mise à jour complète, idempotente et tout, c'est juste qu'une partie de la ressource est en lecture seule.

Une bonne chose à faire si vous choisissez cette option serait de renvoyer un 200 (OK) avec l'entité réellement mise à jour dans la réponse, afin que les clients puissent clairement voir que les champs en lecture seule n'ont pas été mis à jour.

Il y a certainement certaines personnes qui pensent dans l'autre sens - que ce devrait être une erreur d'essayer de mettre à jour une partie en lecture seule d'une ressource. Il y a une justification à cela, principalement sur la base que vous renverriez certainement une erreur si la ressource entier était en lecture seule et que l'utilisateur tentait de la mettre à jour. Cela va certainement à l'encontre du principe de robustesse, mais vous pourriez le considérer comme plus "auto-documenté" pour les utilisateurs de votre API.

Il existe deux conventions pour cela, qui correspondent toutes deux à vos idées originales, mais je vais les développer. La première consiste à interdire aux champs en lecture seule d'apparaître dans le contenu et à renvoyer un HTTP 400 (Bad Request) s'ils le font. Les API de ce type doivent également renvoyer un HTTP 400 s'il existe d'autres champs non reconnus/inutilisables. La seconde consiste à exiger que les champs en lecture seule soient identiques au contenu actuel et à renvoyer un 409 (conflit) si les valeurs ne correspondent pas.

Je n'aime vraiment pas le contrôle d'égalité avec 409 car il oblige invariablement le client à faire un GET afin de récupérer les données actuelles avant de pouvoir faire un PUT. Ce n'est tout simplement pas Nice et cela va probablement conduire à de mauvaises performances, pour quelqu'un, quelque part. J'ai aussi vraiment n'aime pas 403 (interdit) pour cela car cela implique que la ressource entière est protégée, pas seulement une partie de celle-ci. Donc, à mon avis, si vous devez absolument valider au lieu de suivre le principe de robustesse, validez tous de vos demandes et retournez 400 pour ceux qui ont des champs supplémentaires ou non inscriptibles.

Assurez-vous que votre 400/409/tout ce qui comprend des informations sur le problème spécifique et comment le résoudre.

Ces deux approches sont valables, mais je préfère la première conformément au principe de robustesse. Si vous avez déjà travaillé avec avec une grande REST, vous apprécierez la valeur de la compatibilité descendante. Si vous décidez de supprimer un champ existant ou le rendre en lecture seule, c'est une modification rétrocompatible si le serveur ignore simplement ces champs, et les anciens clients continueront de fonctionner. Cependant, si vous effectuez une validation stricte sur le contenu, c'est pas = rétrocompatible et les anciens clients cesseront de fonctionner. Le premier signifie généralement moins de travail pour le mainteneur d'une API et ses clients.

51
Aaronaught

Puissance idem

Après la RFC, un PUT devrait fournir un objet complet à la ressource. La raison principale de ceci est que PUT doit être idempotent. Cela signifie qu'une demande, qui est répétée, doit aboutir au même résultat sur le serveur.

Si vous autorisez des mises à jour partielles, elles ne peuvent plus être idem-puissantes. Si vous avez deux clients. Client A et B, le scénario suivant peut évoluer:

Le client A obtient une image à partir d'images de ressources. Il contient une description de l'image, qui est toujours valide. Le client B met une nouvelle image et met à jour la description en conséquence. L'image a changé. Le client A voit, il n'a pas à changer la description, car c'est comme il le souhaite et ne met que l'image.

Cela conduira à une incohérence, l'image a les mauvaises métadonnées attachées!

Plus ennuyeux encore, tout intermédiaire peut répéter la demande. Au cas où il déciderait que le PUT a échoué.

La signification de PUT ne peut pas être modifiée (bien que vous puissiez en abuser).

Autres options

Heureusement, il existe une autre option, c'est PATCH. PATCH est une méthode qui vous permet de mettre à jour partiellement une structure. Vous pouvez simplement envoyer une structure partielle. Pour les applications simples, c'est très bien. Cette méthode n'est pas garantie d'être efficace. Le client doit envoyer une demande sous la forme suivante:

PATCH /file.txt HTTP/1.1
Host: www.example.com
Content-Type: application/example
If-Match: "e0023aa4e"
Content-Length: 20
{fielda: 1, fieldc: 2}

Et le serveur peut répondre avec 204 (aucun contenu) pour signaler le succès. En cas d'erreur, vous ne pouvez pas mettre à jour une partie de la structure. La méthode PATCH est atomique.

L'inconvénient de cette méthode est que tous les navigateurs ne le prennent pas en charge, mais c'est l'option la plus naturelle dans un service REST.

Exemple de demande de patch:http://tools.ietf.org/html/rfc5789#section-2.1

Correctif Json

L'option json semble être assez complète et intéressante. Mais il peut être difficile à mettre en œuvre pour des tiers. Vous devez décider si votre base d'utilisateurs peut gérer cela.

Il est également quelque peu compliqué, car vous devez construire un petit interpréteur qui convertit les commandes en une structure partielle, que vous allez utiliser pour mettre à jour votre modèle. Cet interpréteur doit également vérifier si les commandes fournies sont pertinentes. Certaines commandes s'annulent. (écrire fielda, supprimer fielda). Je pense que vous voulez signaler cela au client pour limiter le temps de débogage de son côté.

Mais si vous avez le temps, c'est une solution vraiment élégante. Vous devez toujours valider les champs bien sûr. Vous pouvez combiner cela avec la méthode PATCH pour rester dans le modèle REST. Mais je pense que POST serait acceptable ici).

Ça va mal

Si vous décidez d'opter pour l'option PUT, ce qui est quelque peu risqué. Ensuite, vous ne devriez pas au moins ignorer l'erreur. L'utilisateur a une certaine attente (les données seront mises à jour) et si vous cassez cela, vous allez donner à certains développeurs un mauvais moment.

Vous pouvez choisir de signaler en arrière: 409 Conflict ou 403 Forbidden. Cela dépend de la façon dont vous regardez le processus de mise à jour. Si vous le voyez comme un ensemble de règles (centrées sur le système), le conflit sera plus agréable. Quelque chose comme, ces champs ne peuvent pas être mis à jour. (En conflit avec les règles). Si vous le voyez comme un problème d'autorisation (centré sur l'utilisateur), vous devez retourner interdit. Avec: vous n'êtes pas autorisé à modifier ces champs.

Vous devez toujours obliger les utilisateurs à envoyer tous les champs modifiables.

Une option raisonnable pour appliquer cela est de le définir sur une sous-ressource, qui ne propose que les données modifiables.

Opinion personnelle

Personnellement, j'irais (si vous n'avez pas à travailler avec des navigateurs) pour le modèle PATCH simple, puis je l'étendrais plus tard avec un processeur de patch JSON. Cela peut être fait en différenciant les mimetypes: Le type mime du patch json:

application/json-patch

Et json: application/json-patch

facilite sa mise en œuvre en deux phases.

10
Edgar Klerks