REST API 404: ¿URI incorrecto o falta de recursos?

219

Estoy creando una API REST, pero he encontrado un problema.

Parece que la práctica aceptada en el diseño de una API REST es que si el recurso solicitado no existe, se devuelve un 404.

Sin embargo, para mí, esto agrega ambigüedad innecesaria. HTTP 404 se asocia más tradicionalmente con un URI incorrecto. Entonces, en efecto, estamos diciendo "¡O llegaste al lugar correcto, pero ese registro específico no existe, o no existe tal ubicación en Internet! Realmente no estoy seguro de cuál ..."

Considere el siguiente URI:

http://mywebsite/api/user/13

Si recibo un 404, ¿es porque el Usuario 13 no existe? ¿O es porque mi URL debería haber sido:

http://mywebsite/restapi/user/13

En el pasado, acabo de devolver un resultado NULL con un HTTP 200 OKcódigo de respuesta si el registro no existe. Es simple y, en mi opinión, muy limpio, incluso si no es necesariamente una práctica aceptada. ¿Pero hay una mejor manera de hacer esto?

Brian Lacy
fuente
Probablemente un duplicado. stackoverflow.com/questions/3821663/…
Spencer Kormos el
77
La otra pregunta parece estar relacionada con los formatos de cadena de consulta URI. La discusión sobre 404 no es suficiente para responder a mi pregunta, que es si hay una forma más apropiada o útil de determinar qué significa realmente un 404. Lo revisé antes de publicar.
Brian Lacy
¿Es normal cuando el navegador concole consiste en los errores 404? Cuando hago operaciones regulares con uri correcta pero no se encuentran recursos.
Vasiliy Mazhekin
3
404 no indica un URI incorrecto, indica un recurso no encontrado. Esto puede deberse a que no hay un usuario '13', o puede ser porque no hay un recurso / mywebsite / api.
ChrisV

Respuestas:

101

404 es solo el código de respuesta HTTP. Además de eso, puede proporcionar un cuerpo de respuesta y / u otros encabezados con un mensaje de error más significativo que los desarrolladores verán.

Robert Levy
fuente
77
Esto tiene sentido. Sin embargo, tengo que preguntarme si se obtiene algún beneficio al devolver el 404 en primer lugar, en lugar de devolver un 200 con una respuesta nula.
Brian Lacy
15
Si devuelve un 200 con un valor nulo, va en contra de la especificación HTTP. Puede hacer esto, pero su API no se adherirá a la restricción de "interfaz uniforme" de REST.
demandando el
55
... y su cuerpo de respuesta debe incluir hipervínculos a URI válidos. Salvo el URI raíz, y tal vez uno o dos marcadores, sus clientes siempre deben seguir los enlaces que les proporciona el servidor. Entonces no hay necesidad de inventar una semántica detallada sobre exactamente cómo decidieron trabajar fuera del sistema.
fumanchu
@DarrylHebbes, ¿qué quieres decir? Puedo hacer una solicitud y ver el contenido completo de la respuesta en la pestaña Red de la consola del desarrollador de Chrome.
jaapz
Es increíblemente malo devolver un error (404 o lo que sea) para cualquier consulta que devuelva resultados vacíos. Ninguna base de datos lo hará y, por lo general, no desea informar al usuario un resultado vacío como una condición inesperada. Ejemplo: consulta su API para verificar si hay una cita programada para los próximos 30 minutos. Si no hay ninguno, NO debe devolver un 404. Devuelva un 200 OK y una matriz vacía, por favor.
esmiralha
59

Úselo 404si el recurso no existe. No vuelvas 200con el cuerpo vacío.

Esto es similar a undefinedvs cadena vacía (por ejemplo "") en programación. Si bien es muy similar, definitivamente hay una diferencia.

404significa que no existe nada en ese URI (como una variable indefinida en la programación). Volver 200con un cuerpo vacío significa que algo existe allí y que algo está vacío en este momento (como una cadena vacía en la programación).

404no significa que fue un "URI malo". Hay códigos HTTP especiales que están destinados a errores de URI (por ejemplo 414 Request-URI Too Long).

nategood
fuente
1
Oye, creo que podrías estar en algo. ¿No tendría más sentido devolver uno de los "41"? errores cuando hay un problema con el recurso solicitado? Por ejemplo, 410 Gone significa "El recurso solicitado ya no está disponible en el servidor y no se conoce la dirección de reenvío". - (Ver w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.11 )
Brian Lacy
En realidad, el recurso al que me referí anteriormente también dice: "Si el servidor no sabe, o no tiene facilidad para determinar, si la condición es permanente o no, DEBERÍA usarse el código de estado 404 (No encontrado)". Así que quizás esa no sea necesariamente la mejor opción tampoco ...
Brian Lacy
2
No creo que ninguno de los errores 41x sea apropiado para su caso de uso. Me gusta la comparación con una cadena indefinida o vacía, que es una versión más concisa de mi punto en mi respuesta. Hay una diferencia, pero eso no implica que una cadena vacía sea un error. Para estirar la analogía: Usted podría tener un método String getName()que tiene una aplicación como esta: return this.name == null ? "" : this.name. Eso no es incorrecta a menos que desee el cliente sepa que this.namees null.
jhericks 05 de
1
404 sigue siendo la opción más adecuada. Puede (y se le recomienda) usar el cuerpo de esa respuesta 404 para informar al usuario / cliente de detalles específicos para recibir el error. Ver: stackoverflow.com/a/9999335/105484 . En su caso, es posible que desee utilizar ese cuerpo para sugerirle al usuario que reformatee su URI para que se vea como / restapi / user / USER_ID
nategood
Ok, puedo ver algunos puntos buenos aquí, pero 4XX son códigos de error, ¿cómo es eso una situación de error? ¿Cómo puede el cliente evitar que suceda el 404?
Krzysztof Kubicki
20

Como con la mayoría de las cosas, "depende". Pero para mí, su práctica no es mala y no va en contra de la especificación HTTP per se . Sin embargo, aclaremos algunas cosas.

Primero, los URI deben ser opacos. Incluso si no son opacos para las personas, son opacos para las máquinas. En otras palabras, la diferencia entre http://mywebsite/api/user/13, http://mywebsite/restapi/user/13es la misma que la diferencia entre http://mywebsite/api/user/13y, http://mywebsite/api/user/14es decir, no es la misma, no es el mismo período. Entonces, un 404 sería completamente apropiado para http://mywebsite/api/user/14(si no hay tal usuario) pero no necesariamente la única respuesta apropiada.

También puede devolver una respuesta 200 vacía o más explícitamente una respuesta 204 (Sin contenido). Esto transmitiría algo más al cliente. Implicaría que el recurso identificado por http://mywebsite/api/user/14no tiene contenido o es esencialmente nada. Lo que significa que no es un recurso de este tipo. Sin embargo, no necesariamente significa que usted está afirmando que persiste algún usuario en un almacén de datos con ID 14. Esa es su preocupación privada, no la preocupación del cliente que hace la solicitud. Entonces, si tiene sentido modelar sus recursos de esa manera, adelante.

Existen algunas implicaciones de seguridad al proporcionar información a sus clientes que les facilitaría adivinar los URI legítimos. Devolver un 200 por fallas en lugar de un 404 puede darle al cliente una pista de que al menos la http://mywebsite/api/userparte es correcta. Un cliente malintencionado podría seguir intentando diferentes enteros. Pero para mí, un cliente malintencionado podría adivinar la http://mywebsite/api/userparte de todos modos. Un mejor remedio sería usar UUID. http://mywebsite/api/user/3dd5b770-79ea-11e1-b0c4-0800200c9a66es decir, es mejor que http://mywebsite/api/user/14. Al hacer eso, podría usar su técnica de devolver 200 sin revelar mucho.

jhericks
fuente
1
Esta es la solución que elegí y elegí usar un 204 en lugar de un 404. Para mí, 204 significa "Encontré su código, pero no encontré resultados" y 404 significa "No pude encontrar ningún código para ejecutar, es esto el camino equivocado?
Matt Sanders
11

404 No encontrado técnicamente significa que actualmente URI no se map a un recurso. En su ejemplo, interpreto una solicitud http://mywebsite/api/user/13que devuelve un 404 para implicar que esta url nunca se asignó a un recurso. Para el cliente, ese debería ser el final de la conversación.

Para abordar las inquietudes con ambigüedad, puede mejorar su API proporcionando otros códigos de respuesta. Por ejemplo, suponga que desea permitir a los clientes emitir solicitudes GET a la url http://mywebsite/api/user/13, desea comunicar que los clientes deben usar la url canónica http://mywebsite/restapi/user/13. En ese caso, es posible que desee considerar la posibilidad de emitir una redirección permanente al devolver un 301 Moved Permanently y proporcionar la URL canónica en el encabezado Ubicación de la respuesta. Esto le dice al cliente que para futuras solicitudes deben usar la URL canónica.

Ralph Allan Rice
fuente
11

Entonces, en esencia, parece que la respuesta podría depender de cómo se forma la solicitud.

Si el recurso solicitado forma parte del URI según una solicitud http://mywebsite/restapi/user/13y el usuario 13 no existe, entonces un 404 es probablemente apropiado e intuitivo porque el URI es representativo de un usuario / entidad / documento / etc inexistente. Lo mismo se aplicaría a la técnica más segura utilizando un GUID http://mywebsite/api/user/3dd5b770-79ea-11e1-b0c4-0800200c9a66y el argumento api / restapi anterior.

Sin embargo, si el ID del recurso solicitado se incluyó en el encabezado de la solicitud [incluya su propio ejemplo] o, de hecho, en el URI como parámetro, por ejemplo http://mywebsite/restapi/user/?UID=13, el URI seguiría siendo correcto (porque el concepto de USUARIO existe en http://mywebsite/restapi/user/); y por lo tanto, se podría esperar razonablemente que la respuesta sea 200 (con un mensaje detallado apropiado) porque el usuario específico conocido como 13 no existe pero el URI sí. De esta manera, decimos que el URI es bueno, pero la solicitud de datos no tiene contenido.

Personalmente, un 200 todavía no se siente bien (aunque ya he argumentado que sí). Un código de respuesta 200 (sin una respuesta detallada) podría causar que no se investigue un problema cuando se envía una identificación incorrecta, por ejemplo.

Un mejor enfoque sería enviar una 204 - No Contentrespuesta. Esto cumple con la descripción de w3c * El servidor ha cumplido la solicitud pero no necesita devolver un cuerpo de entidad, y podría querer devolver metainformación actualizada. * 1 La confusión, en mi opinión, es causada por la entrada de Wikipedia que indica 204 Sin contenido - El servidor procesó con éxito la solicitud, pero no devuelve ningún contenido. Usualmente se usa como respuesta a una solicitud de eliminación exitosa .La última oración es muy discutible. Considere la situación sin esa oración y la solución es fácil: solo envíe un 204 si la entidad no existe. Incluso hay un argumento para devolver un 204 en lugar de un 404, ¡la solicitud se ha procesado y no se ha devuelto ningún contenido! Sin embargo, tenga en cuenta que los 204 no permiten contenido en el cuerpo de respuesta

Fuentes

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

Juan
fuente
9

Esa es una publicación muy antigua pero me enfrenté a un problema similar y me gustaría compartir mi experiencia con ustedes.

Estoy construyendo una arquitectura de microservicios con API de descanso. Tengo algunos servicios GET de descanso, recopilan datos del sistema de fondo en función de los parámetros de solicitud.

Seguí el resto de los documentos de diseño de la API y envié HTTP 404 con un mensaje de error JSON perfecto al cliente cuando no había datos que se alinearan con las condiciones de la consulta (por ejemplo, se seleccionó el registro cero).

Cuando no había datos para enviar de vuelta al cliente, preparé un mensaje JSON perfecto con código de error interno, etc. para informar al cliente sobre el motivo de "No encontrado" y se lo devolvió al cliente con HTTP 404. Eso funciona bien.

Más tarde, he creado una clase de cliente API de descanso que es un ayudante fácil para ocultar el código relacionado con la comunicación HTTP y usé este ayudante todo el tiempo cuando llamé a mis API de descanso desde mi código.

PERO necesitaba escribir código extra confuso solo porque HTTP 404 tenía dos funciones diferentes:

  • el HTTP 404 real cuando el resto API no está disponible en la URL dada, es arrojado por el servidor de aplicaciones o el servidor web donde se ejecuta el resto de la aplicación API
  • el cliente también recupera HTTP 404 cuando no hay datos en la base de datos en función de la condición where de la consulta.

Importante: mi controlador de errores de la API de rest detecta todas las excepciones que aparecen en el servicio de back-end, lo que significa que, en caso de error, mi API de rest siempre regresa con un mensaje JSON perfecto con los detalles del mensaje.

Esta es la primera versión del método auxiliar de mi cliente que maneja las dos respuestas HTTP 404 diferentes:

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");
}

PERO , debido a que mi cliente Java o JavaScript puede recibir dos tipos de HTTP 404 de alguna manera, necesito verificar el cuerpo de la respuesta en el caso de HTTP 404. Si puedo analizar el cuerpo de la respuesta, entonces estoy seguro de que recibí una respuesta donde había No hay datos para enviar al cliente.

Si no puedo analizar la respuesta, eso significa que recuperé un HTTP 404 real del servidor web (no de la aplicación API del resto).

Es muy confuso y la aplicación cliente siempre necesita realizar un análisis adicional para verificar la razón real de HTTP 404.

Sinceramente, no me gusta esta solución. Es confuso, necesita agregar código extra de mierda a los clientes todo el tiempo.

Entonces, en lugar de usar HTTP 404 en estos dos escenarios diferentes, decidí que haré lo siguiente:

  • Ya no estoy usando HTTP 404 como código HTTP de respuesta en mi aplicación de descanso.
  • Voy a usar HTTP 204 (Sin contenido) en lugar de HTTP 404.

En ese caso, el código del cliente puede ser más elegante:

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);
}

Creo que esto maneja ese problema mejor.

Si tiene alguna solución mejor, compártala con nosotros.

zappee
fuente
Los métodos Apache HttpClient no arrojan excepciones en una respuesta 204. Si su cliente solo responde a objetos comerciales (modelos), entonces eso devolverá nulo. Funciona, pero es difícil para los usuarios finales detectar la situación 204.
chrisinmtown
Todo está bien, pero una pregunta es: ¿con qué frecuencia escribe if para el 404? Y que seria Por otro lado, si realiza una llamada a la API, que define un posible 404 con el error json dentro, puede manejarlo solo en este caso.
Max
zappee Estoy de acuerdo contigo. Tengo un servicio que devuelve datos. Pero hay situaciones en las que los datos están rotos. La URL de solicitud es correcta (por lo que no es un 404), pero en realidad no es un error lógico (500), por lo que 204 parece ser el más adecuado. Es realmente molesto por la falta de una cosa del cuerpo del mensaje. Aunque podría decirse que podría insertar una razón en un encabezado.
cs94njw
6

Este viejo pero excelente artículo ... http://www.infoq.com/articles/webber-rest-workflow dice esto al respecto ...

404 Not Found: el servicio es demasiado vago (o seguro) para darnos una razón real por la cual nuestra solicitud falló, pero sea cual sea la razón, tenemos que lidiar con ella.

Michael Dausmann
fuente
1

El identificador uniforme de recursos es un puntero único al recurso. Un URI de forma pobre no apunta al recurso y, por lo tanto, realizar un GET en él no devolverá un recurso. 404 significa que el servidor no ha encontrado nada que coincida con el URI de solicitud. Si ingresó el URI incorrecto o URI incorrecto, ese es su problema y la razón por la que no llegó a un recurso, ya sea una página HTML o IMG.

demandando
fuente
0

Para este escenario, HTTP 404 es el código de respuesta para la respuesta de la API REST Al igual que 400, 401, 404, 422 entidad no procesable

use el manejo de excepciones para verificar el mensaje de excepción completo.

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);
     }

}

Este bloque de excepción le da el mensaje correcto lanzado por la API REST

Venkata Naresh Babu
fuente