Estoy creando una API donde el usuario puede pedirle al servidor que realice múltiples acciones en una solicitud HTTP. El resultado se devuelve como una matriz JSON, con una entrada por acción.
Cada una de estas acciones puede fallar o tener éxito independientemente una de la otra. Por ejemplo, la primera acción puede tener éxito, la entrada a la segunda acción puede estar mal formateada y no puede validarse y la tercera acción puede causar un error inesperado.
Si hubiera una solicitud por acción, devolvería los códigos de estado 200, 422 y 500 respectivamente. Pero ahora, cuando solo hay una solicitud, ¿qué código de estado debo devolver?
Algunas opciones:
- Siempre devuelva 200 y brinde información más detallada en el cuerpo.
- ¿Quizás siga la regla anterior solo cuando hay más de una acción en la solicitud?
- ¿Quizás devolver 200 si todas las solicitudes tienen éxito, de lo contrario 500 (o algún otro código)?
- Simplemente use una solicitud por acción y acepte la sobrecarga adicional.
- Algo completamente diferente?
Respuestas:
La respuesta corta y directa
Dado que la solicitud habla de ejecutar la lista de tareas (las tareas son el recurso del que estamos hablando aquí), entonces, si el grupo de tareas se ha adelantado a la ejecución (es decir, independientemente del resultado de la ejecución), sería sensato que el estado de respuesta será
200 OK
. De lo contrario, si hubiera un problema que impidiera la ejecución del grupo de tareas, como una falla en la validación de los objetos de la tarea , o algún servicio requerido no está disponible, por ejemplo, el estado de respuesta debería indicar ese error. Después de eso, cuando comienza la ejecución de las tareas, ya que las tareas a realizar se enumeran en el cuerpo de la solicitud, entonces esperaría que los resultados de la ejecución se enumeren en el cuerpo de la respuesta.La respuesta larga y filosófica.
Experimenta este dilema porque se está desviando de lo que HTTP fue diseñado para. No lo está interactuando para administrar recursos, más bien, lo está utilizando como medio de invocación de método remoto (lo cual no es muy extraño, pero funciona mal sin un esquema preconcebido).
Dicho lo anterior, y sin valor para convertir esta respuesta en una guía larga y obstinada, el siguiente es un esquema de URI que se ajusta a un enfoque de gestión de recursos:
/tasks
GET
enumera todas las tareas, paginadoPOST
agrega una sola tarea/tasks/task/[id]
GET
responde con un objeto de estado de una sola tareaDELETE
cancela / elimina una tarea/tasks/groups
GET
enumera todos los grupos de tareas, paginadosPOST
agrega un grupo de tareas/tasks/groups/group/[id]
GET
responde con el estado de un grupo de tareasDELETE
cancela / elimina el grupo de tareasEsta estructura habla de recursos, no de qué hacer con ellos. Lo que se está haciendo con los recursos es la preocupación de otro servicio.
Otro punto importante es que es aconsejable no bloquear durante mucho tiempo en un controlador de solicitudes HTTP. Al igual que la interfaz de usuario, una interfaz HTTP debería responder, en una escala de tiempo que es un poco más lenta (porque esta capa se ocupa de IO).
Hacer el movimiento hacia el diseño de una interfaz HTTP que administre estrictamente los recursos es probablemente tan difícil como alejar el trabajo de un hilo de la interfaz de usuario cuando se hace clic en un botón. Requiere que el servidor HTTP se comunique con otros servicios para ejecutar tareas en lugar de ejecutarlas en el controlador de solicitudes. Esta no es una implementación superficial, es un cambio de dirección.
Algunos ejemplos de cómo se usaría dicho esquema URI
Ejecutar una sola tarea y seguir el progreso:
POST /tasks
con la tarea a ejecutarGET /tasks/task/[id]
hasta que el objeto de respuestacompleted
tenga un valor positivo mientras muestra el estado / progreso actualEjecutando una sola tarea y esperando su finalización:
POST /tasks
con la tarea a ejecutarGET /tasks/task/[id]?awaitCompletion=true
hasta quecompleted
tenga un valor positivo (es probable que tenga un tiempo de espera, por lo que esto debería repetirse)Ejecutar un grupo de tareas y seguir el progreso:
POST /tasks/groups
con el grupo de tareas a ejecutarGET /tasks/groups/group/[groupId]
hasta que lacompleted
propiedad del objeto de respuesta tenga valor, mostrando el estado de la tarea individual (3 tareas completadas de 5, por ejemplo)Solicitar una ejecución para un grupo de tareas y esperar su finalización:
POST /tasks/groups
con el grupo de tareas a ejecutarGET /tasks/groups/group/[groupId]?awaitCompletion=true
hasta que responda con un resultado que denote la finalización (es probable que tenga tiempo de espera, por lo que debe colocarse en bucle)fuente
Mi voto sería dividir estas tareas en solicitudes separadas. Sin embargo, si hay demasiados viajes de ida y vuelta, me encontré con el código de respuesta HTTP 207 - Estado múltiple
Copiar / pegar desde este enlace:
fuente
207
parece ser lo que quiere el OP, pero realmente quiero enfatizar que probablemente sea una mala idea tener este enfoque de solicitud múltiple en uno. Si la preocupación es el rendimiento, entonces debe diseñar arquitecturas escalables horizontalmente en el estilo de la nube (que es algo en lo que los sistemas basados en HTTP son excelentes)Aunque el estado múltiple es una opción, devolvería 200 (Todo está bien) si todas las solicitudes tuvieron éxito y un error (500 o quizás 207) de lo contrario.
El caso estándar generalmente debería ser 200: todo funciona. Y los clientes solo deberían comprobarlo. Y solo si ocurrió el caso de error, puede devolver un 500 (o un 207). Creo que el 207 es una opción válida en el caso de al menos un error, pero si ve el paquete completo como una transacción, también podría enviar 500. - El cliente querrá interpretar el mensaje de error de cualquier manera.
¿Por qué no siempre enviar 207? - Porque los casos estándar deberían ser fáciles y estándar. Mientras que los casos excepcionales pueden ser excepcionales. Un cliente solo debería leer el cuerpo de respuesta y tomar decisiones complejas adicionales, si una situación excepcional lo amerita.
fuente
Una opción sería devolver siempre un código de estado 200 y luego devolver errores específicos en el cuerpo de su documento JSON. Así es exactamente cómo se diseñan algunas API (siempre devuelven un código de estado 200 y envían el error en el cuerpo). Para obtener más detalles sobre los diferentes enfoques, consulte http://archive.oreilly.com/pub/post/restful_error_handling.html
fuente
200
para indicar que todo está bien, la solicitud se recibió y fue válida , y luego uso el JSON para proporcionar detalles sobre lo que sucedió en esa solicitud (es decir, el resultado de las transacciones).Creo que neilsimp1 es correcto, pero recomendaría un rediseño de los datos que se envían de tal manera que pueda enviar
206 - Accepted
y procesar los datos más adelante. Quizás con devoluciones de llamada.El problema al intentar enviar múltiples acciones en una sola solicitud es exactamente el hecho de que cada acción debe tener su propio "estado"
En cuanto a la importación de un CSV (no sé realmente de qué trata el OP, pero es una versión simple). PUBLICA el CSV y recupera un 206. Luego, el CSV puede importarse y puedes obtener el estado de la importación con un GET (200) contra una URL que muestra los errores por fila.
Este mismo patrón se puede aplicar a muchas operaciones por lotes.
El código que maneja la POST solo necesita verificar que el formato de los datos de las operaciones sea válido. Luego, en algún momento posterior, las operaciones pueden ejecutarse. En un trabajador de fondo, por lo que puede escalar más fácilmente, por ejemplo. Luego puede verificar el estado de las operaciones cuando lo desee. Puede usar encuestas o devoluciones de llamadas, o transmisiones o lo que sea para abordar la necesidad de saber cuándo se completa un conjunto de operaciones.
fuente
Ya hay muchas buenas respuestas aquí, pero falta un aspecto:
¿Cuál es el contrato que esperan sus clientes?
Los códigos de retorno HTTP deben indicar al menos una distinción de éxito / fracaso y, por lo tanto, desempeñar el papel de "excepciones del pobre". Entonces 200 significa "contrato completamente cumplido", y 4xx o 5xx indican incumplimiento.
Ingenuamente, esperaría que el contrato de su solicitud de acciones múltiples sea "hacer todas mis tareas", y si una de ellas falla, entonces la solicitud no fue (completamente) exitosa. Normalmente, como cliente, entendería 200 como "todo bien", y los códigos de las familias 400 y 500 me obligan a pensar en las consecuencias de una falla (parcial). Por lo tanto, use 200 para "todas las tareas realizadas" y 500 más una respuesta descriptiva en caso de una falla parcial.
Un contrato hipotético diferente podría ser "intente hacer todas las acciones". Entonces está completamente en línea con el contrato si (algunas de) las acciones fallan. Por lo tanto, siempre devolverá 200 más un documento de resultados donde encontrará la información de éxito / fracaso para las tareas individuales.
Entonces, ¿cuál es el contrato que quieres seguir? Ambos son válidos, pero el primero (200 solo en caso de que se haya hecho todo) es más intuitivo para mí y está mejor en línea con los patrones de software típicos. Y para la mayoría (con suerte) de los casos en que el servicio completó todas las tareas, es sencillo para el cliente detectar ese caso.
Un último aspecto importante: ¿Cómo comunicas la decisión de tu contrato a tus clientes? Por ejemplo, en Java, usaría nombres de métodos como "doAll ()" o "tryToDoAll ()". En HTTP, puede nombrar las URL de punto final en consecuencia, con la esperanza de que los desarrolladores de sus clientes vean, lean y comprendan los nombres (no apostaría por eso). Una razón más para elegir el contrato de menor sorpresa.
fuente
Responder:
Un código de estado describe el estado de una operación. Por lo tanto, tiene sentido tener una operación por solicitud.
Varias operaciones independientes rompen el principio en el que se basan el modelo de solicitud-respuesta y los códigos de estado. Estás luchando contra la naturaleza.
HTTP / 1.1 y HTTP / 2 han hecho que la sobrecarga de solicitudes HTTP sea mucho menor. Calculo que hay muy pocas situaciones en las que sea recomendable agrupar solicitudes independientes.
Dicho eso
(1) Puede realizar múltiples modificaciones con una solicitud PATCH ( RFC 5789 ). Sin embargo, esto requiere que los cambios no sean independientes; se aplican atómicamente (todo o nada).
(2) Otros han señalado el código 207 Multi-Status. Sin embargo, esto se define solo para WebDAV ( RFC 4918 ), una extensión de HTTP.
Una respuesta 207 WebDAV XML sería tan extraña como un pato en una API que no sea WebDAV. No hagas esto.
fuente
Si realmente necesita tener varias acciones en una solicitud, ¿por qué no ajustar todas las acciones en una transacción en el back-end? De esa manera, todos tienen éxito o todos fracasan.
Como cliente que usa la API, puedo lidiar con el éxito o el fracaso completos en una llamada a la API. El éxito parcial es difícil de manejar, ya que tendría que manejar todos los posibles estados resultantes.
fuente