API REST 404: URI incorrect ou ressource manquante?


219

Je crée une API REST, mais j'ai rencontré un problème.

Il semble que la pratique acceptée dans la conception d'une API REST soit que si la ressource demandée n'existe pas, un 404 est retourné.

Cependant, pour moi, cela ajoute une ambiguïté inutile. HTTP 404 est plus traditionnellement associé à un mauvais URI. Donc en fait, nous disons "Soit vous êtes au bon endroit, mais cet enregistrement spécifique n'existe pas, ou il n'y a pas un tel emplacement sur les Internets! Je ne sais vraiment pas lequel ..."

Considérez l'URI suivant:

http://mywebsite/api/user/13

Si je récupère un 404, est-ce parce que l'utilisateur 13 n'existe pas? Ou est-ce parce que mon URL aurait dû être:

http://mywebsite/restapi/user/13

Dans le passé, je viens de renvoyer un résultat NULL avec un HTTP 200 OKcode de réponse si l'enregistrement n'existe pas. C'est simple, et à mon avis très propre, même si ce n'est pas forcément une pratique acceptée. Mais existe-t-il une meilleure façon de procéder?



7
L'autre question semble être liée aux formats de chaîne de requête URI. La discussion sur le 404 n'est pas suffisante pour répondre à ma question, qui est de savoir s'il existe un moyen plus approprié ou utile de déterminer ce que signifie réellement un 404. J'ai examiné celui-ci avant de poster.
Brian Lacy

Est-ce normal lorsque le navigateur concole comprend les erreurs 404? Quand je fais des opérations régulières avec un uri correct mais une ressource introuvable.
Vasiliy Mazhekin

3
404 n'indique pas un URI incorrect, il indique une ressource introuvable. Cela peut être dû au fait qu'il n'y a pas d'utilisateur «13», ou au fait qu'il n'y a pas de ressource / mon site Web / api.
ChrisV

Réponses:


101

404 est juste le code de réponse HTTP. En plus de cela, vous pouvez fournir un corps de réponse et / ou d'autres en-têtes avec un message d'erreur plus significatif que les développeurs verront.


7
C'est logique. Je dois me demander, cependant, si un avantage est réellement obtenu en retournant le 404 en premier lieu, par rapport au retour d'un 200 avec une réponse nulle?
Brian Lacy

15
Si vous retournez un 200 avec un null, vous allez à l'encontre de la spécification HTTP. Vous pouvez le faire, mais votre API n'adhérera pas à la contrainte "Interface uniforme" de REST.
poursuivre

5
... et votre corps de réponse doit inclure des hyperliens vers des URI valides. Sauf l'URI racine, et peut-être un ou deux signets, vos clients doivent toujours suivre les liens qui leur sont fournis par le serveur. Ensuite, il n'est pas nécessaire d'inventer une sémantique détaillée concernant exactement comment ils ont décidé de travailler en dehors du système.
fumanchu

@DarrylHebbes que voulez-vous dire, je peux faire une demande et voir le contenu complet de la réponse dans l'onglet Réseau de la console développeur Chrome.
jaapz

Il est incroyablement mauvais de renvoyer une erreur (404 ou autre) pour toute requête qui renvoie des résultats vides. Aucune base de données ne fera cela et généralement vous ne voulez pas signaler un résultat vide comme une condition inattendue à l'utilisateur. Exemple: vous interrogez votre API pour vérifier si un rendez-vous est prévu pour les 30 prochaines minutes. S'il n'y en a pas, vous ne devez PAS retourner un 404. Renvoyez un 200 OK et un tableau vide, s'il vous plaît.
esmiralha

59

À utiliser 404si la ressource n'existe pas. Ne revenez pas 200avec un corps vide.

Cela s'apparente à undefinedune chaîne vide (par exemple "") dans la programmation. Bien que très similaire, il y a certainement une différence.

404signifie que rien n'existe à cet URI (comme une variable non définie dans la programmation). Revenir 200avec un corps vide signifie que quelque chose existe là-bas et que quelque chose est juste vide en ce moment (comme une chaîne vide en programmation).

404ne signifie pas que c'était un "mauvais URI". Il existe des codes HTTP spéciaux destinés aux erreurs d'URI (par exemple 414 Request-URI Too Long).


1
Hé, je pense que vous pourriez être sur quelque chose. Ne serait-il pas plus logique de retourner l'un des «41»? des erreurs en cas de problème avec la ressource demandée? Par exemple, 410 Gone signifie "La ressource demandée n'est plus disponible sur le serveur et aucune adresse de transfert n'est connue." - (Voir w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.11 )
Brian Lacy

En fait, la ressource que j'ai référencée ci-dessus dit également: "Si le serveur ne sait pas ou n'a pas la possibilité de déterminer si la condition est permanente ou non, le code d'état 404 (Not Found) DEVRAIT être utilisé à la place." Alors peut-être que ce n'est pas nécessairement la meilleure option non plus.
Brian Lacy

2
Je ne pense pas que l'une des erreurs 41x soit appropriée à votre cas d'utilisation. J'aime la comparaison entre une chaîne non définie et une chaîne vide, qui est une version plus concise de mon argument dans ma réponse. Il y a une différence, mais cela n'implique pas qu'une chaîne vide soit une erreur. Pour étirer l'analogie: Vous pourriez avoir une méthode String getName()qui a une mise en œuvre comme ceci: return this.name == null ? "" : this.name. Ce n'est pas incorrect à moins que le client sache que this.nameest null.
jhericks

1
404 est toujours l'option la plus appropriée. Vous pouvez (et êtes encouragé à) utiliser le corps de cette réponse 404 pour informer l'utilisateur / client des détails spécifiques pour la réception de l'erreur. Voir: stackoverflow.com/a/9999335/105484 . Dans votre cas, vous voudrez peut-être utiliser ce corps pour suggérer à l'utilisateur de reformater son URI pour qu'il ressemble à / restapi / user / USER_ID
nategood

Ok, je peux voir quelques bons points ici, mais 4XX sont des codes d'erreur, comment est-ce une situation d'erreur? Comment le client peut-il empêcher le 404 de se produire?
Krzysztof Kubicki

20

Comme pour la plupart des choses, "cela dépend". Mais pour moi, votre pratique n'est pas mauvaise et ne va pas à l'encontre de la spécification HTTP en soi . Cependant, clarifions certaines choses.

Tout d'abord, les URI doivent être opaques. Même s'ils ne sont pas opaques pour les gens, ils sont opaques pour les machines. En d'autres termes, la différence entre http://mywebsite/api/user/13, http://mywebsite/restapi/user/13est la même que la différence entre http://mywebsite/api/user/13et http://mywebsite/api/user/14donc pas la même n'est pas la même période. Un 404 serait donc tout à fait approprié http://mywebsite/api/user/14(s'il n'y a pas un tel utilisateur) mais pas nécessairement la seule réponse appropriée.

Vous pouvez également renvoyer une réponse 200 vide ou plus explicitement une réponse 204 (sans contenu). Cela transmettrait autre chose au client. Cela impliquerait que la ressource identifiée par http://mywebsite/api/user/14n'a pas de contenu ou n'est essentiellement rien. Il ne signifie pas qu'il y a une telle ressource. Cependant, cela ne signifie pas nécessairement que vous prétendez qu'un utilisateur a persisté dans un magasin de données avec l'ID 14. C'est votre préoccupation privée, pas celle du client qui fait la demande. Donc, s'il est logique de modéliser vos ressources de cette façon, allez-y.

Il y a des implications de sécurité à donner à vos clients des informations qui leur permettraient de deviner plus facilement les URI légitimes. Renvoyer 200 échecs au lieu d'un 404 peut donner au client un indice qu'au moins la http://mywebsite/api/userpièce est correcte. Un client malveillant pourrait simplement continuer à essayer différents entiers. Mais pour moi, un client malveillant pourrait de http://mywebsite/api/usertoute façon deviner la pièce. Un meilleur remède serait d'utiliser les UUID. c'est-à http://mywebsite/api/user/3dd5b770-79ea-11e1-b0c4-0800200c9a66- dire mieux que http://mywebsite/api/user/14. Ce faisant, vous pouvez utiliser votre technique de retour de 200 sans donner beaucoup.


1
C'est la solution que j'ai choisie et j'ai utilisé un 204 au lieu d'un 404. Pour moi, 204 signifie "J'ai trouvé votre code, mais je n'ai pas trouvé de résultats" et 404 signifie "Je n'ai trouvé aucun code à exécuter, est-ce le mauvais chemin? "
Matt Sanders

11

404 Introuvable signifie techniquement que uri ne pas map à une ressource. Dans votre exemple, j'interprète une demande http://mywebsite/api/user/13qui renvoie un 404 pour impliquer que cette URL n'a jamais été mappée à une ressource. Pour le client, cela devrait être la fin de la conversation.

Pour résoudre les problèmes d'ambiguïté, vous pouvez améliorer votre API en fournissant d'autres codes de réponse. Par exemple, supposons que vous souhaitiez autoriser les clients à émettre des requêtes GET l'url http://mywebsite/api/user/13, vous souhaitez communiquer que les clients doivent utiliser l'URL canonique http://mywebsite/restapi/user/13. Dans ce cas, vous pouvez envisager d'émettre une redirection permanente en renvoyant un 301 déplacé de manière permanente et en fournissant l'URL canonique dans l'en- tête Location de la réponse. Cela indique au client que pour les demandes futures, il doit utiliser l'url canonique.


11

Donc, en substance, il semble que la réponse puisse dépendre de la façon dont la demande est formée.

Si la ressource demandée fait partie de l'URI conformément à une demande http://mywebsite/restapi/user/13et si l'utilisateur 13 n'existe pas, alors un 404 est probablement approprié et intuitif car l'URI est représentatif d'un utilisateur / entité / document / etc. inexistant. Il en irait de même pour la technique plus sécurisée utilisant un GUID http://mywebsite/api/user/3dd5b770-79ea-11e1-b0c4-0800200c9a66et l'argument api / restapi ci-dessus.

Cependant, si l'ID de ressource demandé était inclus dans l'en-tête de la demande [inclure votre propre exemple], ou bien dans l'URI en tant que paramètre, par exemple, http://mywebsite/restapi/user/?UID=13l'URI serait toujours correct (car le concept d'un UTILISATEUR se termine à http://mywebsite/restapi/user/); et par conséquent, la réponse pourrait raisonnablement être un 200 (avec un message correctement verbeux) car l'utilisateur spécifique connu sous le nom de 13 n'existe pas mais l'URI existe. De cette façon, nous disons que l'URI est bon, mais la demande de données n'a pas de contenu.

Personnellement, un 200 ne se sent toujours pas bien (même si je l'ai déjà dit). Un code de réponse 200 (sans réponse détaillée) pourrait entraîner un problème à ne pas étudier lorsqu'un ID incorrect est envoyé par exemple.

Une meilleure approche serait d'envoyer une 204 - No Contentréponse. Ceci est conforme à la description du w3c * Le serveur a répondu à la demande mais n'a pas besoin de renvoyer un corps d'entité, et pourrait vouloir retourner une métainformation mise à jour. * 1 La confusion, à mon avis, est causée par l'entrée Wikipedia indiquant 204 Pas de contenu - Le serveur a traité la demande avec succès, mais ne renvoie aucun contenu. Généralement utilisé comme réponse à une demande de suppression réussie .La dernière phrase est très discutable. Considérez la situation sans cette phrase et la solution est facile - envoyez simplement un 204 si l'entité n'existe pas. Il y a même un argument pour retourner un 204 au lieu d'un 404, la demande a été traitée et aucun contenu n'a été retourné! Sachez cependant que les 204 ne permettent pas le contenu dans le corps de la réponse

Sources

http://en.wikipedia.org/wiki/List_of_HTTP_status_codes 1. http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html


9

C'est un très vieux post, mais j'ai rencontré un problème similaire et je voudrais partager mon expérience avec vous.

Je construis une architecture de microservices avec des API de repos. J'ai quelques services GET de repos, ils collectent des données du système principal en fonction des paramètres de la demande.

J'ai suivi les autres documents de conception de l'API et j'ai renvoyé HTTP 404 avec un message d'erreur JSON parfait au client lorsqu'aucune donnée ne correspondait aux conditions de la requête (par exemple, aucun enregistrement n'a été sélectionné).

Lorsqu'il n'y avait aucune donnée à renvoyer au client, j'ai préparé un message JSON parfait avec un code d'erreur interne, etc. pour informer le client de la raison du "Not Found" et il a été renvoyé au client avec HTTP 404. Ce fonctionne bien.

Plus tard, j'ai créé une classe cliente d'API de repos qui est une aide facile pour masquer le code lié à la communication HTTP et j'ai utilisé cet assistant tout le temps lorsque j'ai appelé mes API de repos à partir de mon code.

MAIS j'avais besoin d'écrire du code supplémentaire déroutant juste parce que HTTP 404 avait deux fonctions différentes:

  • le vrai HTTP 404 lorsque l'API de repos n'est pas disponible dans l'URL donnée, il est lancé par le serveur d'applications ou le serveur Web sur lequel s'exécute l'application d'API de repos
  • le client récupère également HTTP 404 lorsqu'il n'y a pas de données dans la base de données en fonction de la condition where de la requête.

Important: Mon gestionnaire d'erreurs de l'API de repos intercepte toutes les exceptions qui apparaissent dans le service principal, ce qui signifie qu'en cas d'erreur, mon API de repos retourne toujours avec un message JSON parfait avec les détails du message.

Ceci est la 1ère version de ma méthode d'assistance client qui gère les deux réponses HTTP 404 différentes:

public static String getSomething(final String uuid) {
    String serviceUrl = getServiceUrl();
    String path = "user/" + , uuid);
    String requestUrl = serviceUrl + path;
    String httpMethod = "GET";

    Response response = client
            .target(serviceUrl)
            .path(path)
            .request(ExtendedMediaType.APPLICATION_UTF8)
            .get();

    if (response.getStatus() == Response.Status.OK.getStatusCode()) {
        // HTTP 200
        return response.readEntity(String.class);
    } else {
        // confusing code comes here just because
        // I need to decide the type of HTTP 404...

        // trying to parse response body
        try {
            String responseBody = response.readEntity(String.class);
            ObjectMapper mapper = new ObjectMapper();
            ErrorInfo errorInfo = mapper.readValue(responseBody, ErrorInfo.class);

            // re-throw the original exception
            throw new MyException(errorInfo);

        } catch (IOException e) {
            // this is a real HTTP 404
            throw new ServiceUnavailableError(response, requestUrl, httpMethod);
        }

    // this exception will never be thrown
    throw new Exception("UNEXPECTED ERRORS, BETTER IF YOU DO NOT SEE IT IN THE LOG");
}

MAIS , parce que mon client Java ou JavaScript peut recevoir en quelque sorte deux types de HTTP 404, je dois vérifier le corps de la réponse dans le cas de HTTP 404. Si je peux analyser le corps de la réponse, je suis sûr d'avoir récupéré une réponse là où il y avait aucune donnée à renvoyer au client.

Si je ne suis pas en mesure d'analyser la réponse, cela signifie que j'ai récupéré un vrai HTTP 404 du serveur Web (et non de l'application API restante).

C'est tellement déroutant et l'application cliente doit toujours effectuer une analyse supplémentaire pour vérifier la vraie raison de HTTP 404.

Honnêtement, je n'aime pas cette solution. C'est déroutant, il faut tout le temps ajouter du code de connerie supplémentaire aux clients.

Donc, au lieu d'utiliser HTTP 404 dans ces deux scénarios différents, j'ai décidé de faire ce qui suit:

  • Je n'utilise plus HTTP 404 comme code HTTP de réponse dans mon application de repos.
  • Je vais utiliser HTTP 204 (sans contenu) au lieu de HTTP 404.

Dans ce cas, le code client peut être plus élégant:

public static String getString(final String processId, final String key) {
    String serviceUrl = getServiceUrl();
    String path = String.format("key/%s", key);
    String requestUrl = serviceUrl + path;
    String httpMethod = "GET";

    log(requestUrl);

    Response response = client
            .target(serviceUrl)
            .path(path)
            .request(ExtendedMediaType.APPLICATION_JSON_UTF8)
            .header(CustomHttpHeader.PROCESS_ID, processId)
            .get();

    if (response.getStatus() == Response.Status.OK.getStatusCode()) {
        return response.readEntity(String.class);
    } else {
        String body = response.readEntity(String.class);
        ObjectMapper mapper = new ObjectMapper();
        ErrorInfo errorInfo = mapper.readValue(body, ErrorInfo.class);
        throw new MyException(errorInfo);
    }

    throw new AnyServerError(response, requestUrl, httpMethod);
}

Je pense que cela gère mieux ce problème.

Si vous avez une meilleure solution, partagez-la avec nous.


Les méthodes Apache HttpClient ne lèvent pas d'exception sur une réponse 204. Si votre client répond simplement aux objets métier (modèles), cela retournera null. Fonctionne, mais difficile pour les utilisateurs finaux de détecter la situation 204.
chrisinmtown

Tout va bien, mais une question est: à quelle fréquence écrivez-vous des déclarations pour le 404? Et que serait-ce? D'un autre côté, si vous appelez l'API, qui définit le 404 possible avec l'erreur json à l'intérieur, vous pouvez le gérer juste pour ce cas.
Max

zappee Je suis d'accord avec toi. J'ai un service qui renvoie des données. Mais il y a des situations où les données sont cassées. L'URL de demande est correcte (donc pas un 404), mais ce n'est pas vraiment une erreur logique (500), donc 204 semble le plus approprié. C'est vraiment ennuyeux de ne pas avoir de corps de message. Bien que je puisse sans doute insérer une raison dans un en-tête.
cs94njw


1

L'identificateur de ressource uniforme est un pointeur unique vers la ressource. Un URI de forme médiocre ne pointe pas vers la ressource et donc effectuer un GET dessus ne retournera pas une ressource. 404 signifie que le serveur n'a rien trouvé qui correspond à l'URI de la demande. Si vous avez entré un mauvais URI ou un mauvais URI, c'est votre problème et la raison pour laquelle vous n'avez pas accédé à une ressource, que ce soit une page HTML ou IMG.


0

Pour ce scénario, HTTP 404 est le code de réponse pour la réponse de l'API REST comme l'entité 400, 401, 404, 422 non traitable

utilisez la gestion des exceptions pour vérifier le message d'exception complet.

try{
  // call the rest api
} catch(RestClientException e) {
     //process exception
     if(e instanceof HttpStatusCodeException){
        String responseText=((HttpStatusCodeException)e).getResponseBodyAsString();
         //now you have the response, construct json from it, and extract the errors
         System.out.println("Exception :" +responseText);
     }

}

Ce bloc d'exception vous donne le message approprié lancé par l'API REST

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.