¿Cómo debe una API REST manejar las solicitudes PUT a recursos parcialmente modificables?

41

Supongamos que una API REST, en respuesta a una solicitud HTTP GET , devuelve algunos datos adicionales en un subobjeto owner :

{
  id: 'xyz',
  ... some other data ...
  owner: {
    name: 'Jo Bloggs',
    role: 'Programmer'
  }
}

Claramente, no queremos que nadie pueda PUT de vuelta

{
  id: 'xyz',
  ... some other data ...
  owner: {
    name: 'Jo Bloggs',
    role: 'CEO'
  }
}

y que tenga éxito. De hecho, probablemente ni siquiera vamos a implementar una forma para que eso tenga éxito, en este caso.

Pero esta pregunta no es solo acerca de los subobjetos: ¿qué se debe hacer, en general, con datos que no deberían modificarse en una solicitud PUT?

¿Debería ser necesario que falte en la solicitud PUT?

¿Debería descartarse en silencio?

¿Debería verificarse, y si difiere del valor antiguo de ese atributo, devolver un código de error HTTP en la respuesta?

¿O deberíamos usar los parches JSON RFC 6902 en lugar de enviar todo el JSON?

    
pregunta Robin Green 14.08.2013 - 17:19

2 respuestas

38

No hay ninguna regla, ni en la especificación W3C ni en las reglas no oficiales de REST, que diga que un PUT debe usar el mismo esquema / modelo que su correspondiente GET .

Es bueno si son similares , pero no es inusual que PUT haga las cosas de manera ligeramente diferente. Por ejemplo, he visto muchas API que incluyen algún tipo de ID en el contenido devuelto por GET , por conveniencia. Pero con un PUT , ese ID está determinado exclusivamente por el URI y no tiene ningún significado en el contenido. Cualquier ID encontrada en el cuerpo será ignorada silenciosamente.

RESTO y la web en general está fuertemente vinculada al Principio de robustez : "Sea conservador en lo que haz [enviar], sé liberal en lo que aceptas ". Si estás de acuerdo filosóficamente con esto, entonces la solución es obvia: ignorar los datos no válidos en las solicitudes de PUT . Eso se aplica tanto a los datos inmutables, como en su ejemplo, como a las tonterías reales, por ejemplo. campos desconocidos.

PATCH es potencialmente otra opción, pero no debe implementar PATCH a menos que realmente admita actualizaciones parciales. PATCH significa solo actualizar los atributos específicos que incluyo en el contenido ; no significa no reemplazar la entidad completa, pero excluye algunos campos específicos . De lo que realmente estás hablando no es realmente una actualización parcial, es una actualización completa, idempotent y todo, es solo que parte del recurso es de solo lectura.

Una buena cosa que hacer si elige esta opción sería enviar un 200 (OK) con la entidad actualizada actual en la respuesta, para que los clientes puedan ver claramente que la opción de solo lectura los campos no se actualizaron.

Ciertamente hay algunas personas que piensan de otra manera: debería ser un error intentar actualizar una lectura. -Sólo parte de un recurso. Existe una justificación para esto, principalmente sobre la base de que definitivamente devolvería un error si el recurso completo fuera de solo lectura y el usuario intentara actualizarlo. Definitivamente va en contra del principio de robustez, pero podría considerar que es más "auto-documentado" para los usuarios de su API.

Hay dos convenciones para esto, las cuales corresponden a tus ideas originales, pero las ampliaré. La primera es prohibir que los campos de solo lectura aparezcan en el contenido y, si lo hacen, devolver un HTTP 400 (Solicitud incorrecta). Las API de este tipo también deben devolver un HTTP 400 si hay otros campos no reconocidos / inutilizables. El segundo es requerir que los campos de solo lectura sean idénticos al contenido actual, y devolver un 409 (Conflicto) si los valores no coinciden.

Realmente no me gusta la verificación de igualdad con 409 porque invariablemente requiere que el cliente realice un GET para recuperar los datos actuales antes de poder hacer un PUT . Eso no es bueno y probablemente va a llevar a un rendimiento pobre, para alguien, en algún lugar. También a realmente no me gusta 403 (Prohibido) porque esto implica que el recurso completo está protegido, no solo una parte de él. Así que mi opinión es que, si es absolutamente necesario validar en lugar de seguir el principio de robustez, valide todas de sus solicitudes y devuelva un 400 para cualquiera que tenga campos adicionales o que no se puedan escribir.

Asegúrese de que su 400/409 / lo que sea incluya información sobre cuál es el problema específico y cómo solucionarlo.

Ambos enfoques son válidos, pero prefiero el primero de acuerdo con el principio de robustez. Si alguna vez ha experimentado trabajar con una gran API REST, apreciará el valor de la compatibilidad con versiones anteriores. Si alguna vez decide eliminar un campo existente o hacerlo de solo lectura, es un cambio compatible con versiones anteriores si el servidor simplemente ignora esos campos y los clientes antiguos seguirán funcionando. Sin embargo, si realiza una validación estricta del contenido, ya no es compatible con versiones anteriores, y los clientes antiguos dejarán de funcionar. Lo primero generalmente significa menos trabajo tanto para el mantenedor de una API como para sus clientes.

    
respondido por el Aaronaught 14.09.2013 - 03:49
9

Potencia idem

Siguiendo el RFC, un PUT tendría que entregar el objeto completo al recurso. La razón principal de esto, es que PUT debe ser idempotente. Esto significa que una solicitud, que se repite, debe evaluar el mismo resultado en el servidor.

Si permites actualizaciones parciales, ya no puede ser idem-potente. Si tienes dos clientes. Cliente A y B, entonces el siguiente escenario puede evolucionar:

El cliente A obtiene una imagen de las imágenes de recursos. Esto contiene una descripción de la imagen, que sigue siendo válida. El cliente B pone una nueva imagen y actualiza la descripción en consecuencia. La imagen ha cambiado. El cliente A ve, no tiene que cambiar la descripción, porque es lo que desea y solo pone la imagen.

Esto llevará a una inconsistencia, ¡la imagen tiene los metadatos incorrectos adjuntos!

Aún más molesto es que cualquier intermediario puede repetir la solicitud. En caso de que decida de alguna manera el PUT falló.

El significado de PUT no se puede cambiar (aunque puedes usarlo mal).

Otras opciones

Afortunadamente hay otra opción, esta es PATCH. PATCH es un método que le permite actualizar parcialmente una estructura. Simplemente puede enviar una estructura parcial. Para aplicaciones simples, esto está bien. No se garantiza que este método sea idem potente. El cliente debe enviar una solicitud en el siguiente formulario:

PATCH /file.txt HTTP/1.1
Host: www.example.com
Content-Type: application/example
If-Match: "e0023aa4e"
Content-Length: 20
{fielda: 1, fieldc: 2}

Y el servidor puede responder con 204 (Sin contenido) para marcar el éxito. En caso de error no puede actualizar una parte de la estructura. El método PATCH es atómico.

La desventaja de este método es que no todos los navegadores lo admiten, pero esta es la opción más natural en un servicio REST.

Ejemplo de solicitud de parche: enlace

Aplicación de parches Json

La opción json parece ser bastante completa y una opción interesante. Pero puede ser difícil de implementar para terceros. Tienes que decidir si tu base de usuarios puede manejar esto.

También es algo complicado, porque necesita crear un pequeño intérprete que convierta los comandos en una estructura parcial, que va a utilizar para actualizar su modelo. Este intérprete también debe verificar, si los comandos proporcionados tienen sentido. Algunos comandos se anulan entre sí. (escribe fielda, borra fielda). Creo que desea informar esto al cliente para limitar el tiempo de depuración de su lado.

Pero si tienes tiempo, esta es una solución realmente elegante. Aún debe validar los campos por supuesto. Puede combinar esto con el método PATCH para permanecer en el modelo REST. Pero creo que POST sería aceptable aquí.

Va mal

Si decides optar por la opción PUT, es un poco arriesgado. Entonces al menos no debes descartar el error. El usuario tiene cierta expectativa (los datos se actualizarán) y si rompe esto, le dará a algunos desarrolladores no un buen momento.

Usted podría elegir para marcar hacia atrás: 409 Conflicto o 403 Prohibido. Depende de cómo se mire el proceso de actualización. Si lo ve como un conjunto de reglas (centrado en el sistema), entonces el conflicto será más agradable. Algo así, estos campos no son actualizables. (En conflicto con las reglas). Si lo ve como un problema de autorización (centrado en el usuario), debe devolver prohibido. Con: no estás autorizado a cambiar estos campos.

Aún debe obligar a los usuarios a enviar todos los campos modificables.

Una opción razonable para hacer cumplir esto es establecerlo en un sub-recurso, que solo ofrece los datos modificables.

Opinión personal

Personalmente iría (si no tiene que trabajar con navegadores) para el modelo PATCH simple y luego lo extienda con un procesador de parches JSON. Esto se puede hacer diferenciando en los tipos MIME: El tipo mime del parche json:

aplicación / json-patch

Y JSON:    aplicación / json-patch

facilita su implementación en dos fases.

    
respondido por el Edgar Klerks 15.08.2013 - 00:38

Lea otras preguntas en las etiquetas