Llamar a un método del lado del servidor en un recurso de una manera RESTful

142

Tenga en cuenta que tengo una comprensión rudimentaria de REST. Digamos que tengo esta URL:

http://api.animals.com/v1/dogs/1/

Y ahora, quiero hacer que el servidor haga ladrar al perro. Solo el servidor sabe cómo hacer esto. Digamos que quiero que se ejecute en un trabajo CRON que hace que el perro ladre cada 10 minutos por el resto de la eternidad. ¿Cómo se ve esa llamada? Como que quiero hacer esto:

Solicitud de URL:

ACTION http://api.animals.com/v1/dogs/1/

En el cuerpo de la solicitud:

{"action":"bark"}

Antes de que te enojes conmigo por haber inventado mi propio método HTTP, ayúdame y dame una mejor idea sobre cómo debería invocar un método del lado del servidor de una manera RESTful. :)

EDITAR PARA ACLARAR

Algunas aclaraciones más sobre lo que hace el método "ladrar". Estas son algunas opciones que pueden dar como resultado llamadas API estructuradas de manera diferente:

  1. ladrar solo envía un correo electrónico a dog.email y no registra nada.
  2. ladrar envía un correo electrónico a dog.email y aumenta dog.barkCount en 1.
  3. ladrar crea un nuevo registro de "ladrido" con la grabación bark.timestamp cuando se produce el ladrido. También incrementa dog.barkCount en 1.
  4. ladrar ejecuta un comando del sistema para extraer la última versión del código de perro de Github. Luego envía un mensaje de texto a dog.owner diciéndoles que el nuevo código de perro está en producción.
Kirk Ouimet
fuente
14
Curiosamente, agregar una recompensa parece haber atraído respuestas peores de las que tenía originalmente ;-) Al evaluar las respuestas recuerde que: 1) Las especificaciones para los verbos HTTP excluyen cualquier opción que no sea POST. 2) REST no tiene nada que ver con la estructura de URL: es una lista genérica de restricciones (sin estado, almacenable en caché, en capas, interfaz uniforme, etc.) que confieren beneficios (escalabilidad, confiabilidad, visibilidad, etc.). 3) La práctica actual (como el uso de POST en las especificaciones de RPC) supera a los definicionistas que están inventando sus propias reglas API. 4) REST requiere una interfaz uniforme (siguiendo la especificación HTTP).
Raymond Hettinger
@Kirk, ¿qué piensas sobre las nuevas respuestas? ¿Hay algo que aún quieras saber pero que no se haya abordado en ninguno de ellos? Estaría más que feliz de editar mi respuesta nuevamente si puede ser más útil.
Jordan
@RaymondHettinger PATCHpuede ser apropiado. Explico por qué hacia el final de mi respuesta .
Jordan
PATCH solo sería apropiado para incrementar el dog.barkCount en uno. POST es el método para enviar correos electrónicos, crear un nuevo registro de ladridos, ejecutar comandos para descargar desde Github o activar un mensaje de texto. @ Jordania, su lectura del PATCH RFC es imaginativa pero algo contraria a su intención como una variante de PUT para la modificación parcial de los recursos. No creo que esté ayudando al OP al obtener lecturas no convencionales de las especificaciones HTTP en lugar de reconocer la práctica estándar de usar POST para llamadas a procedimientos remotos.
Raymond Hettinger
@RaymondHettinger, cuya práctica de facto estandariza POST? Todas las interfaces RPC estándar que he visto identifican un recurso por entidad (no RESTful), frente a URI, por lo que una respuesta válida que priorice la convención RPC debería ser poco convencional de todos modos, lo que creo que refuta el valor de RPC convencional: uno es imaginativo o inconsistente . Nunca puede equivocarse con POST, ya que es el conjunto para el procesamiento de datos, pero hay métodos más específicos. REST significa nombrar recursos y describir cambios en su estado, no nombrar procedimientos de cambio de estado. PATCH y POST describen cambios de estado.
Jordan

Respuestas:

280

¿Por qué apuntar a un diseño RESTful?

Los principios RESTful traen las características que hacen que los sitios web sean fáciles (para que un usuario humano aleatorio los "navegue") al diseño de la API de servicios web , por lo que son fáciles de usar para un programador. REST no es bueno porque es REST, es bueno porque es bueno. Y es bueno sobre todo porque es simple .

La simplicidad de HTTP simple (sin sobres SOAP y POSTservicios sobrecargados de URI único ), lo que algunos pueden llamar "falta de características" , es en realidad su mayor fortaleza . De inmediato, HTTP le pide que tenga direccionabilidad y apatridia : las dos decisiones de diseño básicas que mantienen a HTTP escalable hasta los mega-sitios (y mega-servicios) actuales.

Pero REST no es el bulltet plateado: a veces, un estilo RPC ("Llamada a procedimiento remoto", como SOAP) puede ser apropiado , y otras necesidades tienen prioridad sobre las virtudes de la Web. Esto esta bien. Lo que realmente no nos gusta es la complejidad innecesaria . Con demasiada frecuencia, un programador o una empresa incorporan servicios de estilo RPC para un trabajo que HTTP antiguo podría manejar perfectamente. El efecto es que HTTP se reduce a un protocolo de transporte para una enorme carga útil XML que explica lo que "realmente" está sucediendo (no el URI o el método HTTP dan una pista al respecto). El servicio resultante es demasiado complejo, imposible de depurar y no funcionará a menos que sus clientes tengan la configuración exacta que el desarrollador pretendía.

De la misma manera, un código Java / C # no puede estar orientado a objetos, solo usar HTTP no hace que un diseño RESTful. Uno puede estar atrapado en la prisa de pensar en sus servicios en términos de acciones y métodos remotos que deberían llamarse. No es de extrañar que esto termine principalmente en un servicio de estilo RPC (o un híbrido REST-RPC). El primer paso es pensar de manera diferente. Se puede lograr un diseño RESTful de muchas maneras, una es pensar en su aplicación en términos de recursos, no de acciones:

💡 En lugar de pensar en términos de acciones que puede realizar ("hacer una búsqueda de lugares en el mapa") ...

... trate de pensar en términos de los resultados de esas acciones ("la lista de lugares en el mapa que coincide con un criterio de búsqueda").

Iré por los ejemplos a continuación. (Otro aspecto clave de REST es el uso de HATEOAS: no lo cepillo aquí, pero lo hablo rápidamente en otra publicación ).


Problemas del primer diseño

Echemos un vistazo al diseño propuesto:

ACTION http://api.animals.com/v1/dogs/1/

En primer lugar, no deberíamos considerar crear un nuevo verbo HTTP ( ACTION). En términos generales, esto no es deseable por varias razones:

  • (1) Dado solo el URI del servicio, ¿cómo sabrá un programador "aleatorio"ACTION existe verbo?
  • (2) si el programador sabe que existe, ¿cómo sabrá su semántica? ¿Qué significa ese verbo?
  • (3) ¿qué propiedades (seguridad, idempotencia) debería uno esperar que tenga ese verbo?
  • (4) ¿qué pasa si el programador tiene un cliente muy simple que solo maneja verbos HTTP estándar?
  • (5) ...

Ahora consideremos usarPOST (discutiremos por qué a continuación, solo tome mi palabra ahora):

POST /v1/dogs/1/ HTTP/1.1
Host: api.animals.com

{"action":"bark"}

Esto podría estar bien ... pero solo si :

  • {"action":"bark"}era un documento; y
  • /v1/dogs/1/era un URI de "procesador de documentos" (similar a una fábrica). Un "procesador de documentos" es un URI en el que simplemente "arrojaría cosas" y "olvidaría" sobre ellos; el procesador puede redirigirlo a un recurso recién creado después del "lanzamiento". Por ejemplo, el URI para publicar mensajes en un servicio de intermediario de mensajes, que, después de la publicación, lo redirigiría a un URI que muestra el estado del procesamiento del mensaje.

No sé mucho sobre su sistema, pero apuesto a que ambos no son ciertos:

  • {"action":"bark"} no es un documento , en realidad es el método que está tratando de introducir ninja en el servicio; y
  • el /v1/dogs/1/URI representa un recurso "perro" (probablemente el perro con id==1) y no un procesador de documentos.

Entonces, todo lo que sabemos ahora es que el diseño anterior no es tan RESTANTE, pero ¿qué es eso exactamente? ¿Qué tiene de malo? Básicamente, es malo porque es un URI complejo con significados complejos. No se puede inferir nada de eso. ¿Cómo sabría un programador que un perro tiene una barkacción que se puede infundir secretamente con un perro POST?


Diseñando las llamadas a la API de tu pregunta

Así que vamos al grano e intentemos diseñar esos ladridos RESTAMENTE pensando en términos de recursos . Permítame citar el libro de Restful Web Services :

Una POSTsolicitud es un intento de crear un nuevo recurso a partir de uno existente. El recurso existente puede ser el padre del nuevo en un sentido de estructura de datos, la forma en que la raíz de un árbol es el padre de todos sus nodos hoja. O el recurso existente puede ser un recurso especial de "fábrica" cuyo único propósito es generar otros recursos. La representación enviada junto con una POSTsolicitud describe el estado inicial del nuevo recurso. Al igual que con PUT, una POSTsolicitud no necesita incluir una representación en absoluto.

Siguiendo la descripción anterior, podemos ver que barkpuede modelarse como un subrecurso de adog (dado que a barkestá contenido dentro de un perro, es decir, una corteza es "ladrada" por un perro).

De ese razonamiento ya obtuvimos:

  • El método es POST
  • El recurso es /barks, sub-recurso de dog:, que /v1/dogs/1/barksrepresenta una bark"fábrica". Ese URI es único para cada perro (ya que está debajo /v1/dogs/{id}).

Ahora cada caso de su lista tiene un comportamiento específico.

1. ladrar solo envía un correo electrónico a dog.email y no registra nada.

En primer lugar, ¿ladrar (enviar un correo electrónico) es una tarea síncrona o asíncrona? En segundo lugar, ¿la barksolicitud requiere algún documento (el correo electrónico, tal vez) o está vacío?


1.1 ladrar envía un correo electrónico a dog.email y no registra nada (como una tarea sincrónica)

Este caso es simple. Una llamada al barksrecurso de la fábrica produce un ladrido (un correo electrónico enviado) de inmediato y la respuesta (si está bien o no) se da de inmediato:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

(entity-body is empty - or, if you require a **document**, place it here)

200 OK

Como no registra (cambia) nada, 200 OKes suficiente. Muestra que todo salió como se esperaba.


1.2 ladrar envía un correo electrónico dog.emaily no registra nada (como una tarea asincrónica)

En este caso, el cliente debe tener una forma de rastrear la barktarea. La barktarea debería ser un recurso con su propio URI .:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

{document body, if needed;
NOTE: when possible, the response SHOULD contain a short hypertext note with a hyperlink
to the newly created resource (bark) URI, the same returned in the Location header
(also notice that, for the 202 status code, the Location header meaning is not
standardized, thus the importance of a hipertext/hyperlink response)}

202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44

De esta manera, cada uno barkes rastreable. El cliente puede emitir un GETa la barkURI para saber su estado actual. Tal vez incluso use a DELETEpara cancelarlo.


2. ladrar envía un correo electrónico dog.emaily luego incrementadog.barkCount en 1

Este puede ser más complicado si desea que el cliente sepa que el dogrecurso ha cambiado:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

{document body, if needed; when possible, containing a hipertext/hyperlink with the address
in the Location header -- says the standard}

303 See Other
Location: http://api.animals.com/v1/dogs/1

En este caso, la locationintención del encabezado es hacerle saber al cliente que debe echarle un vistazo dog. Del HTTP RFC sobre303 :

Este método existe principalmente para permitir que la salida de una POSTsecuencia de comandos activada redirija al agente de usuario a un recurso seleccionado.

Si la tarea es asíncrona, barkse necesita un subrecurso al igual que la 1.2situación y 303debe devolverse en el GET .../barks/Ymomento en que se completa la tarea.


3. ladrar crea un nuevo " bark" registro con bark.timestampgrabación cuando se produce la corteza. También se incrementa dog.barkCounten 1.

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

(document body, if needed)

201 Created
Location: http://api.animals.com/v1/dogs/1/barks/a65h44

Aquí, barkse crea debido a la solicitud, por lo que 201 Createdse aplica el estado .

Si la creación es asíncrona, 202 Acceptedse requiere a (en su lugar, como dice el RFC de HTTP ).

La marca de tiempo guardada es parte del barkrecurso y se puede recuperar con un GET. El perro actualizado puede ser "documentado" en eso GET dogs/X/barks/Ytambién.


4. ladrar ejecuta un comando del sistema para extraer la última versión del código de perro de Github. Luego envía un mensaje de texto para dog.ownerdecirles que el nuevo código de perro está en producción.

La redacción de este es complicada, pero es una tarea asincrónica simple:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

(document body, if needed)

202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44

Luego, el cliente emitiría GETs para /v1/dogs/1/barks/a65h44conocer el estado actual (si se extraía el código, se enviaba el correo electrónico al propietario y tal). Cada vez que el perro cambia, a 303es aplicable.


Terminando

Citando a Roy Fielding :

Lo único que REST requiere de los métodos es que se definan de manera uniforme para todos los recursos (es decir, para que los intermediarios no tengan que conocer el tipo de recurso para comprender el significado de la solicitud).

En los ejemplos anteriores, POSTestá diseñado uniformemente. Hará al perro " bark". Eso no es seguro (lo que significa que la corteza tiene efectos sobre los recursos), ni idempotente (cada solicitud produce un nuevo bark), que se ajusta bien al POSTverbo.

Un programador sabría: a POSTpara producir barksunbark . Los códigos de estado de respuesta (también con entidad-cuerpo y encabezados cuando es necesario) hacen el trabajo de explicar qué cambió y cómo el cliente puede y debe proceder.

Nota: Las fuentes principales utilizadas fueron: el libro " Restful Web Services ", el HTTP RFC y el blog de Roy Fielding .




Editar:

La pregunta y, por lo tanto, la respuesta han cambiado bastante desde su creación. La pregunta original fue sobre el diseño de un URI como:

ACTION http://api.animals.com/v1/dogs/1/?action=bark

A continuación se muestra la explicación de por qué no es una buena opción:

Cómo los clientes le dicen al servidor QUÉ HACER con los datos es la información del método .

  • Los servicios web RESTful transmiten información del método en el método HTTP.
  • Los servicios típicos de estilo RPC y SOAP mantienen los suyos en el cuerpo de la entidad y el encabezado HTTP.

EN QUÉ PARTE de los datos [el cliente quiere que el servidor] opere es la información de alcance .

  • Los servicios RESTful usan el URI. Los servicios SOAP / RPC-Style utilizan una vez más el cuerpo de la entidad y los encabezados HTTP.

Como ejemplo, tome el URI de Google http://www.google.com/search?q=DOG. Allí, la información del método es GETy la información del alcance es /search?q=DOG.

Larga historia corta:

  • En arquitecturas RESTful , la información del método entra en el método HTTP.
  • En las arquitecturas orientadas a recursos , la información de alcance va al URI.

Y la regla de oro:

Si el método HTTP no coincide con la información del método, el servicio no es RESTful. Si la información de alcance no está en el URI, el servicio no está orientado a los recursos.

Puede poner la " acción" "ladrar" en la URL (o en el cuerpo de la entidad) y usar POST. No hay problema, funciona, y puede ser la forma más sencilla de hacerlo, pero esto no es RESTful .

Para mantener su servicio realmente RESTANTE, es posible que tenga que dar un paso atrás y pensar qué es lo que realmente quiere hacer aquí (qué efectos tendrá en los recursos).

No puedo hablar sobre sus necesidades comerciales específicas, pero permítame darle un ejemplo: considere un servicio de pedidos RESTful donde los pedidos están en URI como example.com/order/123 .

Ahora digamos que queremos cancelar un pedido, ¿cómo lo haremos? Uno puede estar tentado a pensar que es una "acción" de "cancelación " y diseñarlo comoPOST example.com/order/123?do=cancel .

Eso no es RESTful, como hemos dicho anteriormente. En cambio, podríamos PUTuna nueva representación de la ordercon un canceledelemento enviado a true:

PUT /order/123 HTTP/1.1
Content-Type: application/xml

<order id="123">
    <customer id="89987">...</customer>
    <canceled>true</canceled>
    ...
</order>

Y eso es. Si el pedido no se puede cancelar, se puede devolver un código de estado específico. (Un diseño de subrecursos, como POST /order/123/canceledcon el cuerpo de la entidad truepuede, por simplicidad, también estar disponible).

En su escenario específico, puede intentar algo similar. De esa manera, mientras un perro ladra, por ejemplo, un GETat /v1/dogs/1/podría incluir esa información (por ejemplo <barking>true</barking>) . O ... si eso es demasiado complicado, afloje su requisito RESTful y siga conPOST .

Actualizar:

No quiero que la respuesta sea demasiado grande, pero me toma un tiempo acostumbrarme a exponer un algoritmo (una acción ) como un conjunto de recursos. En lugar de pensar en términos de acciones ( "hacer una búsqueda de lugares en el mapa" ), uno debe pensar en términos de los resultados de esa acción ( "la lista de lugares en el mapa que coincide con un criterio de búsqueda" ).

Es posible que vuelva a este paso si descubre que su diseño no se ajusta a la interfaz uniforme de HTTP.

Las variables de consulta son información de alcance , pero no denotan nuevos recursos ( /post?lang=enes claramente el mismo recurso que /post?lang=jp, solo una representación diferente). Más bien, se utilizan para transmitir el estado del cliente (como ?page=10, por ejemplo , ese estado no se mantiene en el servidor; ?lang=entambién es un ejemplo aquí) o parámetros de entrada a recursos algorítmicos ( /search?q=dogs, /dogs?code=1). De nuevo, no recursos distintos.

Propiedades de los verbos HTTP (métodos):

Otro punto claro que se muestra ?action=somethingen el URI no es RESTful, son las propiedades de los verbos HTTP:

  • GETy HEADson seguros (e idempotentes);
  • PUTy DELETEsolo son idempotentes;
  • POST Es ninguno.

Seguridad : Una GETo HEADsolicitud es una solicitud para leer algunos datos, no una solicitud para cambiar cualquier estado del servidor. El cliente puede hacer GETo HEADsolicitar 10 veces y es lo mismo que hacerlo una vez, o nunca hacerlo .

Idempotencia : una operación idempotente en una que tiene el mismo efecto si la aplicas una o más de una vez (en matemáticas, multiplicar por cero es idempotente). Si tiene DELETEun recurso una vez, la eliminación nuevamente tendrá el mismo efecto (el recurso GONEya está ).

POSTNo es seguro ni idempotente. Hacer dos POSTsolicitudes idénticas a un recurso 'de fábrica' probablemente dará como resultado dos recursos subordinados que contengan la misma información. Con sobrecargado (método en URI o entidad-cuerpo) POST, todas las apuestas están desactivadas.

Ambas propiedades fueron importantes para el éxito del protocolo HTTP (a través de redes poco confiables): ¿cuántas veces ha actualizado (GET ) la página sin esperar hasta que esté completamente cargada?

Crear una acción y colocarla en la URL claramente rompe el contrato de los métodos HTTP. Una vez más, la tecnología te permite, puedes hacerlo, pero ese no es un diseño RESTful.

acdcjunior
fuente
Me refiero a la idea de que llamar a una acción en un servidor, designado como una acción en la URL, no es RESTful. POSTfue diseñado para "proporcionar un bloque de datos ... a un proceso de manejo de datos" . Parece que mucha gente distingue los recursos de las acciones, pero en realidad las acciones son solo un tipo de recurso.
Jacob Stevens
1
@JacobStevens El OP cambió la pregunta un poco, así que tengo que actualizar mi respuesta para hacerlo más directo (verifique la pregunta original , tal vez verá lo que quiero decir). Estoy de acuerdo con POST"proporcionar un bloque de datos ... a un proceso de manejo de datos", pero la diferencia es realmente que, un bloque de datos , no un bloque de datos y el procedimiento (acción, método, comando) ejecutado el entonces. Eso es una POSTsobrecarga, y la POSTsobrecarga es un diseño de estilo RPC, no RESTful.
acdcjunior
Supongo que la lógica de acción / método se alojaría en el servidor, de lo contrario, ¿cuál sería el propósito de la llamada? En el caso que describa, estoy de acuerdo, ese no sería un buen diseño. Pero el método o subrutina que realiza la acción estaría especificado por el URI (que es otra razón por la cual un recurso de acción designado como verbo al final de una URL es útil y RESTful, aunque muchos desaconsejan).
Jacob Stevens
66
La respuesta nos actualizó. Es un poco largo porque parecía necesaria una explicación completa ("Tenga en cuenta que tengo una comprensión rudimentaria de REST"). Fue una especie de lucha dejarlo lo más claro posible. Espero que sea útil de alguna manera.
acdcjunior
2
Gran explicación, voté pero el encabezado Ubicación no debe usarse en 202 Respuesta aceptada. Parece ser una interpretación incorrecta que muchas personas hacen de RFC. Consulte este stackoverflow.com/questions/26199228/…
Delmo
6

Yo contesté antes , pero esta respuesta contradice mi viejo respuesta y sigue una estrategia muy diferente para llegar a una solución. Muestra cómo se construye la solicitud HTTP a partir de los conceptos que definen REST y HTTP. También usa en PATCHlugar de POSToPUT .

Pasa por las restricciones REST, luego los componentes de HTTP, luego una posible solución.

DESCANSO

REST es un conjunto de restricciones destinadas a aplicarse a un sistema hipermedia distribuido para hacerlo escalable. Incluso para tener sentido en el contexto del control remoto de una acción, debe pensar en controlar remotamente una acción como parte de un sistema hipermedia distribuido, una parte de un sistema para descubrir, ver y modificar información interconectada. Si eso es más problema de lo que vale, entonces probablemente no sea bueno intentar que sea RESTANTE. Si solo desea una GUI de tipo "panel de control" en el cliente que pueda desencadenar acciones en el servidor a través del puerto 80, entonces probablemente desee una interfaz RPC simple como JSON-RPC a través de solicitudes / respuestas HTTP o un WebSocket.

Pero REST es una forma fascinante de pensar y el ejemplo en la pregunta resulta fácil de modelar con una interfaz RESTful, así que asumamos el desafío por diversión y educación.

REST está definido por cuatro restricciones de interfaz:

identificación de recursos; manipulación de recursos a través de representaciones; mensajes autodescriptivos; e hipermedia como motor del estado de la aplicación.

Usted pregunta cómo puede definir una interfaz, cumpliendo con estas restricciones, a través de las cuales una computadora le dice a otra computadora que ladre un perro. Específicamente, desea que su interfaz sea HTTP, y no desea descartar las características que hacen que HTTP RESTful se use según lo previsto.

Comencemos con la primera restricción: identificación de recursos .

Cualquier información que se pueda nombrar puede ser un recurso: un documento o imagen, un servicio temporal (por ejemplo, "el clima de hoy en Los Ángeles"), una colección de otros recursos, un objeto no virtual (por ejemplo, una persona), etc. .

Entonces un perro es un recurso. Necesita ser identificado.

Más precisamente, un recurso R es una función de pertenencia temporalmente variable M R ( t ), que por tiempo t se asigna a un conjunto de entidades, o valores, que son equivalentes. Los valores en el conjunto pueden ser representaciones de recursos y / o identificadores de recursos .

Puede modelar un perro tomando un conjunto de identificadores y representaciones y diciendo que todos están asociados entre sí en un momento dado. Por ahora, usemos el identificador "perro # 1". Eso nos lleva a la segunda y tercera limitaciones: representación de recursos y autodescripción .

Los componentes REST realizan acciones en un recurso utilizando una representación para capturar el estado actual o previsto de ese recurso y transfiriendo esa representación entre componentes. Una representación es una secuencia de bytes, más metadatos de representación para describir esos bytes.

A continuación se muestra una secuencia de bytes que capturan el estado deseado del perro, es decir, la representación que deseamos asociar con el identificador "perro # 1" (tenga en cuenta que solo representa parte del estado, ya que no tiene en cuenta el nombre del perro, la salud , o incluso ladridos pasados):

Ha estado ladrando cada 10 minutos desde el momento en que se realizó este cambio de estado, y continuará indefinidamente.

Se supone que debe adjuntarse a los metadatos que lo describen. Estos metadatos pueden ser útiles:

Es una declaración en inglés. Describe parte del estado previsto. Si se recibe varias veces, solo permita que el primero tenga efecto.

Finalmente, veamos la cuarta restricción: HATEOAS .

REST ... ve una aplicación como una estructura cohesiva de información y alternativas de control a través de la cual un usuario puede realizar una tarea deseada. Por ejemplo, buscar una palabra en un diccionario en línea es una aplicación, como recorrer un museo virtual o revisar un conjunto de apuntes de clase para estudiar para un examen. ... El siguiente estado de control de una aplicación reside en la representación del primer recurso solicitado, por lo que obtener esa primera representación es una prioridad. ... Por lo tanto, la aplicación modelo es un motor que se mueve de un estado a otro al examinar y elegir entre las transiciones de estado alternativas en el conjunto actual de representaciones.

En una interfaz RESTful, el cliente recibe una representación de recursos para averiguar cómo debe recibir o enviar una representación. Debe haber una representación en algún lugar de la aplicación desde la cual el cliente pueda descubrir cómo recibir o enviar todas las representaciones que debería poder recibir o enviar, incluso si sigue una cadena de representaciones para llegar a esa información. Esto parece bastante simple:

El cliente solicita una representación de un recurso identificado como la página de inicio; en respuesta, obtiene una representación que contiene un identificador de cada perro que el cliente pueda desear. El cliente extrae un identificador y le pregunta al servicio cómo puede interactuar con el perro identificado, y el servicio dice que el cliente puede enviar una declaración en inglés que describa parte del estado previsto del perro. Luego, el cliente envía dicha declaración y recibe un mensaje de éxito o un mensaje de error.

HTTP

HTTP implementa restricciones REST de la siguiente manera:

identificación de recursos : URI

representación de recursos : entidad-cuerpo

autodescripción : código de método o estado, encabezados y posiblemente partes del cuerpo de la entidad (por ejemplo, el URI de un esquema XML)

HATEOAS : hipervínculos

Has decidido en http://api.animals.com/v1/dogs/1 como el URI. Supongamos que el cliente obtuvo esto de alguna página en el sitio.

Usemos este cuerpo de entidad (el valor de nextes una marca de tiempo; un valor de 0significa 'cuando se recibe esta solicitud'):

{"barks": {"next": 0, "frequency": 10}}

Ahora necesitamos un método. PATCH se ajusta a la descripción de "parte del estado deseado" que decidimos:

El método PATCH solicita que se aplique un conjunto de cambios descritos en la entidad de solicitud al recurso identificado por el URI de solicitud.

Y algunos encabezados:

Para indicar el lenguaje de la entidad-cuerpo: Content-Type: application/json

Para asegurarse de que solo ocurra una vez: If-Unmodified-Since: <date/time this was first sent>

Y tenemos una solicitud:

PATCH /v1/dogs/1/ HTTP/1.1
Host: api.animals.com
Content-Type: application/json
If-Unmodified-Since: <date/time this was first sent>
[other headers]

{"barks": {"next": 0, "frequency": 10}}

En caso de éxito, el cliente debe recibir un 204código de estado en respuesta, o 205si la representación de /v1/dogs/1/ha cambiado para reflejar el nuevo horario de ladridos.

En caso de falla, debe recibir un 403 mensaje útil y útil por qué.

No es esencial REST para que el servicio refleje el programa de ladridos en una representación en respuesta a GET /v1/dogs/1/, pero tendría más sentido si una representación JSON incluyera esto:

"barks": {
    "previous": [x_1, x_2, ..., x_n],
    "next": x_n,
    "frequency": 10
}

Trate el trabajo cron como un detalle de implementación que el servidor oculta de la interfaz. Esa es la belleza de una interfaz genérica. El cliente no tiene que saber qué hace el servidor detrás de escena; Lo único que le importa es que el servicio comprenda y responda a los cambios de estado solicitados.

Jordán
fuente
3

La mayoría de las personas usan POST para este propósito. Es apropiado para realizar "cualquier operación insegura o no impotente cuando ningún otro método HTTP parece apropiado".

Las API como XMLRPC usan POST para desencadenar acciones que pueden ejecutar código arbitrario. La "acción" se incluye en los datos POST:

POST /RPC2 HTTP/1.0
User-Agent: Frontier/5.1.2 (WinNT)
Host: betty.userland.com
Content-Type: text/xml
Content-length: 181

<?xml version="1.0"?>
<methodCall>
   <methodName>examples.getStateName</methodName>
   <params>
      <param>
         <value><i4>41</i4></value>
         </param>
      </params>
   </methodCall>

El ejemplo de RPC se da para mostrar que POST es la elección convencional de los verbos HTTP para los métodos del lado del servidor. Aquí están los pensamientos de Roy Fielding sobre POST : prácticamente dice que es RESTful usar los métodos HTTP como se especifica.

Tenga en cuenta que RPC en sí no es muy RESTful porque no está orientado a los recursos. Pero si necesita apatridia, almacenamiento en caché o capas, no es difícil realizar las transformaciones adecuadas. Ver http://blog.perfectapi.com/2012/opinionated-rpc-apis-vs-restful-apis/ para ver un ejemplo.

Raymond Hettinger
fuente
Creo que codificaría URL los parámetros para no ponerlos en la cadena de consulta
tacos_tacos_tacos
@Kirk Sí, pero con una modificación menor, suelte la barra diagonal final: POST api.animals.com/v1/dogs1?action=bark
Raymond Hettinger
Si sigue los consejos de esta respuesta, tenga en cuenta que la API resultante no será RESTful.
Nicholas Shanks
2
Esto no es RESTful porque HTTP establece la URL como el identificador de un recurso y una URL /RPC2no hace nada para identificar un recurso: identifica la tecnología de un servidor. En cambio, esto usa methodNamepara tratar de 'identificar' el 'recurso', pero aun así, no se beneficia de la distinción sustantivo / verbo; la única cosa como "verbo" aquí es methodCall. Esto es como 'hacer recuperación de nombre de estado' en lugar de 'recuperar nombre de estado': este último tiene mucho más sentido.
Jordan
+1 para los enlaces; muy informativo y el experimento "RPC obstinado" es inventivo.
Jordan
2

POSTes el método HTTP diseñado para

Proporcionar un bloque de datos ... a un proceso de manejo de datos

Los métodos del lado del servidor que manejan acciones no asignadas a CRUD es lo que Roy Fielding pretendía con REST, por lo que eres bueno allí, y es por eso que POSTse hizo no idempotente. POSTmanejará la mayoría de las publicaciones de datos en métodos del lado del servidor para procesar la información.

Dicho esto, en su escenario de ladrido de perros, si desea que se realice un ladrido del lado del servidor cada 10 minutos, pero por alguna razón necesita que el disparador se origine en un cliente, PUTserviría mejor el propósito, debido a su idempotencia. Bueno, estrictamente en este escenario no hay riesgo aparente de múltiples solicitudes POST que provoquen que tu perro maulle, pero de todos modos ese es el propósito de los dos métodos similares. Mi respuesta a una pregunta SO similar puede ser útil para usted.

Jacob Stevens
fuente
1
PUT vs. POST tiene que ver con la URL. El tercer párrafo después de 9.6 PUT dice que el propósito de los dos métodos es que la PUTURL se refiera a lo que debe ser reemplazado por el contenido del cliente y la POSTURL se refiera a lo que debe procesar el contenido del cliente como quiera.
Jordan
1

Si asumimos que Barking es un recurso interno / dependiente / secundario sobre el cual el consumidor puede actuar, entonces podríamos decir:

POST http://api.animals.com/v1/dogs/1/bark

perro número 1 ladra

GET http://api.animals.com/v1/dogs/1/bark

devuelve la última marca de tiempo de corteza

DELETE http://api.animals.com/v1/dogs/1/bark

no aplica! así que ignóralo.

bolbol
fuente
Esto solo es RESTful si considera /v1/dogs/1/barkque es un recurso per se y POSTuna descripción de cómo debería cambiar el estado interno de ese recurso. Me parece que tiene más sentido considerarlo /v1/dogs/1/como un recurso e indicar en el cuerpo de la entidad que debería ladrar.
Jordan
mmm ... bueno, es un recurso en el que puedes cambiar su estado. Debido a que el resultado de cambiar su estado es hacer ruido, ¡no lo hace con menos recursos! Estás viendo Bark como un verbo (que es) por eso no puedes considerarlo un recurso. Lo estoy viendo como un recurso dependiente cuyo estado se puede cambiar y dado que su estado es booleano, no veo ninguna razón para mencionarlo en entidad-cuerpo. Esa es solo mi opinión.
bolbol
1

Las revisiones anteriores de algunas respuestas sugirieron que usara RPC. No necesita mirar a RPC ya que es perfectamente posible hacer lo que quiera mientras se adhiere a las restricciones REST.

En primer lugar, no coloque parámetros de acción en la URL. La URL define a qué está aplicando la acción, y los parámetros de consulta son parte de la URL. Debe considerarse enteramente como un sustantivo. http://api.animals.com/v1/dogs/1/?action=barkes un recurso diferente, un sustantivo diferente para http://api.animals.com/v1/dogs/1/. [nb Asker ha eliminado el ?action=barkURI de la pregunta.] Por ejemplo, compárelo http://api.animals.com/v1/dogs/?id=1con http://api.animals.com/v1/dogs/?id=2. Diferentes recursos, que se distinguen solo por la cadena de consulta. Por lo tanto, la acción de su solicitud, a menos que corresponda directamente a un tipo de método existente sin cuerpo (TRACE, OPTIONS, HEAD, GET, DELETE, etc.) debe definirse en el cuerpo de la solicitud.

Luego, decida si la acción es " idempotente ", lo que significa que puede repetirse sin efectos adversos (consulte el siguiente párrafo para obtener más explicaciones). Por ejemplo, establecer un valor en verdadero se puede repetir si el cliente no está seguro de que sucedió el efecto deseado. Envían la solicitud nuevamente y el valor sigue siendo verdadero. Agregar 1 a un número no es idempotente. Si el cliente envía el comando Add1, no está seguro de que funcionó y lo envía nuevamente, ¿el servidor agregó uno o dos? Una vez que haya determinado eso, estará en una mejor posición para elegir entre PUTy POSTpara su método.

Idempotente significa que una solicitud puede repetirse sin cambiar el resultado. Estos efectos no incluyen el registro y otras actividades de administración del servidor. Usando su primer y segundo ejemplo, enviar dos correos electrónicos a la misma persona da como resultado un estado diferente al envío de un correo electrónico (el destinatario tiene dos en su bandeja de entrada, lo que podría considerar spam), por lo que definitivamente usaría POST para eso . Si barkCount en el ejemplo 2 está destinado a ser visto por un usuario de su API o afecta a algo que es visible para el cliente, entonces también es algo que haría que la solicitud no sea idempotente. Si solo debe ser visto por usted, entonces cuenta como un registro del servidor y debe ignorarse al determinar la idempotencia.

Por último, determine si se espera que la acción que desea realizar tenga éxito de inmediato o no. BarkDog es una acción que se completa rápidamente. RunMarathon no lo es. Si su acción es lenta, considere devolver un 202 Accepted, con una URL en el cuerpo de respuesta para que un usuario realice una encuesta para ver si la acción se ha completado. Alternativamente, /marathons-in-progress/haga que los usuarios PUBLICEN en una URL de lista como, y luego, cuando la acción esté hecha, rediríjalos de la URL de ID en progreso a/marathons-complete/ URL.
Para los casos específicos n. ° 1 y n. ° 2, el servidor alojaría una cola y el cliente le enviaría lotes de direcciones. La acción no sería SendEmails, sino algo así como AddToDispatchQueue. El servidor puede sondear la cola para ver si hay alguna dirección de correo electrónico en espera y enviar correos electrónicos si encuentra alguna. Luego actualiza la cola para indicar que la acción pendiente ahora se ha realizado. Tendría otro URI que muestra al cliente el estado actual de la cola. Para evitar el doble envío de correos electrónicos, el servidor también podría mantener un registro de a quién le envió este correo electrónico y verificar cada dirección para asegurarse de que nunca envíe dos a la misma dirección, incluso si PUBLICA la misma lista dos veces para la cola.

Al elegir un URI para cualquier cosa, trate de pensarlo como un resultado, no como una acción. Por ejemplo, google.com/search?q=dogsmuestra los resultados de una búsqueda de la palabra "perros". No necesariamente realiza la búsqueda.

Los casos # 3 y # 4 de su lista tampoco son acciones idempotentes. Sugiere que los diferentes efectos sugeridos pueden afectar el diseño de la API. En los cuatro casos, usaría la misma API, ya que los cuatro cambian el "estado mundial".

Nicholas Shanks
fuente
Digamos que la acción es pasar por una cola gigante de correo electrónico y enviar un mensaje a un montón de personas. ¿Eso es idempotente? ¿Son acciones idempotentes para PUT o POST?
Kirk Ouimet
@kirk He ampliado mi respuesta.
Nicholas Shanks
0

Vea mi nueva respuesta : contradice esta y explica REST y HTTP de manera más clara y precisa.

Aquí hay una recomendación que resulta RESTANTE, pero ciertamente no es la única opción. Para comenzar a ladrar cuando el servicio recibe la solicitud:

POST /v1/dogs/1/bark-schedule HTTP/1.1
...
{"token": 12345, "next": 0, "frequency": 10}

token es un número arbitrario que evita ladridos redundantes sin importar cuántas veces se envíe esta solicitud.

nextindica la hora de la próxima corteza; un valor de 0significa 'ASAP'.

Siempre que lo hagas GET /v1/dogs/1/bark-schedule, deberías obtener algo como esto, donde t es el tiempo de la última corteza y u es t + 10 minutos:

{"last": t, "next": u}

Le recomiendo que use la misma URL para solicitar un ladrido que usa para averiguar sobre el estado actual de ladrido del perro. No es esencial para REST, pero enfatiza el acto de modificar el horario.

El código de estado apropiado es probablemente 205 . Me estoy imaginando a un cliente que mira el horario actual, POSTa la misma URL para cambiarlo, y el servicio le indica que le dé un segundo vistazo al horario para probar que se ha cambiado.

Explicación

DESCANSO

Olvídate de HTTP por un momento. Es esencial comprender que un recurso es una función que toma tiempo como entrada y devuelve un conjunto que contiene identificadores y representaciones . Simplifiquemos eso para: un recurso es un conjunto R de identificadores y representaciones; R puede cambiar: los miembros se pueden agregar, eliminar o modificar. (A pesar de que es malo, diseño inestable a eliminar o modificar los identificadores.) Decimos un identificador que es un elemento de R identifica R , y que una representación que es un elemento de R representa R .

Digamos que R es un perro. Por casualidad identificas a R como /v1/dogs/1. (Es decir, /v1/dogs/1es un miembro del R .) Eso es sólo una de las muchas maneras en que podría identificar R . También puede identificar R como /v1/dogs/1/x-raysy como /v1/rufus.

¿Cómo representas a R ? Quizás con una fotografía. Tal vez con un conjunto de radiografías. O tal vez con una indicación de la fecha y hora en que R ladró por última vez. Pero recuerde que todas estas son representaciones del mismo recurso . /v1/dogs/1/x-rayses un identificador del mismo recurso que está representado por una respuesta a la pregunta "¿cuándo ladró R por última vez?"

HTTP

Las representaciones múltiples de un recurso no son muy útiles si no puede referirse al que desea. Es por eso que HTTP es útil: le permite conectar identificadores a representaciones . Es decir, es una forma de que el servicio reciba una URL y decida qué representación atender al cliente.

Al menos, eso es lo que GEThace. PUTes básicamente el inverso de GET: PUTuna representación r en la URL si desea que futuras GETsolicitudes a esa URL devuelvan r , con algunas posibles traducciones como JSON a HTML.

POSTes una forma más flexible de modificar una representación. Piense en que hay lógica de visualización y lógica de modificación que son equivalentes entre sí, ambas correspondientes a la misma URL. Una solicitud POST es una solicitud para que la lógica de modificación procese la información y modifique cualquier representación (no solo la representación ubicada en la misma URL) que el servicio considere conveniente. Presta atención al tercer párrafo después de 9.6 PUT : no estás reemplazando la cosa en la URL con contenido nuevo; le está pidiendo a la cosa en la URL que procese cierta información y responda inteligentemente en forma de representaciones informativas.

En nuestro caso, le pedimos a la lógica de modificación en /v1/dogs/1/bark-schedule(que es la contraparte de la lógica de visualización que nos dice cuándo ladró por última vez y cuándo ladrará a continuación) que procese nuestra información y modifique algunas representaciones en consecuencia. En respuesta a futuros GETs, la lógica de visualización correspondiente a la misma URL nos dirá que el perro ahora está ladrando como deseamos.

Piense en el trabajo cron como un detalle de implementación. Ofertas HTTP en visualización y modificación de representaciones. A partir de ahora, el servicio le dirá al cliente cuándo ladró el perro por última vez y cuándo ladrará a continuación. Desde la perspectiva del servicio, eso es honesto porque esos tiempos corresponden con trabajos cron pasados ​​y planificados.

Jordán
fuente
-1

REST es un estándar orientado a los recursos, no está impulsado por la acción como sería un RPC.

Si desea que su servidor ladre , debe buscar diferentes ideas como JSON-RPC o la comunicación websockets.

En mi opinión, cada intento de mantenerlo RESTful fallará: puede emitir un POSTcon el actionparámetro, no está creando ningún recurso nuevo, pero como puede tener efectos secundarios, está más seguro.

moonwave99
fuente
POSTfue diseñado para "proporcionar un bloque de datos ... a un proceso de manejo de datos" . Parece que mucha gente distingue los recursos de las acciones, pero en realidad las acciones son solo un tipo de recurso. Llamar a un recurso de acción en un servidor sigue siendo una interfaz uniforme, almacenable en caché, modular y escalable. También es apátrida, pero eso se puede violar si el cliente está diseñado para esperar una respuesta. Pero llamar a un "método vacío" en el servidor es lo que Roy Fielding pretendía con REST .
Jacob Stevens
Como describo en mi respuesta , en REST puede provocar implícitamente que el servidor realice una acción pidiéndole que diga, de ahora en adelante, "su acción se completó", mientras que RPC se basa en la idea de simplemente pedirle al servidor que realice la acción. Ambos tienen mucho sentido, así como la programación imperativa y declarativa tienen sentido.
Jordan