Código de estado HTTP para "Procesamiento continuo"

47

Estoy construyendo una API RESTful que admite tareas de larga duración en cola para un manejo eventual.

El flujo de trabajo típico para esta API sería:

  1. El usuario completa el formulario
  2. El cliente publica datos en la API
  3. API devuelve 202 Aceptado
  4. El cliente redirige al usuario a una URL única para esa solicitud ( /results/{request_id})
  5. ~ eventualmente ~
  6. El cliente vuelve a visitar la URL y ve los resultados en esa página.

Mi problema está en el paso 6. Cada vez que un usuario visita la página, presento una solicitud a mi API ( GET /api/results/{request_id}). Idealmente, la tarea ya se habrá completado, y devolvería un 200 OK con los resultados de su tarea.

Pero los usuarios son agresivos, y espero muchas actualizaciones demasiado entusiastas, cuando el resultado aún no ha terminado de procesarse.

¿Cuál es mi mejor opción para un código de estado que indique que:

  • esta solicitud existe
  • aún no está hecho,
  • Pero tampoco ha fallado.

No espero que un solo código comunique todo eso, pero me gustaría algo que me permita pasar metadatos en lugar de que el cliente espere contenido.

Podría tener sentido devolver un 202, ya que eso no tendría otro significado aquí: es una GETsolicitud, por lo que posiblemente no se "acepte" nada. ¿Sería una elección razonable?

La alternativa obvia a todo esto, que funciona, pero derrota un propósito de los códigos de estado, sería incluir siempre los metadatos:

200 OK

{
    status: "complete",
    data: {
        foo: "123"
    }
}

...o...

200 OK

{
    status: "pending"
}

Luego del lado del cliente, yo (suspiro) switchen response.data.statusdeterminar si la solicitud se ha completado.

¿Es esto lo que debería estar haciendo? ¿O hay una mejor alternativa? Esto se siente tan Web 1.0 para mí.

Matthew Haugen
fuente
1
¿No están los códigos 1xx hechos exactamente para ese propósito?
Andy
@Andy Estaba mirando 102, pero eso es para cosas de WebDAV. Más allá de eso, no ... Son principalmente para comunicaciones en tránsito. Útil para cambiar a Web Sockets y demás.
Matthew Haugen
¿De qué tipo de retraso estás hablando? ¿10 segundos? O 6 horas? Si los retrasos son cortos y generalmente se encuentran dentro de la misma visita del navegador, puede realizar sondeos largos o tomas web en lugar de sondeos periódicos.
GrandmasterB
@GrandmasterB Es horas, potencialmente. No soy responsable del procesamiento del trabajo en sí, por lo que no tengo una estimación muy buena, pero pasará un tiempo. De lo contrario, dejaría POSTabierta la primera solicitud. El problema principal con los sondeos largos o los sockets web es que el usuario puede cerrar el navegador y volver. Podría abrirlos nuevamente en ese momento (y eso es lo que hago), pero parece más limpio tener una única API a la que llamar antes de abrir esos sockets, ya que es un problema extremo que surja ese problema.
Matthew Haugen

Respuestas:

48

HTTP 202 aceptado (HTTP / 1.1)

Estás buscando el HTTP 202 Acceptedestado. Ver RFC 2616 :

La solicitud ha sido aceptada para su procesamiento, pero el procesamiento no se ha completado.

Procesamiento HTTP 102 (WebDAV)

RFC 2518 sugiere usar HTTP 102 Processing:

El código de estado 102 (Procesando) es una respuesta provisional utilizada para informar al cliente que el servidor ha aceptado la solicitud completa, pero aún no la ha completado.

pero tiene una advertencia:

El servidor DEBE enviar una respuesta final después de que se haya completado la solicitud.

No estoy seguro de cómo interpretar la última oración. ¿Debería el servidor evitar enviar algo durante el procesamiento y responder solo después de la finalización? ¿O solo obliga a finalizar la respuesta solo cuando finaliza el procesamiento? Esto podría ser útil si desea informar sobre el progreso. Envíe HTTP 102 y vacíe la respuesta byte por byte (o línea por línea).

Por ejemplo, para un proceso largo pero lineal, puede enviar cien puntos, enjuagando después de cada carácter. Si el lado del cliente (como una aplicación de JavaScript) sabe que debe esperar exactamente 100 caracteres, puede coincidir con una barra de progreso para mostrar al usuario.

Otro ejemplo se refiere a un proceso que consta de varios pasos no lineales. Después de cada paso, puede vaciar un mensaje de registro que eventualmente se mostrará al usuario, para que el usuario final pueda saber cómo va el proceso.

Problemas con el enrojecimiento progresivo

Tenga en cuenta que si bien esta técnica tiene sus méritos, no la recomendaría . Una de las razones es que obliga a que la conexión permanezca abierta, lo que podría perjudicar en términos de disponibilidad del servicio y no se escala bien.

Un mejor enfoque es responder HTTP 202 Acceptedy dejar que el usuario se comunique contigo más tarde para determinar si el procesamiento finalizó (por ejemplo, llamando repetidamente a un URI dado, como el /process/resultque respondería con HTTP 404 no encontrado o HTTP 409 Conflict hasta el proceso finaliza y el resultado está listo), o notifique al usuario cuando se realiza el procesamiento si puede volver a llamar al cliente, por ejemplo, a través de un servicio de cola de mensajes ( ejemplo ) o WebSockets.

Ejemplo práctico

Imagine un servicio web que convierte videos. El punto de entrada es:

POST /video/convert

que toma un archivo de video de la solicitud HTTP y hace algo de magia con él. Imaginemos que la magia requiere mucha CPU, por lo que no se puede hacer en tiempo real durante la transferencia de la solicitud. Esto significa que una vez que se transfiere el archivo, el servidor responderá con un HTTP 202 Acceptedcontenido JSON, lo que significa "Sí, obtuve su video y estoy trabajando en ello; estará listo en algún momento en el futuro y estará disponible a través de la ID 123 ".

El cliente tiene la posibilidad de suscribirse a una cola de mensajes para recibir una notificación cuando finalice el procesamiento. Una vez finalizado, el cliente puede descargar el video procesado yendo a:

GET /video/download/123

lo que lleva a un HTTP 200.

¿Qué sucede si el cliente consulta este URI antes de recibir la notificación? Bueno, el servidor responderá HTTP 404ya que, de hecho, el video aún no existe. Puede estar actualmente preparado. Puede que nunca se haya solicitado. Puede existir en algún momento en el pasado y eliminarse más tarde. Lo único que importa es que el video resultante no está disponible.

Ahora, ¿qué pasa si el cliente no solo se preocupa por el video final, sino también por el progreso (que sería aún más importante si no hay un servicio de cola de mensajes o algún mecanismo similar)?

En este caso, puede usar otro punto final:

GET /video/status/123

lo que daría una respuesta similar a esta:

HTTP 200
{
    "id": 123,
    "status": "queued",
    "priority": 2,
    "progress-percent": 0,
    "submitted-utc-time": "2016-04-19T13:59:22"
}

Hacer la solicitud una y otra vez mostrará el progreso hasta que sea:

HTTP 200
{
    "id": 123,
    "status": "done",
    "progress-percent": 100,
    "submitted-utc-time": "2016-04-19T13:59:22"
}

Es crucial hacer una diferencia entre esos tres tipos de solicitudes:

  • POST /video/convertpone en cola una tarea. Debería llamarse solo una vez: llamarlo nuevamente pondría en cola una tarea adicional.
  • GET /video/download/123se refiere al resultado de la operación: el recurso es el video. El procesamiento, que es lo que sucedió bajo el capó para preparar el resultado real antes de la solicitud e independientemente de la solicitud, es irrelevante aquí. Se puede llamar una o varias veces.
  • GET /video/status/123se refiere al procesamiento per se . No hace cola para nada. No le importa el video resultante. El recurso es el procesamiento en sí. Se puede llamar una o varias veces.
Arseni Mourzenko
fuente
1
Sin GETembargo, ¿tiene sentido un 202 en respuesta a un ? Esa es ciertamente la elección correcta para la inicial POST, por eso la estoy usando. Pero parece semánticamente sospechoso que un GETdecir "aceptado" cuando no acepta nada de esa solicitud en particular.
Matthew Haugen el
2
@MainMa Como dije, subo POSTel trabajo a la cola y luego GETlos resultados, posiblemente después de que el cliente haya cerrado la sesión. Un 404 es algo que también he considerado, pero parece incorrecto, ya que se encuentra la solicitud , simplemente no se ha completado. Eso me indicaría que no se encontró el trabajo en cola, que es una situación muy diferente.
Matthew Haugen
1
@MatthewHaugen: cuando haces la GETparte, no pienses en ello como una solicitud incompleta , sino como una solicitud para obtener el resultado de la operación . Por ejemplo, si le digo que convierta un video y le tome cinco minutos hacerlo, solicitar un video convertido dos minutos después debería dar como resultado HTTP 404, porque el video simplemente todavía no está allí. Solicitar el progreso de la operación en sí, por otro lado, probablemente dará como resultado un HTTP 200 que contenga el número de bytes convertidos, la velocidad, etc.
Arseni Mourzenko
55
El código de estado HTTP para el recurso aún no disponible sugiere devolver una respuesta de conflicto 409 ("La solicitud no se pudo completar debido a un conflicto con el estado actual del recurso"), en lugar de una respuesta 404, en el caso de que un recurso no No existe porque está en el medio de ser generado.
Brian
1
@Brian Su comentario sería una respuesta razonable a esta pregunta. Aunque luego respondería con "[t] su código solo está permitido en situaciones en las que se espera que el usuario pueda resolver el conflicto y volver a enviar la solicitud", lo cual no es estrictamente cierto en mi caso, pero eso parece menos equivocado que "no encontrado". Una parte de mí se inclina hacia un 409 con un encabezado Retry-After fijado. El único problema es que parece extraño devolver un 409 para un GET, pero puedo vivir con esa rareza: es poco probable que se defina de otra manera en el futuro.
Matthew Haugen
5

La alternativa obvia a todo esto, que funciona, pero derrota un propósito de los códigos de estado, sería incluir siempre los metadatos:

Este es el camino correcto a seguir. El estado en que se encuentran los recursos con respecto al registro específico del dominio (también conocido como lógica de negocios) depende del tipo de contenido de la representación del recurso.

Aquí se combinan dos conceptos diferentes que en realidad son diferentes. Uno es el estado de la transferencia de estado entre el cliente y el servidor de un recurso, y el otro es el estado del recurso en sí mismo en cualquier contexto en el que el dominio comercial asuma los diferentes estados de ese recurso. Esto último no tiene nada que ver con los códigos de estado HTTP.

Recuerde que los códigos de estado HTTP corresponden a la transferencia de estado entre el cliente y el servidor del recurso que se está tratando, independientemente de cualquier detalle de ese recurso. Cuando usted es GETun recurso, su cliente le pide al servidor una representación de un recurso en el estado actual en que se encuentra. Esa podría ser una imagen de un pájaro, podría ser un documento de Word, podría ser la temperatura externa actual. Al protocolo HTTP no le importa. El código de estado HTTP corresponde al resultado de esa solicitud. ¿Transmitió POSTdesde el cliente al servidor un recurso al servidor, donde el servidor le dio una URL que el cliente puede ver? ¿Si? Entonces esa es una 201 Createdrespuesta.

El recurso podría ser una reserva de una aerolínea que se encuentra actualmente en el estado 'para ser revisado'. O podría ser una orden de compra del producto que se encuentra en el estado 'aprobado'. Esos estados son específicos del dominio y no de lo que trata el protocolo HTTP. El protocolo HTTP se ocupa de la transferencia de recursos entre el cliente y el servidor.

El punto de REST y HTTP es que los protocolos no se preocupan por los detalles de los recursos. Esto es a propósito, no se ocupa de los problemas específicos del dominio para que pueda usarse sin tener que saber nada sobre los problemas específicos del dominio. No reinterpreta lo que significan los códigos de estado HTTP en cada contexto diferente (un sistema de reserva de línea aérea, un sistema de procesamiento de imágenes, un sistema de seguridad de video, etc.).

El material específico del dominio es para que el cliente y el servidor se resuelvan entre sí en función Content Typedel recurso. El protocolo HTTP es independiente de esto.

En cuanto a cómo el cliente se da cuenta de que el recurso Solicitud ha cambiado de estado, la votación es su mejor opción ya que mantiene el control en el cliente y no asume una conexión ininterrumpida. Particularmente si van a pasar horas hasta que el estado cambie. Incluso si dijiste al infierno con REST, solo vas a mantener la conexión abierta, mantenerla abierta durante horas y asumir que nada saldrá mal sería una mala idea. ¿Qué pasa si el usuario cierra el cliente o se apaga la red? Si la granularidad es de horas, el cliente puede solicitar el estado cada pocos minutos hasta que la Solicitud cambie de "pendiente" a "finalizada".

Espero que ayude a aclarar las cosas.

Cormac Mulhall
fuente
"¿La POST del cliente al servidor transfirió un recurso al servidor, donde el servidor le dio una URL que el cliente puede ver? ¿Sí? Entonces esa es una respuesta creada 201". 202 Aceptado también es aceptable como respuesta a esto si el servidor no puede actuar de inmediato para procesar el recurso, que es lo que está haciendo el OP.
Andy
1
El caso es que el servidor está actuando de inmediato. Crea el recurso con una URL de inmediato. Es solo que el estado del recurso es "Pendiente" (o algo así). Ese es un estado de dominio comercial. En lo que respecta al Protocolo HTTP, el servidor actuó tan pronto como creó el recurso y le dio al cliente la URL del recurso. Puedes OBTENER ese recurso. La solicitud POST en sí no está pendiente. Esto es lo que quiero decir con mantener separados los dos dominios conceptuales diferentes. Si el cliente estaba enviando un incendio y olvida que la solicitud POST no se realizó durante horas, entonces 202 sería aplicable.
Cormac Mulhall
A nadie le importa si la URL existe, pero no puede obtener los datos que representa el recurso porque todavía se está procesando. También podría NO crear la URL hasta que pueda usarse para obtener el video.
Andy
El recurso se crea, solo está en el estado "pendiente". Eso es en sí mismo datos relevantes. En algún momento en el futuro, el servidor puede cambiar el estado de los recursos a "completado" (o "fallido"), pero ese es un concepto diferente a la tarea específica del dominio HTTP de "crear el recurso". Pendiente puede ser un estado perfectamente válido para un recurso de "Solicitud", y el cliente obviamente quiere saber que el servidor ha creado el recurso en ese estado, ya que pasa de pedirle al servidor que cree el recurso para saber que lo consulta para averiguarlo. Si el estado cambió.
Cormac Mulhall
4

Las sugerencias de este blog me parecieron razonables: REST y trabajos de larga duración .

Para resumir:

  1. El servidor devuelve el código "202 Aceptado" con el encabezado "Ubicación" establecido en un URI para que el cliente verifique el estado, por ejemplo, "/ queue / 12345".
  2. Hasta que finalice el procesamiento, el servidor responde a las consultas de estado con "200 OK" y algunos datos de respuesta que muestran el estado del trabajo.
  3. Una vez que finaliza el procesamiento, el servidor responde a las consultas de estado con "303 See Other" y "Location" que contienen URI al resultado final.
Xiangming Hu
fuente
2

El código de estado HTTP para el recurso aún no disponible sugiere que se devuelva una respuesta de conflicto 409, en lugar de una respuesta 404, en el caso de que un recurso no exista porque se está generando.

De la especificación w3 :

10.4.10 409 Conflicto

La solicitud no se pudo completar debido a un conflicto con el estado actual del recurso. Este código solo se permite en situaciones en las que se espera que el usuario pueda resolver el conflicto y volver a enviar la solicitud. El cuerpo de respuesta DEBE incluir suficiente

información para que el usuario reconozca la fuente del conflicto. Idealmente, la entidad de respuesta incluiría suficiente información para que el usuario o agente de usuario solucione el problema; sin embargo, eso podría no ser posible y no es obligatorio.

Es más probable que ocurran conflictos en respuesta a una solicitud PUT. Por ejemplo, si se usaban versiones y la entidad que se PUT incluía cambios en un recurso que entran en conflicto con los realizados por una solicitud anterior (de terceros), el servidor podría usar la respuesta 409 para indicar que no puede completar la solicitud . En este caso, la entidad de respuesta probablemente contendría una lista de las diferencias entre las dos versiones en un formato definido por el Tipo de contenido de respuesta.

Esto es un poco incómodo, ya que el código 409 "solo está permitido en situaciones en las que se espera que el usuario pueda resolver el conflicto y volver a enviar la solicitud". Sugiero que el cuerpo de la respuesta incluya un mensaje (posiblemente en algún formato de respuesta que coincida con el resto de su API) como "Este recurso se está generando actualmente. Se inició a las [TIME] y se estima que se completará a las [TIME]. Por favor inténtalo de nuevo más tarde ".

Tenga en cuenta que solo sugeriría el enfoque 409 si es muy probable que el usuario que solicita el recurso sea también el usuario que inició la generación de ese recurso. Los usuarios que no participan en la generación del recurso encontrarán un error 404 menos confuso.

Brian
fuente
Parece un tramo de lo que 409 realmente está destinado, que es en respuesta a un put.
Andy
@Andy: Cierto, pero también lo es cualquier otra alternativa. Por ejemplo, 202 realmente está destinado a ser una respuesta a la solicitud que inició el procesamiento, no la solicitud que solicitó los resultados del procesamiento. Realmente, la respuesta más compatible con las especificaciones es 404, ya que no se encontró el recurso (porque aún no existía). No hay nada que impida que la API proporcione los datos de API relevantes dentro de la respuesta 404. Eso sí, las respuestas 4xx / 5xx tienden a ser molestas de consumir; algunos idiomas activarán una excepción en lugar de solo proporcionar un código de estado diferente.
Brian
2
No, especialmente los últimos párrafos de la respuesta de MainMa. Separe los puntos finales para verificar el estado de la solicitud y obtener el video en sí. El estado no es el mismo recurso que el video y debe ser direccionable por sí solo.
Andy