Alternativas RESTful para ELIMINAR el cuerpo de la solicitud

94

Si bien la especificación HTTP 1.1 parece permitir cuerpos de mensaje en solicitudes DELETE , parece indicar que los servidores deben ignorarla ya que no hay una semántica definida para ella.

4.3 Cuerpo del mensaje

Un servidor DEBE leer y reenviar el cuerpo de un mensaje en cualquier solicitud; si el método de solicitud no incluye la semántica definida para un cuerpo de entidad, entonces el cuerpo del mensaje DEBE ignorarse al manejar la solicitud.

Ya revisé varias discusiones relacionadas sobre este tema en SO y más allá, como:

La mayoría de las discusiones parecen coincidir en que se puede permitir proporcionar un cuerpo de mensaje en un DELETE , pero generalmente no se recomienda.

Además, he notado una tendencia en varias bibliotecas de cliente HTTP en las que parece que se registran más y más mejoras para que estas bibliotecas admitan los cuerpos de solicitud en DELETE. La mayoría de las bibliotecas parecen complacer, aunque ocasionalmente con un poco de resistencia inicial.

Mi caso de uso requiere la adición de algunos metadatos requeridos en un DELETE (por ejemplo, el "motivo" de la eliminación, junto con algunos otros metadatos necesarios para la eliminación). He considerado las siguientes opciones, ninguna de las cuales parece completamente apropiada y en línea con las especificaciones HTTP y / o las mejores prácticas de REST:

  • Cuerpo del mensaje : la especificación indica que los cuerpos del mensaje en DELETE no tienen valor semántico; no es totalmente compatible con los clientes HTTP; no es una práctica estándar
  • Encabezados HTTP personalizados : exigir encabezados personalizados generalmente va en contra de las prácticas estándar ; usarlos es inconsistente con el resto de mi API, ninguno de los cuales requiere encabezados personalizados; Además, no hay una buena respuesta HTTP disponible para indicar valores de encabezado personalizados incorrectos (probablemente una pregunta separada por completo)
  • Encabezados HTTP estándar : ningún encabezado estándar es apropiado
  • Parámetros de consulta : agregar parámetros de consulta en realidad cambia el URI de solicitud que se está eliminando; contra las prácticas estándar
  • Método POST : (por ejemplo POST /resourceToDelete { deletemetadata }) POST no es una opción semántica para eliminar; POST en realidad representa la acción opuesta deseada (es decir, POST crea subordinados de recursos; pero necesito eliminar el recurso)
  • Múltiples métodos : dividir la solicitud DELETE en dos operaciones (por ejemplo, PUT delete metadata, luego DELETE) divide una operación atómica en dos, dejando potencialmente un estado inconsistente. El motivo de la eliminación (y otros metadatos relacionados) no forman parte de la representación del recurso en sí.

Mi primera preferencia probablemente sería usar el cuerpo del mensaje, después de los encabezados HTTP personalizados; sin embargo, como se indicó, existen algunas desventajas en estos enfoques.

¿Existen recomendaciones o mejores prácticas en línea con los estándares REST / HTTP para incluir tales metadatos requeridos en las solicitudes DELETE? ¿Existen otras alternativas que no he considerado?

Shelley
fuente
2
Ciertas implementaciones como Jerseyno permiten el cuerpo para las deletesolicitudes.
basiljames

Respuestas:

44

A pesar de algunas recomendaciones de no usar el cuerpo del mensaje para las solicitudes DELETE, este enfoque puede ser apropiado en ciertos casos de uso. Este es el enfoque que terminamos usando después de evaluar las otras opciones mencionadas en la pregunta / respuestas, y luego de colaborar con los consumidores del servicio.

Si bien el uso del cuerpo del mensaje no es ideal, ninguna de las otras opciones encajaba perfectamente. El cuerpo de la solicitud DELETE nos permitió agregar semántica de manera fácil y clara alrededor de datos / metadatos adicionales que se necesitaban para acompañar la operación DELETE.

Todavía estaría abierto a otros pensamientos y discusiones, pero quería cerrar el círculo sobre esta pregunta. ¡Agradezco los pensamientos y discusiones de todos sobre este tema!

Shelley
fuente
12
Esta es una mala idea. Un lugar donde esto le traerá problemas es si luego decide utilizar un servicio de aceleración HTTP como Akamai EdgeConnect. Sé a ciencia cierta que EdgeConnect elimina los cuerpos de las solicitudes HTTP DELETE (ya que consumen ancho de banda, es probable que no sean válidas). También es probable que servicios similares hagan lo mismo (consulte la función de aceleración de Kindle y otros servicios similares a CDN). Probablemente debería rediseñarlo para no utilizar verbos HTTP para su servicio. La mayoría de las API tienen poco sentido al usar HTTP-verbs / classic-REST y los problemas de transporte de verbos HTTP son muy difíciles de solucionar.
Gabe
3
Estoy de acuerdo con @Gabe, enviar un cuerpo con métodos que no tienen cuerpo por definición es una forma segura de perder datos al azar a medida que sus bits atraviesan las tuberías de Internet, y le resultará muy difícil depurarlo.
Nicholas Shanks
3
Estos comentarios en contra del uso de DELETE son irrelevantes hasta que aborden los problemas muy válidos que tiene el OP. Estoy haciendo todo lo posible para adherirme al espíritu de REST, pero una eliminación sin simultaneidad optimista y una eliminación que no tiene una operación de lote atómico no es práctica en situaciones de la vida real. Ésta es una deficiencia grave del patrón REST.
Quarkly
Estoy con @Quarkly en esto. No entiendo cuál es la idea RESTFUL sobre cómo deberíamos verificar la concurrencia, etc. Las verificaciones de concurrencia no pertenecen al cliente.
Dirk Wessels
13

Lo que parece querer es una de dos cosas, ninguna de las cuales es pura DELETE:

  1. Tiene dos operaciones, una PUTdel motivo de eliminación seguida de una DELETEdel recurso. Una vez eliminado, el contenido del recurso ya no es accesible para nadie. El 'motivo' no puede contener un hipervínculo al recurso eliminado. O,
  2. Está intentando modificar un recurso de state=activea state=deletedmediante el DELETEmétodo. Los recursos con estado = eliminado son ignorados por su API principal, pero aún pueden ser leídos por un administrador o alguien con acceso a la base de datos. Esto está permitido: DELETEno es necesario borrar los datos de respaldo de un recurso, solo para eliminar el recurso expuesto en ese URI.

Cualquier operación que requiera un cuerpo de mensaje en una DELETEsolicitud se puede dividir en su forma más general, una POSTpara hacer todas las tareas necesarias con el cuerpo del mensaje y una DELETE. No veo ninguna razón para romper la semántica de HTTP.

Nicholas Shanks
fuente
2
¿Qué sucede si la PUTrazón tiene éxito y los DELETErecursos fallan? ¿Cómo se puede prevenir el estado inconsistente?
Lightman
1
@Lightman the PUT solo especifica la intención. Puede existir sin un DELETE correspondiente, lo que indicaría que alguien quería eliminar pero falló o cambió de opinión. Invertir el orden de las llamadas también permitiría que se produjeran DELETE sin un motivo; la provisión de un motivo se consideraría meramente como una anotación. Es por estas dos razones que recomendaría usar la opción 2 de la anterior, es decir, cambiar el estado del registro subyacente para que el recurso HTTP desaparezca de su URL actual. Un recolector de basura / administrador puede purgar los registros
Nicholas Shanks
7

Dada la situación que tiene, tomaría uno de los siguientes enfoques:

  • Enviar un PUT o PATCH : estoy deduciendo que la operación de eliminación es virtual, por la naturaleza de necesitar un motivo de eliminación. Por lo tanto, creo que actualizar el registro a través de una operación PUT / PATCH es un enfoque válido, aunque no es una operación DELETE per se.
  • Utilice los parámetros de consulta : la uri del recurso no se modificará. De hecho, creo que este también es un enfoque válido. La pregunta que vinculó hablaba de no permitir la eliminación si faltaba el parámetro de consulta. En su caso, solo tendría un motivo predeterminado si el motivo no se especifica en la cadena de consulta. El recurso seguirá siendo resource/:id. Puede hacerlo visible con encabezados de enlace en el recurso por cada motivo (con una reletiqueta en cada uno para identificar el motivo).
  • Use un punto final separado por motivo : usando una URL como resource/:id/canceled. En realidad, esto cambia el Request-URI y definitivamente no es RESTful. Nuevamente, los encabezados de los enlaces pueden hacer que esto sea detectable.

Recuerde que el DESCANSO no es ley ni dogma. Piense en ello más como una guía. Entonces, cuando tenga sentido no seguir la guía para el dominio de su problema, no lo haga. Solo asegúrese de que sus consumidores de API estén informados de la variación.

progresión de código
fuente
Con respecto al uso de parámetros de consulta, tengo entendido que la consulta es parte del Request-URI según la sección 3.2 y, por lo tanto, el uso de este enfoque (o de manera similar, los extremos separados) va en contra de la definición del método DELETE , de modo que "resource identificado por el Request-URI "se elimina.
shelley
El recurso se identifica mediante la ruta de uri. Entonces, un GET a /orders/:iddevolvería el mismo recurso que /orders/:id?exclude=orderdetails. La cadena de consulta solo da pistas al servidor, en este caso para excluir los detalles del pedido en la respuesta (si es compatible). De manera similar, si envía DELETE a /orders/:ido /orders/:id?reason=canceledo /orders/:id?reason=bad_credit, todavía está actuando sobre el mismo recurso subyacente. Para mantener una 'interfaz uniforme', tendría una razón predeterminada para que no sea necesario enviar el parámetro de consulta.
código de progresión
@shelley Tiene razón en sus preocupaciones sobre las cadenas de consulta. La cadena de consulta es parte del URI. Enviar una solicitud DELETE a /foo?123significa que está eliminando un recurso diferente que si tuviera que enviar DELETE a /foo?456.
Nicholas Shanks
@codeprogression Lo siento, pero mucho de lo que dices está mal. El recurso se identifica mediante el URI completo, no solo la ruta. Las diferentes cadenas de consulta son recursos diferentes (en el sentido HTTP de la palabra 'recurso'). Además, no se requiere una razón predeterminada para una interfaz uniforme. Ese término se refiere al uso de GET, PUT, POST, PATCH y DELETE de la forma en que HTTP los definió. Lo común es entre proveedores (proveedores de agentes de usuario, proveedores de API, proveedores de proxy de almacenamiento en caché, ISP, etc.) y no dentro de la propia API (¡aunque eso también debe ser uniforme en diseño para la cordura de sus usuarios!).
Nicholas Shanks
@Nicholas No entiendo tu necesidad de discutir una discusión que terminó hace tres años. La respuesta y los comentarios que hice son válidos y correctos desde una vista centrada en REST. REST no es HTTP (ni ninguna implementación de proveedor de HTTP). En el contexto de REST, los recursos son los mismos. Y como dije en mi respuesta, DESCANSO no es ley o dogma, sino orientación.
código de progresión
0

Le sugiero que incluya los metadatos necesarios como parte de la jerarquía de URI en sí. Un ejemplo (ingenuo):

Si necesita eliminar entradas basadas en un rango de fechas, en lugar de pasar la fecha de inicio y la fecha de finalización en el cuerpo o como parámetros de consulta, estructure el URI de tal manera que pase la información requerida como parte del URI.

p.ej

DELETE /entries/range/01012012/31122012 - Eliminar todas las entradas entre el 1 de enero de 2012 y el 31 de diciembre de 2012

Espero que esto ayude.

Suresh Kumar
fuente
5
No cubre casos como enviar un motivo de eliminación, es decir, un campo de comentarios.
Kugel
3
Guau. esa es una idea terrible. Si tiene demasiados metadatos, inflará las restricciones de tamaño en URI.
Balaji Boggaram Ramanarayan
1
Este enfoque no sigue las prácticas RESTful y no se recomienda, ya que tendrá una estructura de URI complicada. Los puntos finales se confunden con la identificación de recursos entrelazados frente a los metadatos y, con el tiempo, se convertirán en una pesadilla de mantenimiento a medida que cambie su API. Es mucho más preferido tener lo rangeespecificado en los parámetros de consulta o la carga útil, que es la esencia de esta pregunta: para comprender el enfoque de las mejores prácticas para el problema, que yo diría que no es este.
digitaldreamer
@digitaldreamer: no entiendo lo que quiere decir con estructura intrincada de URI. Además, este es un HTTP DELETE, por lo que la carga útil no es una opción, pero los parámetros de consulta sí.
Suresh Kumar