Aplicando Post/Redirect/Get Pattern na prática com PHP

Introdução

Quantas vezes, navegando aleatoriamente na internet, não recebemos um alerta
do tipo: “Tem certeza que deseja reenviar as informações para o servidor?” ou
até mesmo uma página de erro dizendo que você deve recarregar a página para
confirmar o reenvio do formulário?

Isso acontece, pois tentamos acessar mais de uma vez uma URL que utiliza verbo
HTTP como POST, por exemplo. É um problema comum quando usamos
estes verbos HTTP que não sejam GET em uma URI e esta mesma URI fica
responsável por processar a requisição e ainda exibir uma página de retorno para
o cliente. Poder ser um simples cadastro, uma página de autenticação, processos
que necessitam de um workflow ou divisões de um processo maior em pequenos
steps, como por exemplo, ao fazer o processo de finalização de uma compra,
onde comumente separamos os diversos passos:


  • 1) Identificação do cliente – Onde o usuário deva logar ou se cadastrar antes
    de ir para o próximo passo;
  • 2) Endereço de entrega;
  • 3) Dados de Pagamento;
  • 4) Confirmação do Pedido;
  • 5) Página de sucesso com confirmação de compra realizada.

E o PRG Pattern (Post/Redirect/Get) é utilizado justamente para
resolvermos este tipo de problema. Este padrão consiste basicamente em separar
realmente o processamento das informações que chegam através do formulário, da
página de resposta que será entregue ao cliente.

Devemos tomar muito cuidado, pois isso não é uma questão apenas ter que cofirmar
o reenvio da requisição. Devemos prestar muita atenção neste tipo de reenvio de
informação, principamente com etapas delicadas do processo, pois um simples
momento de desatenção, pode fazer com que o cliente seja cobrado duas vezes, por
exemplo.

Gerando o erro de reenvio de informações

Vamos construir um simples exemplo usando Silex
com engine de template Twig para reproduzir o
problema e posteriormente arrumá-lo.

Teremos basicamente três rotas inicialmente:

GET /signup

POST /signup_confirmation

POST /success

Nosso arquivo web/index.php do Silex fica assim:

<?php
require_once __DIR__.'/../vendor/autoload.php';

use Symfony\Component\HttpFoundation\Request;

$app = new Silex\Application();
// Adds Twig service provider
$app->register(new Silex\Provider\TwigServiceProvider(), array(
    'twig.path' => __DIR__.'/../views',
));

// Routes

$app->run();

O próximo passo seria adicionar a implementação das rotas no espaço que deixei
reservado no arquivo acima. Então teríamos as seguintes implementações:

// Routes
// GET /signup
$app->get('/signup', function () use ($app) {
    return $app['twig']->render('signup.twig');
});

// POST /signup_confirmation
$app->post('/signup_confirmation', function (Request $request) use ($app) {
    return $app['twig']->render(
        'signup_confirmation.twig',
        [
            'username' => $request->get('username'),
            'password' => md5($request->get('password'))
        ]
    );
});

// POST /success
$app->post('/success', function (Request $request) use ($app) {
    return $app['twig']->render(
        'success.twig',
        ['username' => $request->get('username')]
    );
});

E nossas views:

  • views/signup.twig
<h1>Signup</h1>
<form action='/signup_confirmation' method='post'>
    <fieldset>
        <label for='username'>Username</label>
        <input id='username' name='username' type='text' />
        <label for='password'>Password</label>
        <input id='password' name='password' type='password' />
        <button>Save</button>
    </fieldset>
</form>
  • views/signup_confirmation.twig
<h1>Signup Confirmation</h1>
<form action='/success' method='post'>
    <fieldset>
        <legend>Do you really want complete your registration?</legend>
        <input name='username' type='hidden' value='{{ username }}' />
        <input name='password' type='hidden' value='{{ password }}' />
        <button>Confirm</button>
    </fieldset>
</form>
  • views/success.twig
<h1>Success</h1>
<p>Congratulations, {{ username }}, you were registered successfully!</p>

Pronto! Agora temos o problema reproduzido. Se acessarmos /signup,
submetermos o formulário e tentarmos carregar as páginas seguintes mais de uma
vez, receberemos um alerta, variando de browser para browser, mas que deve ser
algo como no Firefox, por exemplo: “Para mostrar esta página, o Firefox deve
enviar informações que irão repetir a ação (como uma busca ou confirmação de
compra) que foi executada anterioremente”.

Agora vamos ver como podemos usar o PRG Pattern para fugirmos deste problema.

Fugindo do problema de reenvio de formulário

Primeiramente, como se trata de uma sequência de Post/Redirect/Get, fica claro
que as rotas que nós temos não são capazes de resolver nosso problema,
precisamos criar pelo menos mais duas rotas. Estas rotas serão responsáveis por
processar o envio do formulário de cadastro e outra para o processamento da
confirmação do cadastro. O que deixaria nosso cenário assim:

GET /signup

POST /process_signup

GET /signup_confirmation

POST /process_confirmation

GET /success

Deixando um pouco a discussão sobre melhores práticas, só para facilitar nosso
exemplo, vou utilizar Session para trafegar os dados de cadastro entre as
páginas. Então, registramos mais um service provider ao web/index.php

$app->register(new Silex\Provider\SessionServiceProvider());

Depois, podemos alterar nosso arquivo web/index.php para atender estes novos
requisitos, adicionando duas novas rotas. Que basicamente irão receber os dados
passados pelos dois formulários e guardar as informações do cadastro na sessão,
além claro, de fazer o redirecionamento (usando o código HTTP/1.1 303 See
Other
) para a rota correta que deve exibir a página de resposta:

// POST /process_signup
$app->post('/process_signup', function (Request $request) use ($app) {
    $app['session']->set('username', $request->get('username'));
    $app['session']->set('password', md5($request->get('password')));

    return $app->redirect('/signup_confirmation', 303);
});

// POST /process_confirmation
$app->post('/process_confirmation', function () use ($app) {
    $app['session']->set('confirmed', true);

    return $app->redirect('/success', 303);
});

Feito isso, precisaremos adaptar as rotas que já existiam para atender também
esse novo formato. Alteramos as rotas /signup_confirmation e /success para
GET e alteramos seu comportamento para ler as informações da sessão ao invés
da variável $request

$app->get('/signup_confirmation', function () use ($app) {
    return $app['twig']->render(
        'signup_confirmation.twig',
        [
            'username' => $app['session']->get('username'),
            'password' => $app['session']->get('password')
        ]
    );
});

$app->get('/success', function () use ($app) {
    return $app['twig']->render(
        'success.twig',
        [
            'username'  => $app['session']->get('username')
        ]
    );
});

Com isso, podemos navegar tranquilamente entre as páginas e recarregá-las sem
o perigo de reenviar as informações novamente para serem processadas. Claro,
algumas validações extras poderiam ser feitas em um caso de uso real para
este processo, por exemplo, um cliente não poderá acessar a página de sucesso
diretamente sem ter passado pelo processo de cadastro.

Para garantir isso, podemos fazer o seguinte na nossa página /success:

$app->get('/success', function (Request $request) use ($app) {
    if (! $app['session']->get('confirmed')) {
        return $app->redirect('/signup');
    }

    return $app['twig']->render(
        'success.twig',
        [
            'username'  => $app['session']->get('username')
        ]
    );
});

Assim temos um exemplo da aplicação funcionando perfeitamente rodando
Post/Redirect/Get.

O código fonte deste exemplo pode ser visto no GitHub: [PRG Sample]
(https://www.github.com/rplansky/prg-sample.git)

Conclusão

Mais do que evitar o incômodo de receber alertas todas as vezes que tentarmos
acessar uma página que envie dados na requisição, devemos levar em
consideração quais impactos esta requisição duplicada pode causar para o
usuário. Em muitos casos, esta requisição duplicada pode ser só perda de tempo,
sem nenhum mal efetivamente causado a ele. Por exemplo, não há nada de perigoso
um formulário de login ser enviado mais de uma vez, isso não trará impacto
algum para o usuário, apenas uma requisição desnecessária. Mas por outro lado,
uma requisição duplicada na hora de fechar uma compra, talvez (leia-se: com
certeza) seja uma dor de cabeça enorme. Por isso, devemos ficar sempre atentos
às etapas que envolvam processos e informações delicadas.

E cá entre nós, não é nem um pouco difícil aplicar Post/Redirect/Get, vai?!
=)

Definição, restrições e benefícios do modelo de arquitetura REST

Ultimamente vim pensando a respeito de lançar o blog e começar escrevendo um
artigo sobre como criar um web service RESTful, mas antes disso, conversando num
hangout com alguns outros devs, percebi que antes do “como fazer”, seria
bacana um “o que é”, para mostrar os fundamentos e os conceitos por trás da
arquitetura REST. A grande discussão na ocasião era: até que ponto vale a pena
usar um grande esforço para tornar a sua aplicação RESTful.

Primeiramente, gostaria de falar o que este artigo NÃO é: este artigo não tem
como objetivo mostrar o porquê de usar REST ao invés de usar SOAP ou qualquer
outro protocolo/arquitetura para construir sua API.
Este artigo tem como principal objetivo, na verdade, deixar claro o que é o
REST; quais as vantagens de usar esta arquitetura; o que faz da sua API ser
considerada RESTful e o porquê muitas dizem que são, mas na verdade não são.

Vamos lá.

O que é REST?

O termo foi definido no ano 2000, na tese de doutorado de
Roy Fielding e é a sigla para
Representational State Transfer: é um design de arquitetura construído para
servir aplicações em rede. A aplicação mais comum de REST é a própria
World Wide Web, que utilizou REST como base para o desenvolvimento do HTTP
1.1.

REST traz uma série de benefícios – que irei falar logo mais – mas ele não é um
padrão, você não é obrigado a seguí-lo para construir seus web services ou
aplicação web, ele é apenas uma espécie de guia com algumas recomendações.

Este tema é muito comentado quando falamos em construção de web services, mas
este não é um modelo de arquitetura específico para construção de APIs somente,
ele pode – E DEVE – ser utilizado na construção de sistemas web que não
serão expostos na forma de API necessariamente.

As restrições do REST

1. Client-Server

É a restrição básica para uma aplicação REST. O objetivo desta divisão é separar
a arquiterura e responsabilidades em dois ambientes. Assim, o cliente
(consumidor do serviço) não se preocupa com tarefas do tipo: comunicação com
banco de dados, gerenciamento de cache, log, etc. E o contrário também é válido,
o servidor (provedor do serviço) não se preocupa com tarefas como: interface,
experiência do usuário, etc. Permitindo, assim, a evolução independente das duas
arquiteturas.

Neste modelo, o servidor espera pelas requisições do cliente, executa estas
requests e devolve uma resposta.

Modelo client-server

2. Stateless

Um mesmo cliente pode mandar várias requisições para o servidor, porém, cada uma
delas devem ser independentes, ou seja, toda requisição deve conter todas as
informações necessárias para que o servidor consiga entendê-la e processá-la
adequatamente.

Neste caso, o servidor não deve guardar nenhuma informação a respeito do estado
do cliente. Qualquer informação de estado deve ficar no cliente, como as
sessões, por exemplo.

3. Cacheable

Como muitos clientes acessam um mesmo servidor e muitas vezes requisitando os
mesmos resources, é necessário que estas respostas possam ser cacheadas,
evitando processamento desnecessário e aumentando significativamente a
performance.

Isso significa que um primeiro cliente solicita um determinado resource para o
servidor, o servidor processa esta requisição e armazena isso temporariamente
no cache. Quando os demais clientes solicitam o mesmo resource, o servidor
devolve o que está no cache sem ter que reprocessá-lo. A regra para limpar o
cache varia de resource para resource, pode ser limpo sempre que houver
uma troca de estado no resource; pode ser limpo em um determinado intervalo de
tempo antes de ser reprocessado, de hora em hora, por exemplo, e por aí vai.

4. Uniform Interface

É basicamente um contrato para comunicação entre clientes e servidor. São
pequenas regras para deixar um componente o mais genérico possível. Deixando o
componente muito mais fácil de ser refatorado e melhorado.

Dentro desta regra, existe uma espécie de guideline para fazer essa
comunicação uniforme:

  • 1) Identificando o resource – Cada resource deve ter uma URI específica e
    coesa para poder ser acessado, por exemplo, para trazer um determinado usuário
    cadastrado no site:
HTTP/1.1 GET http://rplansky.com/user/rplansky
  • 2) Representação do resource – É a forma como o resource vai ser devoldido
    para o cliente. Esta representação pode ser em HTML, XML, JSON, TXT,
    entre outras. Exemplo de como seria um retorno simples da chamada acima:
{
    "name": "Ricardo Plansky",
    "job": "Web Analyst/Developer",
    "hobbies": ["football", "coding", "music"]
}
  • 3) Resposta auto-explicativa – Além do que vimos até agora, é necessário a
    passagem de meta informações (metadata) na request e na resposta. Algumas
    destas informações são: código HTTP da resposta, Host, Content-Type, etc.
    Tendo como exemplo a mesma URI que acabamos de ver:
GET /#!/users/rplansky HTTP/1.1
User-Agent: Chrome/37.0.2062.94
Accept: application/json
Host: rplansky.com
  • 4) Hypermedia – Esta parte por muitas vezes é esquecida quando falamos de
    REST. Consiste em retornar todas as informações necessárias na resposta para que
    cliente saiba navegar e ter acesso a todos os resources da aplicação. Não vou
    prolongar neste item que será o assunto do próximo post. Apenas um exemplo:

Requisição

HTTP/1.1 POST http://rplansky.com/rplansky/posts

Resposta

{
    "post": {
        "id": 42,
        "title": "Conceitos REST",
        "decription": "Um pouco sobre conceito da arquitetura REST",
        "_links": [
            {
                "href": "/rplansky/post/42",
                "method": "GET",
                "rel": "self"
            },
            {
                "href": "/rplansky/post/42",
                "method": "DELETE",
                "rel": "remove"
            },
            {
                "href": "/rplansky/post/42/comments",
                "method": "GET",
                "rel": "comments"
            },
            {
                "href": "/rplansky/posts/42/comments",
                "method": "POST",
                "rel": "new_comment"
            },
            {...}
        ]
    },
    "_links": {
        "href": "/post",
        "method": "GET",
        "rel": "list"
    }
}

5. Layered System

A sua aplicação deve ser composta por camadas, e estas camadas devem ser fáceis
de alterar, tanto para adicionar mais camadas, quanto para removê-las. Dito
isso, um dos princípios desta restrição é que o cliente nunca deve chamar
diretamente o servidor da aplicação sem antes passar por um intermediador, no
caso, pode ser um load balancer ou qualquer outra máquina que faça a interface
com o(s) servidor(es).
Isso garante que o cliente se preocupe apenas com a comunicação com o
intermediador e o intermediador fica responsável por distribuir as requições
nos servidores, seja um ou mais, indifere nesse caso. O importante é ficar claro
que criando um intermediador, a sua estrutura fica muito mais flexível à
mudanças.

Load Balancer

6. Code-On-Demand (Opcional)

Esta condição permite que o cliente possa executar algum código sob demanda, ou
seja, estender parte da lógica do servidor para o cliente, seja através de um
applet ou scripts. Assim, diferentes clientes podem se comportar de maneiras
específicas mesmo que utilizando exatamente os mesmos serviços providos pelo
servidor.

Como este item não faz parte da arquitetura em si, ele é considerado opcional.
Ele pode ser utilizado quando executar alguma parte do serviço do lado do
cliente for mais eficaz ou rápida.

E o que é RESTful?

Muita gente, quando usam diferentes URIs, verbos HTTP e diferentes formatos de
retorno, passam a dizer que têm uma API RESTful. Tudo isso é muito importante e
faz parte, mas não é apenas isso que compõe o RESTful.

Para uma API ser considerada RESTful, ela deve seguir estritamente as regras
definidas na arquitetura REST (com exceção da 6, obviamente, que é opcional),
além de possuir um certo nível de coesão e maturidade, definidos na escala
chamada de
Richardson Maturity Model.

Falarei no próximo artigo mais detalhadamente sobre como construir na prática
uma API RESTful seguindo este modelo. Mas para dar uma breve noção, os níveis
são:

  • Nível 0: Resumidamente é a ausência de qualquer regra, é apenas a
    utilização do HTTP como transporte das operações no servidor. Normalmente se usa
    apenas um endpoint (URI) e um verbo HTTP.

  • Nível 1: Aplicação de resources. A API é dividida em diferentes
    endpoints que apontam para um ou mais resources.

  • Nível 2: Implementação de verbos HTTP para diferentes tipos de operações
    que deseja executar. Uma mesma URI por aceitar mais de um verbo HTTP, por
    exemplo: GET /user pode retornar todos os usuários e
    POST /user passando os atributos do usuário pode criar um novo.

  • Nível 3: Um novo conceito é adicionado, chamado:
    HATEOAS
    (Hypertext As The Engine Of Application State). Onde a API deve fornecer para o
    cliente toda a informação necessária para interagir com a aplicação.

Conclusão

REST é um modelo de arquitetura bem definido para servir aplicações web. Ele
já oferece todo o guideline necessário para construirmos um serviço coeso,
escalável e performático – e não é difícil. No dia-a-dia nós já utilizamos
muitas das recomendações do REST: client-server, cache, stateless, etc. Para
passarmos a ser RESTful, é apenas uma questão de se policiar e botar em prática
alguns novos passos, até que tudo se torne automático.
Seguir este modelo de arquitetura nos permite se preparar para o crescimento do
serviço sem grandes impactos, além de oferecer aos clientes da aplicação um
modelo que é de conhecimento comum, intuitivo até, evitando a necessidade de
fazê-lo entender sobre uma nova arquitetura para consumir o serviço.

Em breve falo um pouco mais de REST… na prática.

Referências