Chamar um método do lado do servidor em um recurso de maneira RESTful

142

Lembre-se de que tenho um entendimento rudimentar do REST. Digamos que eu tenho este URL:

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

E agora, quero fazer o servidor fazer o cachorro latir. Somente o servidor sabe como fazer isso. Digamos que eu queira executá-lo em um trabalho CRON que faça o cachorro latir a cada 10 minutos pelo resto da eternidade. Como é essa chamada? Eu meio que quero fazer isso:

Solicitação de URL:

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

No corpo da solicitação:

{"action":"bark"}

Antes de você ficar bravo comigo por criar meu próprio método HTTP, me ajude e me dê uma idéia melhor de como devo chamar um método do lado do servidor de uma maneira RESTful. :)

EDITAR PARA ESCLARECIMENTO

Mais alguns esclarecimentos sobre o que o método "casca" faz. Aqui estão algumas opções que podem resultar em chamadas de API de estrutura diferente:

  1. O latido apenas envia um email a dog.email e não registra nada.
  2. bark envia um email para dog.email e os incrementos dog.barkCount por 1.
  3. bark cria um novo registro "bark" com a gravação bark.timestamp quando a casca ocorreu. Também incrementa dog.barkCount em 1.
  4. O bark executa um comando do sistema para retirar a versão mais recente do código do cão do Github. Em seguida, ele envia uma mensagem de texto para o proprietário do cão, informando que o novo código do cão está em produção.
Kirk Ouimet
fonte
14
Curiosamente, adicionar uma recompensa parece ter atraído respostas piores do que você originalmente tinha ;-) Ao avaliar as respostas, lembre-se de que: 1) As especificações para os verbos HTTP impedem qualquer escolha que não seja POST. 2) O REST não tem nada a ver com a estrutura da URL - é uma lista genérica de restrições (sem estado, armazenável em cache, em camadas, interface uniforme etc.) do que conferir benefícios (escalabilidade, confiabilidade, visibilidade, etc.). 3) A prática atual (como usar o POST nas especificações de RPC) supera os definicionistas que estão criando suas próprias regras de API. 4) O REST requer uma interface uniforme (seguindo a especificação HTTP).
Raymond Hettinger
@ Kirk, o que você achou das novas respostas? Ainda há algo que você deseja saber, mas não foi abordado em nenhum deles? Ficaria feliz em editar minha resposta novamente, se puder ser mais útil.
Jordânia
@RaymondHettinger PATCHpode ser apropriado. Eu explico o porquê no final da minha resposta .
10133 Jordan
PATCH seria apropriado apenas para incrementar o dog.barkCount em um. POST é o método para enviar e-mail, criar um novo registro de latido, executar comandos para baixar do Github ou acionar uma mensagem de texto. @ Jordânia, sua leitura do RFC PATCH é imaginativa, mas um pouco diferente da intenção de ser uma variante do PUT para modificação parcial dos recursos. Eu não acho que você esteja ajudando o OP criando leituras não convencionais das especificações HTTP em vez de reconhecer a prática padrão de usar o POST para chamadas de procedimento remoto.
Raymond Hettinger
@RaymondHettinger cuja prática de fato padroniza o POST? Todas as interfaces RPC padrão que eu vi identificaram um recurso por entidade (não RESTful), versus URI, portanto, uma resposta válida priorizando a convenção RPC precisaria ser não convencional de qualquer maneira, o que acho que desmente o valor da RPC convencional: uma é imaginativa ou inconsistente . Você nunca pode dar errado com o POST, pois é o elemento principal para o processamento de dados, mas existem métodos mais específicos. REST significa nomear recursos e descrever alterações em seu estado, não nomear procedimentos de alteração de estado. PATCH e POST descrevem mudanças de estado.
Jordânia

Respostas:

280

Por que apontar para um design RESTful?

Os princípios RESTful trazem os recursos que tornam os sites fáceis (para um usuário humano aleatório "navegá-los") no design da API de serviços da web , para que eles sejam fáceis de usar por um programador. REST não é bom porque é REST, é bom porque é bom. E é bom principalmente porque é simples .

A simplicidade do HTTP simples (sem envelopes SOAP e POSTserviços sobrecarregados com URI único ), o que alguns podem chamar de "falta de recursos" , é na verdade sua maior força . Logo de cara, o HTTP pede que você tenha capacidade de endereçamento e ausência de estado : as duas decisões básicas de design que mantêm o HTTP escalável até os mega-sites de hoje (e mega-serviços).

Mas o REST não é o marcador de prata: às vezes um estilo RPC ("Chamada de Procedimento Remoto" - como SOAP) pode ser apropriado e, outras vezes, outras necessidades têm precedência sobre as virtudes da Web. Isto é bom. O que realmente não gostamos é de complexidade desnecessária . Muitas vezes, um programador ou uma empresa traz serviços no estilo RPC para um trabalho que o HTTP antigo simples poderia suportar. O efeito é que o HTTP é reduzido a um protocolo de transporte para uma enorme carga XML que explica o que "realmente" está acontecendo (não o URI ou o método HTTP dá uma pista sobre isso). O serviço resultante é muito complexo, impossível de depurar e não funcionará, a menos que seus clientes tenham a configuração exata que o desenvolvedor pretendia.

Mesma forma que um código Java / C # pode ser não orientada a objetos, usando apenas HTTP não faz um design RESTful. Pode-se ficar com a pressa de pensar em seus serviços em termos de ações e métodos remotos que devem ser chamados. Não é de admirar que isso acabe principalmente em um serviço no estilo RPC (ou em um híbrido REST-RPC). O primeiro passo é pensar de forma diferente. Um design RESTful pode ser alcançado de várias maneiras, uma maneira é pensar em seu aplicativo em termos de recursos, não ações:

💡 Em vez de pensar em termos de ações, ele pode executar ("faça uma busca por lugares no mapa") ...

... tente pensar em termos dos resultados dessas ações ("a lista de lugares no mapa que corresponde aos critérios de pesquisa").

Vou dar exemplos abaixo. (Outro aspecto importante do REST é o uso do HATEOAS - não o escovo aqui, mas falo sobre isso rapidamente em outro post .)


Edições do primeiro desenho

Vamos dar uma olhada no design proposto:

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

Primeiro, não devemos considerar a criação de um novo verbo HTTP ( ACTION). De um modo geral, isso é indesejável por vários motivos:

  • (1) Dado apenas o URI de serviço, como um programador "aleatório" saberá que o ACTIONverbo existe?
  • (2) se o programador sabe que existe, como ele saberá sua semântica? O que esse verbo significa?
  • (3) que propriedades (segurança, idempotência) devemos esperar que esse verbo tenha?
  • (4) e se o programador tiver um cliente muito simples que lide apenas com verbos HTTP padrão?
  • (5) ...

Agora vamos considerar o usoPOST (discutirei o porquê abaixo, basta aceitar minha palavra agora):

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

{"action":"bark"}

Isso pode ser bom ... mas somente se :

  • {"action":"bark"}era um documento; e
  • /v1/dogs/1/era um URI de "processador de documentos" (semelhante à fábrica). Um "processador de documentos" é um URI no qual você simplesmente "joga as coisas" e "esquece" delas - o processador pode redirecioná-lo para um recurso recém-criado após a "execução". Por exemplo, o URI para postar mensagens em um serviço de intermediário de mensagens, que após a postagem o redirecionaria para um URI que mostra o status do processamento da mensagem.

Não sei muito sobre o seu sistema, mas já aposto que os dois não são verdadeiros:

  • {"action":"bark"} não é um documento , na verdade é o método que você está tentando invadir o serviço; e
  • o /v1/dogs/1/URI representa um recurso "cão" (provavelmente o cão com ele id==1) e não um processador de documentos.

Então, tudo o que sabemos agora é que o design acima não é tão RESTful, mas o que é isso exatamente? O que há de tão ruim nisso? Basicamente, é ruim porque é um URI complexo com significados complexos. Você não pode deduzir nada disso. Como um programador saberia que um cachorro tem uma barkação que pode ser secretamente infundida com uma ação POSTnele?


Criando chamadas de API da sua pergunta

Então, vamos direto ao ponto e tentamos projetar esses latidos RESTfully pensando em termos de recursos . Permitam-me citar o livro Restful Web Services :

Uma POSTsolicitação é uma tentativa de criar um novo recurso a partir de um existente. O recurso existente pode ser o pai do novo em um sentido de estrutura de dados, da maneira como a raiz de uma árvore é o pai de todos os seus nós de folha. Ou o recurso existente pode ser um recurso especial de "fábrica" cujo único objetivo é gerar outros recursos. A representação enviada junto com uma POSTsolicitação descreve o estado inicial do novo recurso. Como no PUT, uma POSTsolicitação não precisa incluir nenhuma representação.

Seguindo a descrição acima, podemos ver que barkpode ser modelado como uma sub-fonte de adog ( uma vez que a barkestá contida em um cachorro, ou seja, uma casca é "latida" por um cachorro).

A partir desse raciocínio, já obtivemos:

  • O método é POST
  • O recurso é /barks, sub-fonte de dog:, /v1/dogs/1/barksrepresentando uma bark"fábrica". Esse URI é único para cada cão (já que está abaixo /v1/dogs/{id}).

Agora, cada caso da sua lista tem um comportamento específico.

1. o latido apenas envia um e-mail para dog.emaile não registra nada.

Em primeiro lugar, latir (enviar um email) é uma tarefa síncrona ou assíncrona? Em segundo lugar, a barksolicitação requer algum documento (o e-mail, talvez) ou está vazio?


1.1 casca envia um e-mail para dog.emaile não registra nada (como uma tarefa síncrona)

Este caso é simples. Uma chamada para o barksrecurso de fábrica produz uma casca (um e-mail enviado) imediatamente e a resposta (se OK ou não) é dada imediatamente:

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 ele registra (muda) nada, 200 OKé suficiente. Isso mostra que tudo correu como esperado.


1.2 casca envia um e-mail para dog.emaile não registra nada (como uma tarefa assíncrona)

Nesse caso, o cliente deve ter uma maneira de rastrear a barktarefa. A barktarefa deve ser um recurso com seu próprio 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

Dessa forma, cada um barké rastreável. O cliente pode então emitir um GETno barkURI para saber seu estado atual. Talvez até use a DELETEpara cancelar.


2. casca envia um e-mail para dog.emaile depois incrementa dog.barkCountem 1

Essa pode ser mais complicada, se você quiser que o cliente saiba que o dogrecurso foi alterado:

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

Nesse caso, a locationintenção do cabeçalho é informar ao cliente que ele deve dar uma olhada dog. Do HTTP RFC sobre303 :

Esse método existe principalmente para permitir que a saída de um POSTscript ativado redirecione o agente do usuário para um recurso selecionado.

Se a tarefa for assíncrona, barké necessário um sub-recurso, exatamente como a 1.2situação, e 303deve ser retornado GET .../barks/Yquando a tarefa estiver concluída.


3. casca cria um novo " bark" registro com a bark.timestampgravação quando a casca ocorreu. Também aumenta dog.barkCountem 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

Aqui, barké criado devido à solicitação, portanto o status 201 Createdé aplicado.

Se a criação for assíncrona, 202 Acceptedserá necessário um ( como o HTTP RFC diz ).

O registro de data e hora salvo faz parte do barkrecurso e pode ser recuperado com um GETa ele. O cão atualizado também pode ser "documentado" GET dogs/X/barks/Y.


4. bark executa um comando do sistema para retirar a versão mais recente do código do cão do Github. Em seguida, ele envia uma mensagem de texto dog.ownerinformando que o novo código de cão está em produção.

A redação deste é complicada, mas é praticamente uma tarefa assíncrona simples:

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

O cliente então emitia GETs para /v1/dogs/1/barks/a65h44saber o estado atual (se o código foi extraído, o e-mail foi enviado ao proprietário e assim por diante). Sempre que o cão muda, a 303é aplicável.


Empacotando

Citando Roy Fielding :

A única coisa que o REST exige dos métodos é que eles sejam definidos uniformemente para todos os recursos (ou seja, para que os intermediários não precisem conhecer o tipo de recurso para entender o significado da solicitação).

Nos exemplos acima, POSTé projetado uniformemente. Isso fará o cachorro " bark". Isso não é seguro (significando que a casca tem efeitos sobre os recursos), nem idempotente (cada solicitação produz uma nova bark), que se encaixa POSTbem no verbo.

Um programador saberia: POSTa barksrende a bark. Os códigos de status de resposta (também com o corpo da entidade e os cabeçalhos quando necessário) explicam o que mudou e como o cliente pode e deve proceder.

Nota: As principais fontes usadas foram: livro " Restful Web Services ", o HTTP RFC e o blog de Roy Fielding .




Editar:

A pergunta e, portanto, a resposta mudaram bastante desde que foram criadas. A pergunta original perguntou sobre o design de um URI como:

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

Abaixo está a explicação de por que não é uma boa escolha:

Como os clientes dizem ao servidor O QUE FAZER com os dados são as informações do método .

  • Os serviços da web RESTful transmitem informações sobre o método no método HTTP.
  • Os serviços típicos de estilo RPC e SOAP mantêm os deles no corpo da entidade e no cabeçalho HTTP.

Em que parte dos dados [o cliente deseja que o servidor] funcione estão as informações de escopo .

  • Serviços RESTful usam o URI. Os serviços no estilo SOAP / RPC usam mais uma vez o corpo da entidade e os cabeçalhos HTTP.

Como exemplo, use o URI do Google http://www.google.com/search?q=DOG. Lá, as informações do método são GETe as informações do escopo são /search?q=DOG.

Longa história curta:

  • Nas arquiteturas RESTful , as informações do método entram no método HTTP.
  • Em Arquiteturas Orientadas a Recursos , as informações do escopo são inseridas no URI.

E a regra de ouro:

Se o método HTTP não corresponder às informações do método, o serviço não é RESTful. Se as informações do escopo não estiverem no URI, o serviço não será orientado a recursos.

Você pode colocar a ação "latir" na URL (ou no corpo da entidade) e usar . Não tem problema, ele funciona e pode ser a maneira mais simples de fazer isso, mas isso não é RESTful .POST

Para manter seu serviço realmente RESTful, pode ser necessário dar um passo atrás e pensar no que você realmente deseja fazer aqui (que efeitos isso terá sobre os recursos).

Não posso falar sobre suas necessidades comerciais específicas, mas deixe-me dar um exemplo: considere um serviço de pedidos RESTful em que os pedidos são em URIs example.com/order/123.

Agora diga que queremos cancelar um pedido, como vamos fazer isso? Pode-se sentir tentado a pensar que é uma "ação" de "cancelamento " e designá-la como POST example.com/order/123?do=cancel.

Isso não é RESTful, como falamos acima. Em vez disso, poderíamos PUTuma nova representação do ordercom um canceledelemento enviado para true:

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

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

E é isso. Se o pedido não puder ser cancelado, um código de status específico poderá ser retornado. (Um design de sub-fonte, como POST /order/123/canceledo corpo da entidade true, também pode estar disponível por simplicidade.)

No seu cenário específico, você pode tentar algo semelhante. Dessa forma, enquanto um cachorro está latindo, por exemplo, um GETat /v1/dogs/1/pode incluir essa informação (por exemplo <barking>true</barking>) . Ou ... se isso for muito complicado, afrouxe seu requisito RESTful e atenha-se POST.

Atualizar:

Não quero tornar a resposta muito grande, mas demora um pouco para expor um algoritmo (uma ação ) como um conjunto de recursos. Em vez de pensar em termos de ações ( "faça uma pesquisa por locais no mapa" ), é preciso pensar em termos dos resultados dessa ação ( "a lista de locais no mapa que corresponde aos critérios de pesquisa" ).

Você pode voltar a esta etapa se achar que seu design não se encaixa na interface uniforme do HTTP.

As variáveis ​​de consulta são informações de escopo , mas não denotam novos recursos ( /post?lang=ené claramente o mesmo recurso que /post?lang=jp, apenas uma representação diferente). Em vez disso, eles são usados ​​para transmitir o estado do cliente (como ?page=10, para que o estado não seja mantido no servidor; ?lang=entambém é um exemplo aqui) ou parâmetros de entrada para recursos algorítmicos ( /search?q=dogs, /dogs?code=1). Mais uma vez, não recursos distintos.

Propriedades dos métodos dos verbos HTTP:

Outro ponto claro que mostra ?action=somethingno URI não é RESTful, são as propriedades dos verbos HTTP:

  • GETe HEADsão seguros (e idempotentes);
  • PUTe DELETEsão apenas idempotentes;
  • POST não é nenhum.

Segurança : Um GETou HEADpedido é um pedido para ler alguns dados, não é um pedido para alterar qualquer estado do servidor. O cliente pode fazer GETou HEADsolicitar 10 vezes e é o mesmo que fazê-lo uma vez ou nunca fazê-lo .

Idempotência : uma operação idempotente em uma que tenha o mesmo efeito, seja aplicada uma ou mais de uma vez (em matemática, multiplicar por zero é idempotente). Se você DELETEusar um recurso uma vez, a exclusão novamente terá o mesmo efeito (o recurso GONEjá está ).

POSTnão é seguro nem idempotente. Fazer duas POSTsolicitações idênticas a um recurso 'factory' provavelmente resultará em dois recursos subordinados contendo as mesmas informações. Com sobrecarregado (método no URI ou no corpo da entidade) POST, todas as apostas estão desativadas.

Ambas as propriedades foram importantes para o sucesso do protocolo HTTP (em redes não confiáveis!): Quantas vezes você atualizou ( GET) a página sem esperar até que ela esteja totalmente carregada?

Criar uma ação e colocá-la na URL quebra claramente o contrato dos métodos HTTP. Mais uma vez, a tecnologia permite, você pode fazê-lo, mas esse não é o design RESTful.

acdcjunior
fonte
Aceito a ideia de que chamar uma ação em um servidor, designado como uma ação na URL, não é RESTful. POSTfoi projetado para "fornecer um bloco de dados ... para um processo de manipulação de dados" . Parece que muitas pessoas distinguem recursos de ações, mas realmente ações são apenas um tipo de recurso.
Jacob Stevens
1
@JacobStevens O OP mudou um pouco a pergunta, então preciso atualizar minha resposta para torná-la mais direta (verifique a pergunta original , talvez você entenda o que quero dizer). Concordo em POST"fornecer um bloco de dados ... a um processo de manipulação de dados", mas a diferença é realmente que, um bloco de dados , não um bloco de dados e o procedimento (ação, método, comando) a ser executado então. Isso está POSTsobrecarregando, e a POSTsobrecarga é do estilo RPC, não RESTful.
Acdcjunior
Estou presumindo que a lógica de ação / método esteja alojada no servidor, caso contrário, qual seria o objetivo da chamada? No caso em que você descreve, eu concordo, isso não seria um bom design. Mas o método ou sub-rotina que executa a ação seria especificado pelo URI (que é outro motivo pelo qual um recurso de ação designado como verbo no final de uma URL é útil e RESTful, apesar de muitos desaconselharem).
Jacob Stevens
6
A resposta nos atualizado. É um pouco longo, porque parecia necessária uma explicação completa ("Lembre-se de que tenho uma compreensão rudimentar do REST"). Foi meio que uma luta deixar tudo o mais claro possível. Espero que seja útil de alguma forma.
acdcjunior
2
Ótima explicação, votei, mas o cabeçalho do local não deve ser usado na resposta 202 Aceita. Parece ser uma interpretação errada que muitas pessoas fazem da RFC. Verifique este stackoverflow.com/questions/26199228/…
Delmo
6

I respondeu anteriormente , mas esta resposta contradiz a minha resposta velho e segue uma estratégia muito diferente para chegar a uma solução. Ele mostra como a solicitação HTTP é criada a partir dos conceitos que definem REST e HTTP. Ele também usa em PATCHvez de POSTou PUT.

Ele percorre as restrições REST, os componentes do HTTP e, em seguida, uma solução possível.

DESCANSAR

O REST é um conjunto de restrições que devem ser aplicadas a um sistema hipermídia distribuído para torná-lo escalável. Mesmo para entendê-lo no contexto de controlar remotamente uma ação, é necessário pensar em controlar remotamente uma ação como parte de um sistema hipermídia distribuído - parte de um sistema para descobrir, visualizar e modificar informações interconectadas. Se isso é mais problemático do que vale, provavelmente não é bom tentar torná-lo RESTful. Se você deseja apenas uma GUI do tipo "painel de controle" no cliente que possa acionar ações no servidor via porta 80, provavelmente desejará uma interface RPC simples como JSON-RPC através de solicitações / respostas HTTP ou um WebSocket.

Mas o REST é uma maneira fascinante de pensar, e o exemplo da pergunta é fácil de modelar com uma interface RESTful, então vamos assumir o desafio de diversão e educação.

O REST é definido por quatro restrições de interface:

identificação de recursos; manipulação de recursos através de representações; mensagens auto-descritivas; e hipermídia como o mecanismo do estado do aplicativo.

Você pergunta como pode definir uma interface, atendendo a essas restrições, através da qual um computador diz a outro para fazer um cachorro latir. Especificamente, você deseja que sua interface seja HTTP e não deseja descartar os recursos que tornam o HTTP RESTful quando usado como pretendido.

Vamos começar com a primeira restrição: identificação de recursos .

Qualquer informação que possa ser nomeada pode ser um recurso: um documento ou imagem, um serviço temporal (por exemplo, "o clima de hoje em Los Angeles"), uma coleção de outros recursos, um objeto não virtual (por exemplo, uma pessoa) etc. .

Então um cachorro é um recurso. Ele precisa ser identificado.

Mais precisamente, um recurso R é uma função de associação temporariamente variável M R ( t ), que por tempo t mapeia para um conjunto de entidades ou valores que são equivalentes. Os valores no conjunto podem ser representações de recursos e / ou identificadores de recursos .

Você modela um cão usando um conjunto de identificadores e representações e dizendo que todos estão associados um ao outro em um determinado momento. Por enquanto, vamos usar o identificador "dog # 1". Isso nos leva à segunda e terceira restrições: representação de recursos e auto-descrição .

Os componentes REST executam ações em um recurso usando uma representação para capturar o estado atual ou pretendido desse recurso e transferindo essa representação entre componentes. Uma representação é uma sequência de bytes, além de metadados de representação para descrever esses bytes.

A seguir, é apresentada uma sequência de bytes que captura o estado pretendido do cão, ou seja, a representação que desejamos associar ao identificador "cão nº 1" (observe que ele representa apenas parte do estado, pois não considera o nome, a saúde do cão). ou até latidos passados):

Ele está latindo a cada 10 minutos desde o momento em que essa alteração de estado foi efetuada e continuará indefinidamente.

Ele deve estar anexado aos metadados que o descrevem. Esses metadados podem ser úteis:

É uma declaração em inglês. Descreve parte do estado pretendido. Se for recebido várias vezes, permita apenas que o primeiro tenha efeito.

Por fim, vejamos a quarta restrição: HATEOAS .

REST ... vê um aplicativo como uma estrutura coesa de informações e alternativas de controle através das quais um usuário pode executar uma tarefa desejada. Por exemplo, procurar uma palavra em um dicionário on-line é um aplicativo, como fazer uma excursão por um museu virtual ou revisar um conjunto de notas de aula para estudar para um exame. ... O próximo estado de controle de um aplicativo reside na representação do primeiro recurso solicitado, portanto, obter essa primeira representação é uma prioridade. ... O aplicativo de modelo é, portanto, um mecanismo que se move de um estado para o outro, examinando e escolhendo entre as transições de estado alternativas no conjunto atual de representações.

Em uma interface RESTful, o cliente recebe uma representação de recurso para descobrir como deve receber ou enviar uma representação. Deve haver uma representação em algum lugar do aplicativo a partir da qual o cliente possa descobrir como receber ou enviar todas as representações que deve poder receber ou enviar, mesmo se seguir uma cadeia de representações para chegar a essas informações. Isso parece bastante simples:

O cliente solicita uma representação de um recurso identificado como a página inicial; em resposta, obtém uma representação que contém um identificador de todos os cães que o cliente pode querer. O cliente extrai um identificador e pergunta ao serviço como ele pode interagir com o cão identificado, e o serviço diz que o cliente pode enviar uma declaração em inglês descrevendo parte do estado pretendido do cão. Em seguida, o cliente envia essa declaração e recebe uma mensagem de sucesso ou uma mensagem de erro.

HTTP

O HTTP implementa restrições REST da seguinte maneira:

identificação de recurso : URI

representação de recursos : entidade-corpo

auto-descrição : método ou código de status, cabeçalhos e possivelmente partes do corpo da entidade (por exemplo, o URI de um esquema XML)

HATEOAS : hiperlinks

Você decidiu http://api.animals.com/v1/dogs/1como o URI. Vamos supor que o cliente tenha obtido isso de alguma página do site.

Vamos usar esse corpo de entidade (o valor de nexté um carimbo de data / hora; um valor de 0significa 'quando essa solicitação é recebida'):

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

Agora precisamos de um método. PATCH se encaixa na descrição "parte do estado pretendido" que decidimos:

O método PATCH solicita que um conjunto de alterações descrito na entidade solicitada seja aplicado ao recurso identificado pelo Request-URI.

E alguns cabeçalhos:

Para indicar o idioma do corpo da entidade: Content-Type: application/json

Para garantir que isso aconteça apenas uma vez: If-Unmodified-Since: <date/time this was first sent>

E nós temos um pedido:

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}}

Em caso de sucesso, o cliente deve receber um 204código de status em resposta ou a 205se a representação de /v1/dogs/1/tiver sido alterada para refletir o novo cronograma de latidos.

Em caso de falha, deve receber uma 403e uma mensagem útil do motivo.

Não é essencial para o REST que o serviço reflita o agendamento da casca em uma representação em resposta a GET /v1/dogs/1/, mas faria mais sentido se uma representação JSON incluísse isso:

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

Trate o trabalho cron como um detalhe de implementação que o servidor oculta da interface. Essa é a beleza de uma interface genérica. O cliente não precisa saber o que o servidor faz nos bastidores; tudo o que importa é que o serviço entenda e responda às mudanças de estado solicitadas.

Jordânia
fonte
3

A maioria das pessoas usa o POST para esse fim. É apropriado para executar "qualquer operação insegura ou não-potencial, quando nenhum outro método HTTP parecer apropriado".

APIs como XMLRPC usam POST para acionar ações que podem executar código arbitrário. A "ação" está incluída nos dados do 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>

O RPC é um exemplo para mostrar que o POST é a escolha convencional de verbos HTTP para métodos do lado do servidor. Aqui estão os pensamentos de Roy Fielding sobre o POST - ele praticamente diz que é RESTful usar os métodos HTTP conforme especificado.

Observe que o próprio RPC não é muito RESTful porque não é orientado a recursos. Mas se você precisar de apatridia, armazenamento em cache ou camadas, não será difícil fazer as transformações apropriadas. Veja http://blog.perfectapi.com/2012/opinionated-rpc-apis-vs-restful-apis/ para um exemplo.

Raymond Hettinger
fonte
Eu acho que você seria UrlEncode os params não colocá-lo na cadeia de consulta
tacos_tacos_tacos
@Kirk Sim, mas com uma pequena modificação, solte a barra final final: POST api.animals.com/v1/dogs1?action=bark
Raymond Hettinger
se você seguir os conselhos desta resposta, lembre-se de que a API resultante não será RESTful.
Nicholas Shanks
2
Isso não é RESTful porque o HTTP estabelece a URL como identificador de um recurso e uma URL /RPC2não faz nada para identificar um recurso - ele identifica uma tecnologia de servidor. Em vez disso, isso serve methodNamepara tentar 'identificar' o 'recurso' - mas mesmo assim, ele não se beneficia da distinção substantivo / verbo; a única coisa parecida com "verbo" aqui é methodCall. É como 'fazer recuperação do nome do estado' em vez de 'recuperar o nome do estado' - o último faz muito mais sentido.
Jordan
+1 para os links; muito informativo e o experimento "RPC opinativo" é inventivo.
Jordan
2

POSTé o método HTTP projetado para

Fornecendo um bloco de dados ... para um processo de manipulação de dados

Métodos do lado do servidor que lidam com ações não mapeadas por CRUD é o que Roy Fielding pretendia com o REST, então você é bom lá, e é por isso que POSTfoi feito para não ser idempotente. POSTmanipulará a maioria dos lançamentos de dados nos métodos do servidor para processar as informações.

Dito isso, em seu cenário de latidos de cães, se você deseja que uma latida no servidor seja realizada a cada 10 minutos, mas por algum motivo precisa que o gatilho se origine de um cliente, PUTserviria melhor ao objetivo, devido à sua idempotência. Bem, estritamente nesse cenário, não há risco aparente de várias solicitações POST, fazendo com que seu cão mie, mas de qualquer maneira esse é o objetivo dos dois métodos semelhantes. Minha resposta a uma pergunta SO semelhante pode ser útil para você.

Jacob Stevens
fonte
1
PUT vs. POST tem tudo a ver com o URL. O terceiro parágrafo após o 9.6 PUT diz que o objetivo dos dois métodos é que a PUTURL se refira ao que deve ser substituído pelo conteúdo do cliente e a POSTURL se refira ao que deve processar o conteúdo do cliente da maneira que desejar.
Jordânia
1

Se assumirmos que o latido é um recurso interno / dependente / secundário no qual o consumidor pode atuar, poderíamos dizer:

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

latas de cão número 1

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

retorna o último carimbo de data e hora da casca

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

não se aplica! então ignore.

bolbol
fonte
Isso é apenas RESTful se você considerar /v1/dogs/1/barkum recurso em si e POSTuma descrição de como o estado interno desse recurso deve mudar. Acho que faz mais sentido considerar apenas /v1/dogs/1/como um recurso e indicar no corpo da entidade que deve latir.
Jordan
mmm .. bem, é um recurso que você pode alterar seu estado. Como o resultado da mudança de estado está causando ruído, não o torna menos recurso! Você está vendo Bark como um verbo (que é) e é por isso que você não pode considerá-lo um recurso. Estou olhando para ele como um recurso dependente, cujo estado pode ser alterado e, como é booleano, não vejo motivo para mencioná-lo no corpo da entidade. Essa é apenas a minha opinião.
bolbol
1

Revisões anteriores de algumas respostas sugeriram o uso do RPC. Você não precisa procurar o RPC, pois é perfeitamente possível fazer o que deseja enquanto segue as restrições REST.

Em primeiro lugar, não coloque parâmetros de ação no URL. O URL define para o qual você está aplicando a ação e os parâmetros de consulta fazem parte do URL. Deve ser pensado inteiramente como um substantivo. http://api.animals.com/v1/dogs/1/?action=barké um recurso diferente - um substantivo diferente - para http://api.animals.com/v1/dogs/1/. [nb Asker removeu o ?action=barkURI da pergunta.] Por exemplo, compare http://api.animals.com/v1/dogs/?id=1com http://api.animals.com/v1/dogs/?id=2. Recursos diferentes, diferenciados apenas pela sequência de consultas. Portanto, a ação da sua solicitação, a menos que corresponda diretamente a um tipo de método existente sem corpo (TRACE, OPTIONS, HEAD, GET, DELETE etc.) deve ser definida no corpo da solicitação.

Em seguida, decida se a ação é " idempotente ", o que significa que pode ser repetida sem efeitos adversos (consulte o próximo parágrafo para mais explicações). Por exemplo, definir um valor como true pode ser repetido se o cliente não tiver certeza de que o efeito desejado aconteceu. Eles enviam a solicitação novamente e o valor permanece verdadeiro. Adicionar 1 a um número não é idempotente. Se o cliente envia o comando Add1, não tem certeza de que funcionou e o envia novamente, o servidor adicionou um ou dois? Depois de determinar isso, você estará em uma posição melhor para escolher entre PUTe POSTpara o seu método.

Idempotente significa que uma solicitação pode ser repetida sem alterar o resultado. Esses efeitos não incluem o log e outras atividades administrativas do servidor. Usando seus primeiro e segundo exemplos, o envio de dois e-mails para a mesma pessoa resulta em um estado diferente do envio de um e-mail (o destinatário tem dois na caixa de entrada, que podem ser considerados spam), então eu definitivamente usaria o POST para isso. . Se o barkCount no exemplo 2 tiver a intenção de ser visto por um usuário da sua API ou afetar algo visível ao cliente, também será algo que tornaria a solicitação não idempotente. Se for apenas para ser visualizado por você, ele conta como log do servidor e deve ser ignorado ao determinar a idempotência.

Por fim, determine se a ação que você deseja executar pode ter sucesso imediato ou não. O BarkDog é uma ação de conclusão rápida. RunMarathon não é. Se sua ação for lenta, considere retornar a 202 Accepted, com um URL no corpo da resposta para que um usuário faça uma pesquisa para ver se a ação está concluída. Como alternativa, peça aos usuários POST para um URL da lista /marathons-in-progress/e, quando a ação for concluída, redirecione-os do URL do ID em andamento para o /marathons-complete/URL.
Para os casos específicos # 1 e # 2, o servidor hospedaria uma fila e o cliente publicaria lotes de endereços nela. A ação não seria SendEmails, mas algo como AddToDispatchQueue. O servidor pode pesquisar a fila para verificar se há algum endereço de email aguardando e enviar emails, se houver. Em seguida, atualiza a fila para indicar que a ação pendente foi executada. Você teria outro URI mostrando ao cliente o estado atual da fila. Para evitar o envio duplo de e-mails, o servidor também pode manter um registro de quem enviou esse e-mail e verificar cada endereço para garantir que ele nunca envie dois para o mesmo endereço, mesmo se você POSTAR a mesma lista duas vezes para a fila.

Ao escolher um URI para qualquer coisa, tente pensar nele como resultado, não como uma ação. Por exemplo, google.com/search?q=dogsmostra os resultados de uma pesquisa para a palavra "cães". Não é necessário executar a pesquisa.

Os casos 3 e 4 da sua lista também não são ações idempotentes. Você sugere que os diferentes efeitos sugeridos possam afetar o design da API. Nos quatro casos, eu usaria a mesma API, pois os quatro mudam o "estado mundial".

Nicholas Shanks
fonte
Digamos que a ação seja percorrer uma fila gigante de e-mails e enviar uma mensagem para várias pessoas. Isso é idempotente? São ações idempotentes para PUT ou POST?
Kirk Ouimet
@kirk Eu expandi minha resposta.
Nicholas Shanks
0

Veja minha nova resposta - ela contradiz essa e explica REST e HTTP com mais clareza e precisão.

Aqui está uma recomendação que é RESTful, mas certamente não é a única opção. Para começar a latir quando o serviço receber a solicitação:

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

token é um número arbitrário que evita latidos redundantes, não importa quantas vezes essa solicitação seja enviada.

nextindica a hora da próxima casca; um valor de 0significa 'ASAP'.

Sempre que você GET /v1/dogs/1/bark-schedule, você deve obter algo parecido com isto, onde t é o tempo da última casca e u é t + 10 minutos:

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

Eu recomendo que você use o mesmo URL para solicitar um latido que você usa para descobrir o estado atual do latido do cão. Não é essencial para o REST, mas enfatiza o ato de modificar o agendamento.

O código de status apropriado é provavelmente 205 . Estou imaginando um cliente que analisa o agendamento atual, POSTs para o mesmo URL para alterá-lo e é instruído pelo serviço a dar uma segunda olhada no agendamento para provar que foi alterado.

Explicação

DESCANSAR

Esqueça o HTTP por um momento. É essencial entender que um recurso é uma função que leva tempo como entrada e retorna um conjunto contendo identificadores e representações . Vamos simplificar isso para: um recurso é um conjunto R de identificadores e representações; R pode mudar - os membros podem ser adicionados, removidos ou modificados. (Apesar de ser mau, design instável para remover ou modificar identificadores.) Eu digo um identificador que é um elemento de R identifica R , e que uma representação que é um elemento de R representa R .

Digamos que R é um cachorro. Você identificou R como /v1/dogs/1. (Significado /v1/dogs/1é membro de R .) Essa é apenas uma das muitas maneiras que você poderia identificar R . Você também pode identificar R como /v1/dogs/1/x-rayse como /v1/rufus.

Como você representa R ? Talvez com uma fotografia. Talvez com um conjunto de raios-X. Ou talvez com uma indicação da data e hora em que R latiu pela última vez. Mas lembre-se de que essas são todas representações do mesmo recurso . /v1/dogs/1/x-raysé um identificador do mesmo recurso que é representado por uma resposta à pergunta "quando R latiu pela última vez?"

HTTP

Várias representações de um recurso não serão muito úteis se você não puder se referir à que deseja. É por isso que o HTTP é útil: permite conectar identificadores a representações . Ou seja, é uma maneira do serviço receber uma URL e decidir qual representação servir ao cliente.

Pelo menos, é o que GETfaz. PUTé basicamente o inverso de GET: você é PUTuma representação r na URL se desejar que GETsolicitações futuras para essa URL retornem r , com algumas traduções possíveis, como JSON para HTML.

POSTé uma maneira mais flexível de modificar uma representação. Pense em haver lógica de exibição e lógica de modificação que são equivalentes entre si - ambas correspondendo à mesma URL. Uma solicitação POST é uma solicitação para a lógica de modificação processar as informações e modificar quaisquer representações (não apenas a representação localizada pela mesma URL) que o serviço achar adequado. Preste atenção ao terceiro parágrafo após 9.6 PUT : você não está substituindo a coisa no URL por um novo conteúdo; você está solicitando que o URL processe algumas informações e responda de forma inteligente na forma de representações informativas.

No nosso caso, solicitamos que a lógica de modificação em /v1/dogs/1/bark-schedule(que é a contrapartida da lógica de exibição que nos diz quando latiu pela última vez e quando latirá em seguida) processe nossas informações e modifique algumas representações de acordo. Em resposta a GETs futuros , a lógica de exibição correspondente à mesma URL nos dirá que o cachorro está latindo agora como desejamos.

Pense no trabalho cron como um detalhe de implementação. O HTTP trata da visualização e modificação de representações. A partir de agora, o serviço dirá ao cliente quando o cão latiu pela última vez e quando ele latirá a seguir. Do ponto de vista do serviço, isso é honesto, porque esses horários correspondem a tarefas cron planejadas e passadas.

Jordânia
fonte
-1

REST é um padrão orientado a recursos, não é acionado por ação como seria um RPC.

Se você deseja que seu servidor descasque , procure idéias diferentes, como JSON-RPC ou comunicação por Websockets.

Toda tentativa de mantê-lo RESTful falhará na minha opinião: você pode emitir um POSTcom o actionparâmetro, você não está criando novos recursos, mas como pode ter efeitos colaterais, fica mais seguro.

moonwave99
fonte
POSTfoi projetado para "fornecer um bloco de dados ... para um processo de manipulação de dados" . Parece que muitas pessoas distinguem recursos de ações, mas realmente ações são apenas um tipo de recurso. Chamar um recurso de ação em um servidor ainda é uma interface uniforme, armazenável em cache, modular e escalável. Também é sem estado, mas pode ser violado se o cliente for projetado para esperar uma resposta. Mas chamar um "método nulo" no servidor é o que Roy Fielding pretendia com o REST .
Jacob Stevens
Como descrevo em minha resposta , no REST, você pode implicitamente fazer com que o servidor execute uma ação pedindo que diga, a partir de agora, "sua ação foi concluída", enquanto o RPC se baseia na idéia de apenas solicitar ao servidor que execute a acção. Ambos fazem todo o sentido, assim como a programação imperativa e declarativa.
Jordan