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:

  1. 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
  2. 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.
  3. 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.
  4. 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:

29. março 2012 by João Batista Neto
Categories: Artigos | Tags: , , , | 5 comments

Comments (5)

  1. Parabens João! Ótimo artigo, aguardando o próximo!

  2. Vai virar um manual de REST ;D
    PARABENS!!!!

  3. Ótimo artigo!
    Parabéns!

  4. Babei! Show!!!! :D

    REI!

  5. Cara, muito bom o post. Parabéns.. Estou ansioso para a sequencia…

Leave a Reply

Required fields are marked *

*