Transferência de estado representacional – REST – via HTTP
Olá pessoal,
Outro dia falei sobre estado e qual a diferença entre ser e estar. Quando falamos sobre REST ou transferência de estado representacional via HTTP, estamos falando sobre a utilização do protocolo HTTP para transferir estado.
Sobre estado, entendam que não me importa se a garota é linda, me importa apenas se ela está bonita ou se está feia agora por estar desarrumada e como fazer para transferir essa imagem utilizando o protocolo HTTP. Percebam também o destaque ao via HTTP, pois REST não se trata exclusivamente sobre o protocolo HTTP, apenas vou utilizar, nesse artigo, exclusivamente o protocolo HTTP e, para isso, vamos abordar os métodos de requisição HTTP, campos de cabeçalho HTTP e seus significados.
Por ser um tema muito técnico e, de certa forma, complexo, o conteúdo foi separado em 4 artigos:
- Esse artigo, que fala sobre a especificação do protocolo HTTP e como podemos ter significados diferentes para requisições para uma mesma URI, apenas variando o método de requisição ou campos de cabeçalho HTTP
- O segundo artigo, que falará sobre a criação de um servidor que compreende esse protocolo e sabe diferenciar as requisições feitas para entregar o conteúdo adequado ao solicitado pelo client.
- O terceiro artigo, que falará sobre a criação de um client que compreende esse protocolo e sabe como especificar a requisição para obter a resposta adequada ao seus objetivos.
- O último artigo, que falará sobre as consequências, problemas e vantagens em se ter uma API, abordará algumas APIs populares como Facebook, Twitter, Google, etc.
Métodos de requisições segundo o protocolo HTTP/1.1
Toda a base do protocolo HTTP se dá através de requisições e respostas: Clients fazem requisições para um servidor que, por sua vez, responde apropriadamente à essa requisição. Essas requisições são feitas para recursos e podem ser feitas utilizando alguns métodos de requisição diferentes:
Métodos seguros:
Os métodos seguros são aqueles que não modificam, gravam ou deletam informações de mecanismos de armazenamento e persistência, ou seja, o client que fazer uma consulta. São chamados de métodos seguros pois, caso alguma coisa dê errada, o usuário não é responsável por isso. Por definição, GET e HEAD são métodos seguros, que os usuários utilizam para fazer requisições com o objetivo de se obter alguma informação.
Métodos idempotentes:
Os métodos idempotentes são aqueles, como o próprio nome deixa claro, cujo resultado de N>0 requisições é igual ao resultado de uma única requisição (a não ser em caso de erros ou expiração de sessão). Todos àqueles métodos que não podem ter, segundo a especificação do protocolo HTTP, “efeitos colaterais” na outra ponta (ex: excluir ou modificar um documento), são chamados, por definição, de métodos idempotentes, como OPTIONS e TRACE. Os métodos GET, HEAD, PUT e DELETE também compartilham essa característica, mas é possível que uma sequência de requisições GET (ou HEAD, PUT e DELETE) sejam não idempotentes, caso a requisição atual dependa de algum valor que foi modificado na requisição anterior.
Métodos e seus significados
-
- OPTIONS
- Significa requisição por informação sobre as opções disponíveis para comunicação com um servidor.
- Com esse método os clients conseguem determinar as opções ou requisitos relacionados com determinado recurso, sem recuperá-lo ou executar uma ação sobre tal recurso.
- As respostas de uma requisição HTTP usando o método OPTIONS não podem ser armazenadas em cache.
-
- GET
- Significa obter uma informação que está em determinada URI, na forma de uma entidade.
- As respostas de uma requisição HTTP usando o método GET podem ser armazenadas em cache.
-
- HEAD
- Mesmo significado que o método GET, porém sem obter o corpo da requisição.
- Com esse método os clients conseguem obter meta informações sobre determinado recurso, sem ter que transferir todo o recurso.
- Os cabeçalhos de uma requisição HTTP usando o método HEAD para determinada URI devem ser idênticos a resposta feita à mesma URI utilizando o método GET.
- As respostas de uma requisição HTTP usando o método HEAD podem ser armazenadas em cache.
-
- POST
- Significa que uma entidade enviada para o servidor pode ser utilizada para adicionar ou atualizar um recurso naquela URI, por exemplo:
- Adicionar um comentário em um artigo, fórum, etc.
- Adicionar anotações em determinado recurso.
- Enviar um conjunto de dados para serem processados, como criação de uma transação, etc.
- Adicionar novas informações em um sistema de armazenamento e persistência.
- As respostas de uma requisição HTTP usando o método POST não podem ser armazenadas em cache a não ser que a resposta dessa requisição inclua um cabeçalho Cache-Control ou Expires.
- Caso uma requisição HTTP usando o método POST tenha criado um recurso que não pode ser obtido por uma requisição HTTP, então essa requisição deve retornar código de status HTTP 200 (Ok) ou 204 (No Content).
- Caso uma requisição HTTP usando o método POST tenha criado um novo recurso, um código de status HTTP 201 (Created) deve ser retornado em conjunto com uma entidade que represente o status da requisição e refira ao recurso.
-
- PUT
- Significa que a entidade enviada na requisição DEVE ser tratada como um recurso à ser armazenado. Caso a requisição seja feita à uma URI que já contém um recurso, a entidade enviada na requisição DEVE ser tratada como uma versão mais recente do recurso à ser atualizada.
- Caso a requisição não aponte para um recurso existente e o servidor for capaz de atender a requisição criando um novo recurso, então o servidor DEVE informar a criação retornando um código de status HTTP 201 (Created).
- Caso a requisição aponte para um recurso existente e o servidor for capaz de atender a requisição modificando o recurso, então o servidor DEVE retornar um código de status HTTP 200 (Ok) ou 204 (No Content) para indicar o sucesso da requisição.
- Caso o servidor não possa atender a requisição, seja criando ou modificando um recurso, o servidor DEVE retornar uma mensagem de erro que descreva o motivo do problema.
- O servidor deve compreender que o client sabe exatamente o objetivo daquela URI e NÃO DEVE, sob hipótese alguma, aplicar a requisição à qualquer outro recurso.
- A principal diferença entre os métodos POST e PUT está na URI. Em um método POST, o client sabe que a URI identifica um recurso que irá manipular a entidade enviada na requisição, enquanto a URI em uma requisição PUT identifica a entidade que está sendo enviada com a requisição.
-
- DELETE
- Significa que o servidor deve apagar o recurso identificado pela URI requisitada.
- Em uma requisição HTTP DELETE, o servidor pode marcar um recurso para deleção, retardando a deleção efetiva e delegando sua real deleção para ser feita por uma pessoa, ou seja, o client não tem garantia de que sua requisição será realmente executada, mesmo que o código de status HTTP dessa requisição indique sucesso.
- O código de status da resposta dessa requisição PODE ser 200 (Ok) se a resposta incluir uma entidade que descreva a resposta, ou 202 (Accepted) caso a requisição não tenha sido executada (ex: foi delegada à uma pessoa) ou, ainda, 204 (No Content) caso ela tenha sido executada, mas não tenha uma entidade no corpo da resposta que a descreva.
URI e semântica
URI significa Uniform Resource Identifier, ou seja, é um identificador de recurso utilizado pelos clients para se relacionar com determinados recursos e é a base de todo o “estilo REST” via HTTP. Quando clients REST fazem uma requisição para um URI, significa que o client tem um objetivo específico com essa requisição e esse objetivo pode ser identificado pela URL (localização do recurso) e o método de requisição.
Se, em um sistema de armazenamento, temos dados sobre continentes, países, divisões políticas (como estados e unidades federativas), cidades, bairros, etc., podemos ter URIs que identificam cada um desses recursos. Como a definição de REST estabelece que o servidor não deve depender do contexto do client (o client é stateless), então as requisições devem ser de tal forma, que o servidor possa entender o que o client quer e possa respondê-lo adequadamente. No caso do nosso sistema de armazenamento que contém os dados sobre continentes, países, etc., podemos ter a seguinte estrutura:
- http://example.org/south-america/countries.json
-
Essa URI identifica um recurso que pode representar todos os países da América do Sul. Os clients podem fazer requisições à essa URI usando métodos diferentes, cada método pode significar uma coisa completamente diferente, por exemplo:
-
- GET
- O client deseja obter a lista de países da América do Sul.
-
- POST
- O client está enviando uma entidade para ser processada, talvez ele queira modificar alguma informação de algum país da América do Sul.
-
- PUT
- Nesse caso, o client deseja modificar a própria lista de países da América do Sul(a grande diferença entre POST e PUT). Vejam que eu não disse um país da lista, ao fazer uma requisição utilizando o método PUT, a URI identifica o recurso que deseja ser criado ou atualizado e, nesse caso, como a lista já existe, então significa que o client deseja realmente modificar a própria lista de países da América do Sul.
-
- DELETE
- Assim como o PUT, o client quer apagar a lista de países da América do Sul. Obviamente ele (o client) sabe que não necessariamente o recurso será apagado, ou seja, a lista pode ser marcada para deleção futura, mas o client está tentando apagar o recurso específico, nesse caso, a lista de países da América do Sul.
-
Aceitação e requisições condicionais
Além do método de requisição, podemos utilizar campos de cabeçalho HTTP para especificar a requisição e, assim, obter exatamente aquilo que precisamos. A especificação do protocolo HTTP/1.1 define alguns campos de cabeçalho e seus objetivos; vamos ver alguns dos campos de requisição mais interessantes:
Accept
O campo Accept é bem claro, significa que o client está dizendo: “Para essa requisição, aceito respostas do tipo X”. Ou seja, o servidor, ao receber uma instrução dessa, DEVERÁ entregar o conteúdo do tipo solicitado ou informar que não é capaz de entregar esse tipo de conteúdo. A especificação desse campo é a seguinte:
Accept = "Accept" ":"
#( media-range [ accept-params ] )
media-range = ( "*/*"
| ( type "/" "*" )
| ( type "/" subtype )
) *( ";" parameter )
accept-params = ";" "q" "=" qvalue *( accept-extension )
accept-extension = ";" token [ "=" ( token | quoted-string ) ]
Imaginem que nosso client fez uma requisição HTTP utilizando o método GET para http://example.org/south-america/brazil, o que essa requisição significa? Abaixo alguns significados para essa requisição, variando apenas o campo Accept:
-
- Accept: image/*
- Utilizar o asterisco, o client está dizendo que aceita qualquer coisa. Nesses casos, a decisão fica por conta do servidor, que deverá entregar o conteúdo mais sensato segundo suas regras de negócio.
- Ao informar image/*, significa que o client espera qualquer tipo de imagem, seja png, jpeg, gif, etc.
-
- Accept: text/*;q=0.2, text/html
- O parâmetro q é um fator que qualifica o tipo aceito pelo client.
- Ao informar text/*;q=0.2, text/html, o client está dizendo que prefere text/html, mas se não tiver como entregar esse tipo de resposta, pode entregar qualquer tipo texto.
- Accept: text/*, text/html, text/html;level=1, */*
- No caso acima, o client está dizendo que prefere text/html;level=1, mas se não tiver, pode entregar text/html e se também não tiver, pode entregar qualquer tipo de texto e, ainda, se não tiver, pode entregar qualquer coisa.
Assim como o Accept que é utilizado para especificar o tipo de conteúdo esperado na resposta, o client pode utilizar outros campos para detalhar o que ele aceita como resposta:
-
- Accept-Charset
- Informa o conjunto de caracteres aceito na resposta.
- Accept-Charset: UTF-8 significa que o client aceita, como conjunto de caracteres, UTF-8.
-
- Accept-Encoding
- Informa a codificação aceita na resposta.
- Accept-Encoding: gzip significa que o client aceita, como codificação da resposta, gzip.
-
- Accept-Language
- Informa o idioma aceito na resposta.
- Accept-Language: pt-br, pt;q=0.8 significa que o client prefere português brasileiro como idioma da resposta, mas aceita português se não for tiver em português brasileiro.
Caso o servidor não consiga entregar o conteúdo conforme aceito pelo client no campo Accept, então o servidor DEVERÁ retornar um código de status HTTP 406 (Not Acceptable).
Condicionais
Além do conteúdo aceito pelo client, ele também poderá especificar algumas condições para a requisição, entre essas condições estão:
-
- If-Match
- O campo If-Match pode ser utilizado pelo client, que já possui 1 ou mais entidades recebidas do mesmo recurso, para verificar qual é a mais recente ou, em caso de uma atualização, para instruir o servidor para apenas atualizar a entidade caso ela não tenha sido atualizada desde seu recebimento.
-
- If-None-Match
- O campo If-None-Match é exatamente o inverso de If-Match, se o client enviar esse campo, o servidor deverá verificar se existe algum recurso como informado e somente entregará ou atualizará um recurso, caso não exista. Caso a entidade exista e o método de requisição seja HEAD ou GET, o servidor deverá retornar um código de status HTTP 304 (Not Modified); para quaisquer outros métodos de requisição, o servidor deverá retornar um código de status HTTP 412 (Precondition Failed).
-
- If-Modified-Since
- O campo If-Modified-Since permite ao client informar uma data, onde o servidor apenas entregará um conteúdo, caso a entidade requisitada tenha sido modificada depois dela. Caso a entidade não tenha sofrido modificações posteriores a data informada, o servidor retornará o código de status HTTP 304 (Not Modified).
-
- If-Unmodified-Since
- O campo If-Unmodified-Since é exatamente o inverso de If-Modified-Since, se o recurso tiver sido modificado, o servidor deverá retornar um código de status HTTP 412 (Precondition Failed).
-
- If-Range
- O campo If-Range é utilizado pelo client, que já possui um pedaço da entidade requisitada, para obter o restante dela. Nesse caso, o client pode utilizar o campo Range em conjunto com um If-Unmodified-Since ou If-Match. Perceba que o client deve utilizar If-Unmodified-Since ou If-Match pois é fundamental que o servidor entregue o restante da mesma entidade.
Como podemos ver, o protocolo HTTP oferece uma série de possibilidades para que as requisições sejam bastante específicas. Apesar de muito técnico/teórico, essas informações são fundamentais para que possamos prosseguir com esse assunto.
No próximo artigo, vamos utilizar tudo o que falamos até agora para implementar um servidor que entende o protocolo HTTP e sabe entregar aquilo que o client está solicitando.
Mais informações sobre a especificação do protocolo HTTP/1.1, mais especificamente a seção 9 e 14 da RFC 2616, utilizadas como base para o conteúdo desse artigo, poderão ser encontradas em:
- RFC 2616 – Hypertext Transfer Protocol
- RFC 2616, Sec. 9 – Method Definitions
- RFC 2616, Sec. 14 – Header Field Definitions
Parabens João! Ótimo artigo, aguardando o próximo!
Vai virar um manual de REST ;D
PARABENS!!!!
Ótimo artigo!
Parabéns!
Babei! Show!!!!
REI!
Cara, muito bom o post. Parabéns.. Estou ansioso para a sequencia…