Empezando con el descanso

Resumen de REST

REST significa REpresentational State Transfer y fue acuñado por Roy Fielding en su tesis doctoral Architectural Styles and the Design of Network-based Software Architectures. En él identifica principios arquitectónicos específicos como:

  • Recursos direccionables: la abstracción clave de información y datos en REST es un recurso y cada recurso debe ser direccionable a través de un URI.

  • Una interfaz uniforme y restringida: uso de un pequeño conjunto de métodos bien definidos para manipular nuestros recursos.

  • Orientado a la representación: un recurso al que hace referencia un URI puede tener diferentes formatos y diferentes plataformas necesitan diferentes formatos, por ejemplo, los navegadores necesitan HTML, JavaScript necesita JSON y las aplicaciones Java pueden necesitar XML, JSON, CSV, texto, etc. Entonces interactuamos con servicios utilizando la representación de ese servicio.

  • Comuníquese sin estado: las aplicaciones sin estado son más fáciles de escalar.

  • Hipermedia como motor del estado de la aplicación: permita que nuestros formatos de datos impulsen las transiciones de estado en nuestras aplicaciones.

El conjunto de estos principios arquitectónicos se denomina REST. Los conceptos de REST están inspirados en los de HTTP. Roy Fielding, quien nos dio REST, también es uno de los autores de las especificaciones HTTP.

Servicios web y Servicios web RESTful son servicios que están expuestos a Internet para el acceso programático. Son api online a las que podemos llamar desde nuestro código. Hay dos tipos de [servicios web “grandes”] 3 servicios web SOAP y REST.

Servicios web RESTful: los servicios web que se escriben aplicando los conceptos arquitectónicos REST se denominan servicios web RESTful, que se centran en los recursos del sistema y cómo se puede transferir el estado de un recurso a través del protocolo HTTP a diferentes clientes.

Este documento se centra únicamente en los servicios web RESTful, por lo que no entraremos en detalles de SOAP WS.

No hay reglas estrictas al diseñar servicios web RESTful como

  • Sin estándar de protocolo
  • Sin estándar de canal de comunicación
  • Sin estándar de definición de servicio

Pero SOAP tiene reglas estrictas para todo esto. Todos los servicios web SOAP siguen la especificación SOAP que dicta lo que debe ser cada servicio web SOAP. Esta especificación es desarrollada y administrada por un comité y si SOAP WS no sigue ni una sola regla, entonces, por definición, no es SOAP.

Conceptos de Servicios Web RESTful

Hay algunas pautas que deben tenerse en cuenta al diseñar/desarrollar una API RESTful:

  1. Ubicaciones basadas en recursos/URI
  2. Uso adecuado de los métodos HTTP
  3. HATEOAS (Hipermedia como motor del estado de aplicación)

El enfoque principal al desarrollar API RESTful debe ser hacer que la API sea “tan RESTful como sea posible”.

RESTO sobre HTTP

REST es una arquitectura agnóstica de protocolo propuesta por Roy Fielding en su [disertación] (https://www.ics.uci.edu/~fielding/pubs/dissertation/fielding_dissertation.pdf) (siendo el capítulo 5 la presentación de REST), que generaliza el concepto probado de navegadores web como clientes para para desacoplar clientes en un sistema distribuido de servidores.

Para que un servicio o API sea RESTful, debe cumplir con restricciones dadas como:

  • Servidor de cliente
  • Apátrida
  • Caché
  • Sistema de capas
  • Interfaz uniforme
    • Resources identification
    • Resources representation
    • Self-descriptive messages
    • Hypermedia

Además de las restricciones mencionadas en la disertación de Fielding, en su publicación de blog [Las API REST deben estar impulsadas por hipertexto] (http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven) , Fielding aclaró que solo invocar un servicio a través de HTTP no lo convierte en RESTful. Por lo tanto, un servicio también debe respetar otras reglas que se resumen a continuación:

  • La API debe cumplir y no violar el protocolo subyacente. Aunque REST se usa a través de HTTP la mayor parte del tiempo, no está restringido a este protocolo.

  • Fuerte enfoque en los recursos y su presentación a través de tipos de medios.

  • Los clientes no deben tener conocimiento inicial o suposiciones sobre los recursos disponibles o su estado devuelto ([recurso “escrito”] (http://soabits.blogspot.co.at/2012/04/restful-resources-are-not-typed .html)) en una API, pero apréndalos sobre la marcha a través de solicitudes emitidas y respuestas analizadas. Esto le da al servidor la oportunidad de moverse o cambiar el nombre de los recursos fácilmente sin interrumpir la implementación de un cliente.

Modelo de madurez de Richardson

El Modelo de madurez de Richardson es una forma de aplicar restricciones REST sobre HTTP para obtener servicios web RESTful.

Leonard Richardson dividió las aplicaciones en estas 4 capas:

  • Nivel 0: uso de HTTP para el transporte
  • Nivel 1: uso de URL para identificar recursos
  • Nivel 2: uso de verbos y estados HTTP para las interacciones
  • Nivel 3: uso de HATEOAS

Dado que la atención se centra en la representación del estado de un recurso, se recomienda admitir múltiples representaciones para el mismo recurso. Por lo tanto, una representación podría presentar una descripción general del estado del recurso, mientras que otra devuelve los detalles completos del mismo recurso.

Tenga en cuenta también que dadas las restricciones de Fielding, una API es RESTful de manera efectiva solo una vez que se implementa el tercer nivel de RMM.


Solicitudes y respuestas HTTP

Una solicitud HTTP es:

  • Un verbo (también conocido como método), la mayoría de las veces uno de GET, [POST](https://tools.ietf .org/html/rfc7231#section-4.3.3), PONER, [ELIMINAR](https://tools.ietf .org/html/rfc7231#section-4.3.5) o PARCHE
  • Una dirección URL
  • Encabezados (pares clave-valor)
  • Opcionalmente, un cuerpo (también conocido como carga útil, datos)

Una respuesta HTTP es:

  • Un estado, la mayoría de las veces uno de 2xx (correcto), [4xx (error de cliente)](https://tools. ietf.org/html/rfc7231#section-6.5) o 5xx (error del servidor)
  • Encabezados (pares clave-valor)
  • Un cuerpo (también conocido como carga útil, datos)

Características de los verbos HTTP:

  • Verbos que tienen cuerpo: POST, PUT, PATCH
  • Verbos que deben ser seguros (es decir, que no deben modificar los recursos): GET
  • Verbos que deben ser idempotentes (es decir, que no deben volver a afectar los recursos cuando se ejecutan varias veces): GET (nulipotent), PUT, DELETE
        body  safe  idempotent
GET      ✗     ✔     ✔
POST     ✔     ✗     ✗
PUT      ✔     ✗     ✔
DELETE   ✗     ✗     ✔
PATCH    ✔     ✗     ✗

En consecuencia, los verbos HTTP se pueden comparar con las funciones CRUD:

  • [Create](https://www.wikiod.com/es/http/http-para-api#crear un recurso): POST
  • READ: OBTENER
  • [UPDATE](https://www.wikiod.com/es/http/http-para-api#Editar un recurso): PUT, PATCH
  • [DELIMINAR](https://www.wikiod.com/es/http/http-para-api#Eliminar un recurso): ELIMINAR

Tenga en cuenta que una solicitud PUT pide a los clientes que envíen el recurso completo con los valores actualizados. Para actualizar parcialmente un recurso, se podría usar un verbo PATCH (ver ¿Cómo actualizar parcialmente un recurso?).

Estados de respuesta HTTP habituales

Éxito

Redirección

  • 304 (NO MODIFICADO): el cliente puede usar la versión en caché que tiene del recurso solicitado

Errores del cliente

Errores del servidor

Notas

Nada te impide agregar un cuerpo a las respuestas erróneas, para que el rechazo sea más claro para los clientes. Por ejemplo, el 422 (ENTIDAD NO PROCESABLE) es un poco vago: el cuerpo de la respuesta debe proporcionar el motivo por el cual no se pudo procesar la entidad.


HATEOAS

Cada recurso debe proporcionar hipermedia a los recursos a los que está vinculado. Un enlace está al menos compuesto por:

  • Un rel (por relación, también conocido como nombre): describe la relación entre el recurso principal y el(los) vinculado(s)
  • Un href: la URL dirigida a los recursos vinculados

También se pueden usar atributos adicionales para ayudar con la desaprobación, la negociación de contenido, etc.

Cormac Mulhall explica que el cliente debe decidir qué verbo HTTP usar en función de lo que está tratando de hacer. En caso de duda, la documentación de la API debería ayudarlo de todos modos a comprender las interacciones disponibles con todos los hipermedios.


Tipos de medios

Los tipos de medios ayudan a tener mensajes autodescriptivos. Hacen parte del contrato entre clientes y servidores, para que puedan intercambiar recursos e hipermedias.

Aunque application/json y application/xml son tipos de medios bastante populares, no contienen mucha semántica. Simplemente describen la sintaxis general utilizada en el documento. Deben usarse tipos de medios más especializados que admitan los requisitos de HATEOAS (o extenderse a través de tipos de medios de proveedores), como:

Un cliente le dice a un servidor qué tipos de medios entiende al agregar el encabezado “Aceptar” a su solicitud, por ejemplo:

Accept: application/hal+json

Si el servidor no puede producir el recurso solicitado en tal representación, devuelve un [406 (NO ACEPTABLE)] (https://tools.ietf.org/html/rfc7231#section-6.5.6). De lo contrario, agrega el tipo de medio en el encabezado Content-Type de la respuesta que contiene el recurso representado, por ejemplo:

Content-Type: application/hal+json

sin estado > con estado

¿Por qué?

Un servidor con estado implica que las sesiones de los clientes se almacenan en un almacenamiento local de instancia de servidor (casi siempre en sesiones de servidor web). Esto comienza a ser un problema al intentar [escalar horizontalmente] (http://stackoverflow.com/a/11715598/1225328): si oculta varias instancias de servidor detrás de un balanceador de carga, si un cliente se envía primero a * instancia # 1* al iniciar sesión, pero luego a la instancia n.° 2 al obtener un recurso protegido, por ejemplo, la segunda instancia manejará la solicitud como anónima, ya que **la sesión del cliente se almacenó localmente en la *instancia n.° 1 ***.

Se han encontrado soluciones para abordar este problema (p. ej., configurando replicación de sesión y/o sesión persistente), pero la arquitectura REST propone otro enfoque: simplemente no haga que su servidor tenga estado, * hágalo sin estado *. Según Fielding:

Cada solicitud del cliente al servidor debe contener toda la información necesaria para comprender la solicitud y no puede aprovechar ningún contexto almacenado en el servidor. Por lo tanto, el estado de la sesión se mantiene completamente en el cliente.

En otras palabras, una solicitud debe manejarse exactamente de la misma manera, independientemente de si se envía a la instancia n.º 1 o a la instancia n.º 2. Esta es la razón por la que las aplicaciones sin estado se consideran más fáciles de escalar.

¿Cómo?

Un enfoque común es una autenticación basada en tokens, especialmente con los modernos tokens web JSON. Sin embargo, tenga en cuenta que JWT todavía tiene algunos problemas, particularmente con respecto a [invalidación] (http://stackoverflow.com/q/21978658/1225328) y [prolongación automática de vencimiento] (http://stackoverflow.com/q/26739167/1225328 ) (es decir, la función recuérdame).

Notas al margen

El uso de cookies o encabezados (o cualquier otra cosa) no tiene nada que ver con si el servidor tiene estado o no: estos son solo medios que se usan aquí para transportar tokens (identificador de sesión para servidores con estado, JWT, etc.), nada más.

Cuando solo los navegadores utilizan una API RESTful, (HttpOnly y [secure](https://en.wikipedia.org/wiki/HTTP_cookie# Las cookies Secure_cookie)) pueden ser bastante convenientes ya que los navegadores las adjuntarán automáticamente a las solicitudes salientes. Vale la pena mencionar que si opta por las cookies, tenga cuidado con CSRF (una buena manera de evitarlo es hacer que los clientes generar y enviar el mismo valor secreto único tanto en una cookie como en un encabezado HTTP personalizado ).


API almacenable en caché con [solicitudes condicionales] (https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests)

Con el encabezado Última modificación

El servidor puede proporcionar un encabezado de fecha Última modificación a las respuestas que contienen recursos que se pueden almacenar en caché. Los clientes deben almacenar esta fecha junto con el recurso.

Ahora, cada vez que los clientes solicitan a la API que lea el recurso, pueden agregar a sus solicitudes un encabezado If-Modified-Since que contiene la última fecha de “Última modificación” que recibieron y almacenaron. Luego, el servidor tiene que comparar el encabezado de la solicitud y la fecha real de última modificación del recurso. Si son iguales, el servidor devuelve un 304 (NO MODIFICADO) con un cuerpo vacío: el cliente solicitante debe usar el recurso almacenado en caché actualmente. posee.

Además, cuando los clientes solicitan a la API que actualice el recurso (es decir, con un verbo inseguro), pueden agregar un encabezado [If-Unmodified-Since] (https://tools.ietf.org/html/rfc7232#section- 3.4). Esto ayuda a lidiar con las condiciones de carrera: si el encabezado y la fecha real de última modificación son diferentes, el servidor devuelve un 412 (FALLO EN LA CONDICIÓN PREVIA). A continuación, el cliente debe leer el nuevo estado del recurso antes de volver a intentar modificarlo.

Con el encabezado ETag

Una ETag (etiqueta de entidad) es un identificador para un estado específico de un recurso. Puede ser un hash MD5 del recurso para una validación fuerte, o un identificador específico de dominio para una [ validación débil] (https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests#Weak_validation).

Básicamente, el proceso es el mismo que con el encabezado Última modificación: el servidor proporciona un encabezado ETag a las respuestas que contienen recursos que se pueden almacenar en caché, y los clientes deben almacenar este identificador junto con el recurso.

Luego, los clientes proporcionan un encabezado [If-None-Match] (https://tools.ietf.org/html/rfc7232#section-3.2) cuando desean leer el recurso, que contiene la última ETag que recibieron y almacenaron. . El servidor ahora puede devolver un 304 (NO MODIFICADO) si el encabezado coincide con la ETag real del recurso.

Nuevamente, los clientes pueden proporcionar un encabezado [If-Match] (https://tools.ietf.org/html/rfc7232#section-3.1) cuando desean modificar el recurso, y el servidor debe devolver un 412 (FALLÓ LA CONDICIÓN PREVIA) si la ETag proporcionada no coincide con la real.

Notas adicionales

ETag > fecha

Si los clientes proporcionan tanto la fecha como la ETag en sus solicitudes, se debe ignorar la fecha. De RFC 7232 (aquí y aquí) :

Un destinatario DEBE ignorar If-Modified-Since/If-Unmodified-Since si la solicitud contiene un campo de encabezado If-None-Match/If-Match; la condición en If-None-Match/If-Match se considera un reemplazo más preciso de la condición en If-Modified-Since/If-Unmodified-Since, y las dos solo se combinan en aras de interoperar con intermediarios más antiguos que podrían no implementar If-None-Match/If-Match.

Etiquetas electrónicas poco profundas

Además, si bien es bastante obvio que las últimas fechas de modificación se conservan junto con los recursos del lado del servidor, [varios enfoques] (http://stackoverflow.com/q/12049642/1225328) están disponibles con ETag.

Un enfoque habitual es implementar ETags poco profundos: el servidor procesa la solicitud como si no se proporcionaran encabezados condicionales, pero solo al final, genera el ETag de la respuesta que está a punto de devolver (por ejemplo, haciéndolo hash) y compara con el proporcionado. Esto es relativamente fácil de implementar ya que solo se necesita un interceptor HTTP (y ya existen muchas implementaciones según el servidor). Dicho esto, vale la pena mencionar que este enfoque [ahorrará ancho de banda pero no rendimiento del servidor] (http://www.baeldung.com/etags-for-rest-with-spring):

Una implementación más profunda del mecanismo ETag podría proporcionar beneficios mucho mayores, como atender algunas solicitudes de la memoria caché y no tener que realizar ningún cálculo, pero la implementación definitivamente no sería tan simple ni tan conectable como el enfoque superficial descrito aquí.


Errores comunes

¿Por qué no debería poner verbos en una URL?

HTTP no es RPC: lo que hace que HTTP sea significativamente diferente de RPC es que las solicitudes se dirigen a recursos. Después de todo, URL significa Localizador uniforme de recursos, y una URL es un URI: un identificador uniforme de recursos. La URL tiene como objetivo el recurso con el que desea tratar, el método HTTP indica lo que desea hacer con él. Los métodos HTTP también se conocen como verbos: los verbos en las URL no tienen sentido. Tenga en cuenta que las relaciones HATEOAS tampoco deben contener verbos, ya que los enlaces también apuntan a los recursos.

¿Cómo actualizar parcialmente un recurso?

Dado que las solicitudes PUT piden a los clientes que envíen todo el recurso con los valores actualizados, PUT /users/123 no se puede usar simplemente para actualizar el correo electrónico de un usuario, por ejemplo. Como explica William Durand en Por favor. Don’t Patch Like An Idiot., hay disponibles varias soluciones compatibles con REST:

  • Exponga las propiedades del recurso y use el método PUT para enviar un valor actualizado, ya que la [especificación PUT] (https://tools.ietf.org/html/rfc7231#section-4.3.4) establece que * las actualizaciones de contenido parciales son posibles al apuntar a un recurso identificado por separado con un estado que se superpone a una parte del recurso más grande*:
PUT https://example.com/api/v1.2/users/123/email
body:
  [email protected]
  • Utilice una solicitud PATCH que contenga un conjunto de instrucciones que describan cómo se debe modificar el recurso (por ejemplo, siguiendo JSON Patch):
PATCH https://example.com/api/v1.2/users/123
body:
  [
    { "op": "replace", "path": "/email", "value": "[email protected]" }
  ]
  • Use una solicitud PATCH que contenga una representación parcial del recurso, como se propone en [comentario de Matt Chapman] (http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an- idiota/#comentario-1495702936):
PATCH https://example.com/api/v1.2/users/123
body:
  {
    "email": "[email protected]"
  }

¿Qué pasa con las acciones que no encajan en el mundo de las operaciones CRUD?

Citando a Vinay Sahni en [Prácticas recomendadas para diseñar una API RESTful pragmática] (http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api#restful):

Aquí es donde las cosas pueden ponerse borrosas. Hay una serie de enfoques:

  1. Reestructurar la acción para que aparezca como un campo de un recurso. Esto funciona si la acción no toma parámetros. Por ejemplo, una acción activar podría asignarse a un campo booleano activado y actualizarse a través de un PATCH al recurso.

  2. Trátelo como un subrecurso con principios RESTful. Por ejemplo, la API de GitHub te permite destacar un gist con PUT /gists/:id/star y unstar con DELETE /gists/:id/star.

  3. A veces realmente no tienes forma de mapear la acción a una estructura RESTful sensata. Por ejemplo, una búsqueda de múltiples recursos realmente no tiene sentido para ser aplicada al punto final de un recurso específico. En este caso, /search tendría más sentido aunque no sea un recurso. Esto está bien: solo haga lo correcto desde la perspectiva del consumidor de API y asegúrese de que esté documentado claramente para evitar confusiones.


Prácticas comunes

  • La API está documentada. Hay herramientas disponibles para ayudarlo a crear su documentación, p. Swagger o Spring REST Docs.

  • La API está [versionada] (http://stackoverflow.com/a/398564/1225328), ya sea a través de encabezados o a través de la URL:

https://example.com/api/v1.2/blogs/123/articles
                        ^^^^
  • Los recursos tienen [nombres en plural] (http://stackoverflow.com/a/21809963/1225328):
https://example.com/api/v1.2/blogs/123/articles
                             ^^^^^     ^^^^^^^^
  • Las URL usan kebab-case (las palabras están en minúsculas y separadas por guiones):
https://example.com/api/v1.2/quotation-requests
                             ^^^^^^^^^^^^^^^^^^
{
  ...,
  _links: {
    ...,
    self: { href: "https://example.com/api/v1.2/blogs/123/articles/789" }
    ^^^^
  }
}
  • Las relaciones HATEOAS usan lowerCamelCase (las palabras se escriben en minúsculas, luego en mayúsculas, excepto la primera, y se omiten los espacios), para permitir que los clientes de JavaScript usen la [notación de puntos] (https://developer.mozilla.org/en-US/docs /Web/JavaScript/Reference/Operators/Property_Accessors#Dot_notation) respetando las convenciones de nomenclatura de JavaScript al acceder a los enlaces:
{
  ...,
  _links: {
    ...,
    firstPage: { "href": "https://example.com/api/v1.2/blogs/123/articles?pageIndex=1&pageSize=25" }
    ^^^^^^^^^
  }
}

Gestión de blogs a través de una API RESTful HTTP

Los siguientes ejemplos usan HAL para expresar HATEOAS y hacen uso de:

  • CURIE (URI compacta): se usa para proporcionar enlaces a la documentación de la API
  • Plantillas de URI: URI que incluye parámetros que deben sustituirse antes de que se resuelva el URI

Obtener blog 123

Solicitud

GET https://example.com/api/v1.2/blogs/123
headers:
  Accept: application/hal+json

Respuesta

status: 200 (OK)
headers:
  Content-Type: application/hal+json
body:
  {
    "id": 123,
    "title": "The blog title",
    "description": "The blog description",
    "_links": {
      "curies": [{ "name": "doc", "href": "https://example.com/docs/{rel}", "templated": true }],
      "self": { "href": "https://example.com/api/v1.2/blogs/123" },
      "doc:articles": { "href": "https://example.com/api/v1.2/blogs/123/articles{?pageIndex,pageSize}", "templated": true }
    }
  }

Crear un nuevo artículo en el blog 123

Solicitud

POST https://example.com/api/v1.2/blogs/123/articles
headers:
  Content-Type: application/json
  Accept: application/hal+json
  X-Access-Token: XYZ
body:
  {
    "title": "The title 2",
    "content": "The content 2"
  }

Respuesta

status: 201 (CREATED)
headers:
  Content-Type: application/hal+json
body:
  {
    "id": 789,
    "title": "The title 2",
    "content": "The content 2",
    "_links": {
      "curies": [{ "name": "doc", "href": "https://example.com/docs/{rel}", "templated": true }],
      "self": { "href": "https://example.com/api/v1.2/blogs/123/articles/789" },
      "doc:blog": { "href": "https://example.com/api/v1.2/blogs/123", "title": "The blog title" },
      "doc:comments": { "href": "https://example.com/api/v1.2/blogs/123/articles/789/comments{?pageIndex,pageSize}", "templated": true }
    }
  }

Obtenga el artículo 789 del blog 123

Solicitud

GET https://example.com/api/v1.2/blogs/123/articles/789
headers:
  Accept: application/hal+json

Respuesta

status: 200 (OK)
headers:
  Content-Type: application/hal+json
body:
  {
    "id": 789,
    "title": "The title 2",
    "content": "The content 2",
    "_links": {
      "curies": [{ "name": "doc", "href": "https://example.com/docs/{rel}", "templated": true }],
      "self": { "href": "https://example.com/api/v1.2/blogs/123/articles/789" },
      "doc:blog": { "href": "https://example.com/api/v1.2/blogs/123", "title": "The blog title" },
      "doc:comments": { "href": "https://example.com/api/v1.2/blogs/123/articles/789/comments{?pageIndex,pageSize}", "templated": true }
    }
  }

Obtenga la página 4 de 25 artículos del blog 123

Solicitud

GET https://example.com/api/v1.2/blogs/123/articles?pageIndex=4&pageSize=25
headers:
  Accept: application/hal+json

Respuesta

status: 200 (OK)
headers:
  Content-Type: application/hal+json
body:
  {
    "pageIndex": 4,
    "pageSize": 25,
    "totalPages": 26,
    "totalArticles": 648,
    "_link": {
      "firstPage": { "href": "https://example.com/api/v1.2/blogs/123/articles?pageIndex=1&pageSize=25" },
      "previousPage": { "href": "https://example.com/api/v1.2/blogs/123/articles?pageIndex=3&pageSize=25" },
      "self": { "href": "https://example.com/api/v1.2/blogs/123/articles?pageIndex=4&pageSize=25" },
      "nextPage": { "href": "https://example.com/api/v1.2/blogs/123/articles?pageIndex=5&pageSize=25" },
      "lastPage": { "href": "https://example.com/api/v1.2/blogs/123/articles?pageIndex=26&pageSize=25" }
    },
    "_embedded": [
      {
        ...
      }, {
        "id": 456,
        "title": "The title 1",
        "content": "The content 1",
        "_links": {
          "curies": [{ "name": "doc", "href": "https://example.com/docs/{rel}", "templated": true }],
          "self": { "href": "https://example.com/api/v1.2/blogs/123/articles/456" },
          "doc:blog": { "href": "https://example.com/api/v1.2/blogs/123", "title": "The blog title" },
          "doc:comments": { "href": "https://example.com/api/v1.2/blogs/123/articles/456/comments{?pageIndex,pageSize}", "templated": true }
        }
      }, {
        "id": 789,
        "title": "The title 2",
        "content": "The content 2",
        "_links": {
          "curies": [{ "name": "doc", "href": "https://example.com/docs/{rel}", "templated": true }],
          "self": { "href": "https://example.com/api/v1.2/blogs/123/articles/789" },
          "doc:blog": { "href": "https://example.com/api/v1.2/blogs/123", "title": "The blog title" },
          "doc:comments": { "href": "https://example.com/api/v1.2/blogs/123/articles/789/comments{?pageIndex,pageSize}", "templated": true }
        }
      }, {
        ...
      }
    ]
  }

Actualización artículo 789 del blog 123

Solicitud

PUT https://example.com/api/v1.2/blogs/123/articles/789
headers:
  Content-Type: application/json
  Accept: application/hal+json
  X-Access-Token: XYZ
body:
  {
    "id": 789,
    "title": "The title 2 updated",
    "content": "The content 2 updated"
  }

Respuesta

status: 200 (OK)
headers:
  Content-Type: application/hal+json
body:
  {
    "id": 789,
    "title": "The title 2 updated",
    "content": "The content 2 updated",
    "_links": {
      "curies": [{ "name": "doc", "href": "https://example.com/docs/{rel}", "templated": true }],
      "self": { "href": "https://example.com/api/v1.2/blogs/123/articles/789" },
      "doc:blog": { "href": "https://example.com/api/v1.2/blogs/123", "title": "The blog title" },
      "doc:comments": { "href": "https://example.com/api/v1.2/blogs/123/articles/789/comments{?pageIndex,pageSize}", "templated": true }
    }
  }

Notas

  • El identificador que se utiliza para identificar el recurso a actualizar es el de la URL: el del cuerpo (si lo hay) debe ignorarse silenciosamente.
  • Como una solicitud PUT actualiza todo el recurso, si no se hubiera enviado ningún contenido, debería haberse eliminado del recurso persistente.

Eliminar artículo 789 del blog 123

Solicitud

DELETE https://example.com/api/v1.2/blogs/123/articles/789
headers:
  Accept: application/hal+json
  X-Access-Token: XYZ

Respuesta

status: 204 (NO CONTENT)
headers:
  Content-Type: application/hal+json
body:
  {
    "_links": {
      "curies": [{ "name": "doc", "href": "https://example.com/docs/{rel}", "templated": true }],
      "doc:blog": { "href": "https://example.com/api/v1.2/blogs/123", "title": "The blog title" }
    }
  }

Violando el RESTO

<stock>
    <add>
        <item>
            <name>Milk</name>
            <quantity>2</quantity>
        </item>
    </add>
</stock>

Poner este cuerpo en un recurso como /stocks/123 viola la idea detrás de REST. Si bien este cuerpo es “poner” y contiene toda la información necesaria, también viene con una llamada de método para “agregar” en algún lugar cuando se procesa el cuerpo. Siguiendo REST uno publicaría el artículo en /stocks/123/items/.