Dev (Back & Front)ARTIGO

Transferência de estado representacional via HTTP – Parte 01

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 trata exclusivamente sobre o protocolo HTTP. Apenas vou utilizar neste artigo exclusivamente o protocolo HTTP e, para isso, vamos abordar os métodos de requisição e os campos de cabeçalho HTTP, assim como os seus significados.

Por ser um tema muito técnico e, de certa forma, complexo, o conteúdo foi separado em quatro artigos:

  1. Este 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, os problemas e as vantagens de 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 fizer 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 aqueles 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 os 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 à 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 a 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 a ser atualizada. Caso a requisição não aponte para um recurso existente e o servidor for capaz de atender à 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 à 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 à 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 a 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 pelo 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 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 uma 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, na qual 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 à 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 deste artigo, poderão ser encontradas em:

é engenheiro de aplicações e trabalha com ambiente web desde 2000 em diversas linguagens, como Java e PHP, dedicando esforços ao desenvolvimento de bibliotecas reutilizáveis para a comunidade. Especialista em integração de sistemas, possui várias bibliotecas reutilizáveis publicadas como open-source para a comunidade, como biblioteca Cielo, PayPal, ECT (Correios), BuscaPé, Lomadee, Twitter, Facebook entre várias outras. É administrador do fórum iMasters e iMasters Code, onde compartilha conhecimento com a comunidade de desenvolvedores. Também é autor de cursos no iMasters PRO.

Ver perfil