web-dev-qa-db-fra.com

Quelle est la meilleure méthode RESTful pour renvoyer le nombre total d’éléments dans un objet?

Je développe un service d'API REST) pour un grand site de réseau social auquel je participe. Jusqu'à présent, cela fonctionne très bien. Je peux publier GET, POST, PUT et DELETE demandes d’objet URL et d’affecter mes données, mais ces données sont paginées (limitées à 30 résultats à la fois).

Cependant, quel serait le meilleur moyen RESTful d’obtenir le nombre total de membres, par exemple, via mon API?

Actuellement, j'émets des demandes à une structure d'URL comme celle-ci:

  • / api/members - Retourne une liste de membres (30 à la fois comme mentionné ci-dessus)
  • / api/members/1 - Affecte un seul membre, en fonction de la méthode de requête utilisée

Ma question est la suivante: comment utiliser une structure d'URL similaire pour obtenir le nombre total de membres de ma candidature? De toute évidence, demander uniquement le champ id (similaire à l’API graphique de Facebook) et compter les résultats serait inefficace, car seule une tranche de 30 résultats serait renvoyée.

123
Martin Bean

Bien que la réponse à/API/utilisateurs soit paginée et ne renvoie que 30 enregistrements, rien ne vous empêche d'inclure également dans la réponse le nombre total d'enregistrements et d'autres informations pertinentes, telles que la taille de la page, le numéro/offset de page, etc. .

L'API StackOverflow est un bon exemple de cette même conception. Voici la documentation pour la méthode Users - https://api.stackexchange.com/docs/users

78
Franci Penov

Je préfère utiliser les en-têtes HTTP pour ce type d'informations contextuelles.

Pour le nombre total d'éléments que j'utilise X-total-count entête.
Pour les liens vers la page suivante, la page précédente, etc. J'utilise http Link header:
http://www.w3.org/wiki/LinkHeader

Github le fait de la même manière: https://developer.github.com/v3/#pagination

À mon avis, c'est plus propre car il peut être utilisé également lorsque vous renvoyez un contenu qui ne prend pas en charge les liens hypertexte (binaires, images, etc.).

62
Ondrej Bozek

J'ai effectué des recherches approfondies sur ce sujet et d'autres REST questions relatives à la pagination récemment, et j'ai trouvé constructif d'ajouter certaines de mes conclusions ici. J'élargis un peu la question pour inclure des réflexions sur la pagination ainsi que le compte car ils sont intimement liés.

En-têtes

Les métadonnées de pagination sont incluses dans la réponse sous la forme d'en-têtes de réponse. Le gros avantage de cette approche est que la charge utile de réponse elle-même est simplement celle que demandait le demandeur de données. Faciliter le traitement de la réponse pour les clients qui ne sont pas intéressés par les informations de pagination.

De nombreux en-têtes (standard et personnalisés) sont utilisés dans la nature pour renvoyer des informations relatives à la pagination, y compris le nombre total.

X-Total-Count

X-Total-Count: 234

Ceci est utilisé dans certainsAPI j'ai trouvé à l'état sauvage. Il existe également packages NPM pour ajouter la prise en charge de cet en-tête, par exemple. Bouclage. Certains articles recommandent également de définir cet en-tête.

Il est souvent utilisé en combinaison avec l'en-tête Link, ce qui est une très bonne solution pour la pagination, mais il manque les informations relatives au nombre total.

Lien

Link: </TheBook/chapter2>;
      rel="previous"; title*=UTF-8'de'letztes%20Kapitel,
      </TheBook/chapter4>;
      rel="next"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel

Je pense, après avoir lu beaucoup de choses à ce sujet, que le consensus général est d’utiliser le Link header pour fournir des liens de pagination aux clients utilisant rel=next, rel=previous etc. Le problème est qu’il manque le nombre total d’enregistrements. C’est pourquoi de nombreuses API combinent cela avec le X-Total-Count entête.

En variante, certaines API et par exemple Pour le standard JsonApi , utilisez le format Link, mais ajoutez les informations dans une enveloppe de réponse plutôt que dans un en-tête. Cela simplifie l'accès aux métadonnées (et crée un emplacement pour ajouter les informations de comptage total) aux dépens de la complexité croissante de l'accès aux données elles-mêmes (en ajoutant une enveloppe).

Gamme de contenu

Content-Range: items 0-49/234

Promu par un article de blog nommé en-tête de plage, je vous choisis (pour la pagination)! . L’auteur plaide en faveur de l’utilisation de Range et de Content-Range en-têtes pour la pagination. Lorsque nous lisons attentivement le RFC sur ces en-têtes, nous constatons que l'extension de leur signification au-delà des plages d'octets était en fait anticipée par la RFC et est explicitement autorisé. Lorsqu'il est utilisé dans le contexte de items au lieu de bytes, l'en-tête Range nous donne en fait le moyen de demander une certaine plage d'éléments et d'indiquer à quelle plage du résultat total se rapportent les éléments de réponse. . Cet en-tête offre également un excellent moyen d’afficher le nombre total. Et il s’agit d’un véritable standard qui associe généralement les messages un à un à la pagination. C'est aussi tilisé dans la nature .

Enveloppe

De nombreuses API, y compris celle de notre site Web Q & A préféré utilisent une enveloppe , un wrapper autour des données utilisées pour ajouter méta-informations sur les données. En outre, les normes OData et JsonApi utilisent toutes deux une enveloppe de réponse.

Le gros inconvénient de ceci (à mon humble avis) est que le traitement des données de réponse devient plus complexe car les données réelles doivent être trouvées quelque part dans l'enveloppe. Il existe également de nombreux formats différents pour cette enveloppe et vous devez utiliser le bon. Il est révélateur que les enveloppes de réponse d'OData et de JsonApi sont extrêmement différentes, OData mélangeant des métadonnées à plusieurs points de la réponse.

Endpoint séparé

Je pense que cela a été suffisamment couvert dans les autres réponses. Je ne me suis pas beaucoup renseigné sur cette question car je partage l’opinion selon laquelle cela prête à confusion, car vous disposez désormais de plusieurs types de terminaux. Je pense que c'est mieux si chaque point final représente une (collection de) ressource (s).

Autres réflexions

Nous devons non seulement communiquer les métadonnées de pagination liées à la réponse, mais également permettre au client de demander des pages/plages spécifiques. Il est intéressant d'examiner également cet aspect pour aboutir à une solution cohérente. Ici aussi, nous pouvons utiliser des en-têtes (l'entête Range semble bien convenir) ou d'autres mécanismes tels que les paramètres de requête. Certaines personnes recommandent de traiter les pages de résultats comme des ressources distinctes, ce qui peut avoir un sens dans certains cas d'utilisation (par exemple, /books/231/pages/52. J'ai fini par sélectionner une gamme sauvage de paramètres de requête fréquemment utilisés tels que pagesize, page[size] et limit etc en plus de supporter l’en-tête Range (et en tant que paramètre de requête).

56
Stijn de Witt

Vous pouvez renvoyer le compte sous la forme d'un en-tête HTTP personnalisé en réponse à une demande HEAD). Ainsi, si un client ne souhaite que le compte, vous n'avez pas besoin de renvoyer la liste réelle. pas besoin d'une URL supplémentaire.

(Ou, si vous êtes dans un environnement contrôlé d'un point de terminaison à l'autre, vous pouvez utiliser un verbe HTTP personnalisé tel que COUNT.)

22
bzlm

Alternative quand vous n'avez pas besoin d'objets réels

réponse de Franci Penov est certainement la meilleure solution. Vous renvoyez donc toujours les éléments avec toutes les métadonnées supplémentaires relatives aux entités que vous demandez. C'est la façon dont cela devrait être fait.

mais parfois, renvoyer toutes les données n'a pas de sens, parce que vous n'en avez peut-être pas besoin du tout. Vous n'avez peut-être besoin que de métadonnées sur la ressource demandée. Comme le nombre total ou le nombre de pages ou autre chose. Dans ce cas, vous pouvez toujours demander à votre service de requête d'URL de ne pas renvoyer d'éléments, mais simplement des métadonnées telles que:

/api/members?metaonly=true
/api/members?includeitems=0

ou quelque chose de similaire ...

21
Robert Koritnik

Je recommanderais d'ajouter des en-têtes pour les mêmes, comme:

HTTP/1.1 200

Pagination-Count: 100
Pagination-Page: 5
Pagination-Limit: 20
Content-Type: application/json

[
  {
    "id": 10,
    "name": "shirt",
    "color": "red",
    "price": "$23"
  },
  {
    "id": 11,
    "name": "shirt",
    "color": "blue",
    "price": "$25"
  }
]

Pour plus de détails, consultez:

https://github.com/adnan-kamili/rest-api-response-format

Pour le fichier swagger:

https://github.com/adnan-kamili/swagger-response-template

11
adnan kamili

À partir de "X -" - le préfixe était obsolète. (voir: https://tools.ietf.org/html/rfc6648 )

Nous avons trouvé que "Accept-Ranges" était le meilleur choix pour mapper la plage de pagination: https://tools.ietf.org/html/rfc7233#section-2. Comme les "Range Units" peuvent soit être "octets" ou "jeton". Les deux ne représentent pas un type de données personnalisé. (voir: https://tools.ietf.org/html/rfc7233#section-4.2 ) Il est néanmoins indiqué que

Les implémentations HTTP/1.1 PEUVENT ignorer les plages spécifiées en utilisant d'autres unités.

Ce qui indique: l'utilisation d'unités de plage personnalisées n'est pas contre le protocole, mais il PEUT être ignoré.

De cette façon, nous devrions définir Accept-Ranges sur "members" ou sur le type d'unité à distance souhaité. Et en outre, définissez également Content-Range sur la plage actuelle. (voir: https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.12 )

De toute façon, je m'en tiendrai à la recommandation de la RFC7233 ( https://tools.ietf.org/html/rfc7233#page-8 ) pour envoyer un 206 au lieu de 200:

Si toutes les conditions préalables sont remplies, le serveur prend en charge la plage.
champ d'en-tête pour la ressource cible et les plages spécifiées sont
valide et satisfaisable (comme défini dans la section 2.1), le serveur DEVRAIT
envoyer une réponse 206 (contenu partiel) avec une charge utile contenant un
ou plusieurs représentations partielles correspondant à la satisfiable
plages demandées, telles que définies à la section 4.

En conséquence, nous aurions les champs d'en-tête HTTP suivants:

Pour le contenu partiel:

206 Partial Content
Accept-Ranges: members
Content-Range: members 0-20/100

Pour le contenu complet:

200 OK
Accept-Ranges: members
Content-Range: members 0-20/20
5
Lepidopteron

Qu'en est-il d'un nouveau point de terminaison>/api/members/count qui appelle simplement Members.Count () et renvoie le résultat

2
Steve Woods

Semble le plus facile d’ajouter un

GET
/api/members/count

et renvoyer le nombre total de membres

2
willcodejavaforfood

Parfois, les frameworks (tels que $ resource/AngularJS) nécessitent un tableau en tant que résultat de requête, et vous ne pouvez pas avoir de réponse comme {count:10,items:[...]} dans ce cas, je stocke "count" dans responseHeaders.

P. S. En fait, vous pouvez le faire avec $ resource/AngularJS, mais cela nécessite quelques ajustements.

2
Vahe Hovhannisyan