Para quem não sabe, Web::Simple é um modulo para criar pequenas aplicações web.
Ele é rápido e pequeno, e funciona utilizando pĺack, assim como quase todos frameworks agora.
Se você ainda não sabe quando deve utilizar Web::Simple ou Catalyst, uma dica é: se não existem muitas regras[1] e você vai se virar com os módulos para conversar com as apis[2], faça com Web::Simple.
[1] “muitas regras”, na verdade, é se você vai ter que escrever muitos dispatchers.
[2] API do twitter, OAuth etc.. essas APIs já estão prontas para integrar com o catalyst, então por que repetir tudo?
O que é um dispatcher
Em palavras para novatos, dispatcher é o meio pelo qual você diz ao framework qual código cada URL deve executar.
No caso do Web::Simple, a lógica é definida no retorno da sub dispatch_request.
O retorno de dispatch_request deve ser uma ARRAY de várias outras subs. Essas subs são escritas usando atributos que informam quando elas devem ser executadas.
Hello World da documentação:
sub dispatch_request { sub (GET) { [ 200, [ 'Content-type', 'text/plain' ], [ 'Hello world!' ] ] }, sub () { [ 405, [ 'Content-type', 'text/plain' ], [ 'Method not allowed' ] ] } }
Acima, o código retorna duas subs, e cada uma retorna uma ARRAYREF, onde o primeiro valor é o status do HTTP, o segundo são os headers, e por último, o conteúdo.
Instalando e começando a usar
Considerando que você já instalou o cpanm seja via local::lib ou perlbrew, execute:
$ cpanm Web::Simple; $ cpanm Starman
Após instalar o Web::Simple e o Starman, crie em algum lugar:
$ cd /tmp/
com o seu editor favorito, abra o arquivo HelloWorld.psgi e escreva nele:
#!/usr/bin/env perl package HelloWorld; use Web::Simple; sub dispatch_request { sub (GET) { [ 200, [ 'Content-type', 'text/plain' ], [ 'Hello world!' ] ] }, sub () { [ 405, [ 'Content-type', 'text/plain' ], [ 'Method not allowed' ] ] } } HelloWorld->run_if_script;
Volte para o terminal, e suba o starman:
$ starman HelloWorld.psgi --workers 2
Com esses parâmetros, o starman sobe a sua app na porta 5000 com bind para 0.0.0.0, usando 2 workers.
Acesse [http://0.0.0.0:5000](http://0.0.0.0:5000 0.0.0.0:5000) e veja, seu Hello World! está na tela.
Agora, edite seu arquivo para ficar assim:
#!/usr/bin/env perl package HelloWorld; use Web::Simple; use utf8; use Encode; sub dispatch_request { sub (GET) { [ 200, [ 'Content-type', 'text/plain; charset=utf-8' ], [ encode("utf8", 'São Paulo Perl mongers') ] ] }, sub () { [ 405, [ 'Content-type', 'text/plain' ], [ 'Method not allowed' ] ] } } HelloWorld->;run_if_script;
Se você recarregar sua página, o texto não vai mudar. Para isso, é necessário que recarregue sua app.
Faça um ps aux para descobrir qual o PID master do starman:
renato@ood:/tmp/ws$ ps aux|grep starman renato 4080 1.1 0.0 10932 6964 pts/6 S+ 06:36 0:00 starman master HelloWorld.psgi --workers 2 renato 4081 0.4 0.0 12412 7844 pts/6 S+ 06:36 0:00 starman worker HelloWorld.psgi --workers 2 renato 4082 0.2 0.0 12412 7844 pts/6 S+ 06:36 0:00 starman worker HelloWorld.psgi --workers 2 renato 4085 0.0 0.0 4396 832 pts/7 R+ 06:36 0:00 grep --color=auto starman
No caso, o PID do master é o 4080, o primeiro da lista. Envie um sinal de HUP para ele:
$ kill -HUP 4080
Agora, se você acessar [http://0.0.0.0:5000](http://0.0.0.0:5000 0.0.0.0:5000), irá ver São Paulo Perl mongers escrito corretamente. Se você remover o use utf8, junto com o Encode e o encode(“utf8”, …), também vai funcionar sem erro de encoding, porém, o perl está apenas passando pra frente o que você escreveu, sem saber o que é, e o browser está respeitando o que foi dito no header.
A melhor forma é você saber qual encoding é o seu conteúdo e sempre tratar isso.
Agora que você já tem uma aplicação rodando, também sabe como reiniciá-la, vamos entender o que acontece no dispatcher.
A primeira sub disse apenas no seu atributo que seu request era GET. Sem informar nada, entende-se que todas URLs digitadas serão afetadas. Portanto, [http://0.0.0.0:5000/foobar](http://0.0.0.0:5000/foobar /foobar) vai exibir o mesmo conteúdo.
A outra sub não disse nada, ou seja, ela é o jeito que o Matt S Trout criou para fazer o “not found”, ou seja, esse metodo é executado quando nenhuma das outras regras se aplicarem.
Para ver o 405 em ação, execute, se você tem curl instalado:
$ curl -X POST http://0.0.0.0:5000/ Method not allowed
Veja que DELETE, HEAD e OPTIONS também não são aceitos, mas GET são:
$ curl -X GET http://0.0.0.0:5000/ São Paulo Perl mongers
Web::Simple – especificações das regras do dispatcher
Regras foram retiradas com base na documentação do dia 26/02/2013.
*Por metodo HTTP: *
sub (GET) { match com base no METHOD do HTTP. Nesse caso, GET.
sub (GET|HEAD) { GET ou HEAD entrariam nessa regra.
Por caminho, o famoso Path:
sub (/login) { Uma sub com atributo começando com / significa uma regra por path. Nesse caso, o mais simples, o caminho inteiro é considerado. Ou seja, quando alguém acessar http://0.0.0.0:5000/login, ele será executado.
sub (/user/*) { aqui, parâmetros são capturados por posição. Cada * pode ser qualquer valor, edite seu arquivo para:
sub (/user/*) { my ($self, $user_id) = @_; [ 200, [ 'Content-type', 'text/plain; charset=utf-8' ], [ encode("utf8", 'Olá, user ' . $user_id ) ] ] },
Faça o kill -HUP para reiniciar e depois acesse http://0.0.0.0:5000/user/1456789 então você vai ver o texto “Olá, user 1456789”
Você pode juntar vários desses:
sub (/user/*/*) { my ($self, $user_1, $user_2) = @_; e/ou dependendo e como você quer:
sub (/alguma-coisa/*/sua-outra-url/*) { my ($self, $alguma_1, $outra_2) = @_;
Vamos com outro exemplo agora, se você tem a url /processos-por-ids/123/134/1456/856/624 você precisa escrever:
sub (/processos-por-ids/**) { my ($self, $ids) = @_; my @ids = split qr|/|, $ids; sub (/processos-por-ids/**) { my ($self, $ids) = @_; my @ids = split qr|/|, $ids; [ 200, [ 'Content-type', 'text/plain; charset=utf-8' ], [ encode("utf8", "IDs:\n" . join("\n", @ids) ) ] ] },
Pronto! Agora supondo que você queria editar vários processos de uma vez, você poderia criar:
sub (/processos-por-ids/**/editar) { my ($self, $ids) = @_; my @ids = split qr|/|, $ids; ... }
Observações: Como o Web::Simple tenta tratar as extensões dos “arquivos”, então você precisa tomar cuidado, pois, por padrão, o Web::Simple não considera de regra nada que fica depois de um ponto final na url.
/one/* matches /one/two.three and captures "two" /one/*.* matches /one/two.three and captures "two.three" /** matches /one/two.three and captures "one/two" /**.* matches /one/two.three and captures "one/two.three"
E, por último, mas não menos importante, existe a regra dos 3 pontinhos…
sub (/foo/…) { Ela significa, que “pode ter algo aqui, mas talvez não” e também te dá uma chance de criar sub encadeadas recursivamente.
/foo # não faz match /foo/ # faz o match e reseta o path para '/' para as proximas subs encadeadas /foo/bar/baz # faz o match e reseta o path para '/bar/baz' para as proximas subs encadeadas
Perceba que /foo ficou de fora. Para incluí-lo, você tem que fazer sub (/foo…) { sem colocar a barra depois do foo.
/foo # faz match e o path vira ''
Um uso bom seria começar tudo com /… para e isso capturado como idioma do site. /pt/ ou /en/.
Estes dois códigos abaixo são equivalentes:
sub (/foo) { 'I match /foo' }, sub (/foo/...) { sub (/bar) { 'I match /foo/bar' }, sub (/*) { 'I match /foo/{id}' }, }
e
sub (/foo...) { # execute código aqui que são uteis para todas as chamdas. sub (~) { 'I match /foo' }, sub (/bar) { 'I match /foo/bar' }, sub (/*) { 'I match /foo/{id}' }, }
Então, quando você utiliza /foo… você acessa o path ” utilizando ~
O segundo jeito de escrever é “melhor”, pois permite que você possa escrever apenas uma vez o código que vai ser utilizado em todas as subs daquele escopo. Por exemplo, o resultset do DBIC.
sub (/user...) { my $user_rs = $schema->;resultset('User'); sub (~) { $user_rs }, sub (/*) { $user_rs->;find($_[1]) }, }
Parâmetros com nomes
Às vezes, você pode querer receber os parâmetros em um HASHREF no lugar de recebê-los em variáveis separadas.
sub (/*:one/*:two/*:three/*:four) { my ($self, $hash) = @_; use Data::Dumper; [ 200, [ 'Content-type', 'text/plain; charset=utf-8' ], [ Dumper $hash ] ] },
Acessando http://0.0.0.0:5000/um/dois/tres/quatro, vai ser algo parecido com:
$VAR1 = { 'three' =>; 'tres', 'one' =>; 'um', 'two' =>; 'dois', 'four' =>; 'quatro' };
Marcações por extensão
Como foi já foi dito, o Web::Simple tem meios para tratar as extensões. Então, você pode criar:
sub (.html) { isso geralmente é utilizado para escolher o meio que você vai renderizar.
Capturando Query e body parameter
Eles podem ser capturados com:
sub (?<param spec>;) { # match URI query sub (%<param spec>;) { # match body params
É possível capturar os parâmetros encodados via application/x-www-form-urlencoded ou multipart/form-data.
Existem várias maneiras de capturar:
param~ # parametro opcional param= # parametro requerido @param~ # parametro opcional e multiplo @param= # parametro requerido e multiplo :param~ # opcional e vai para um hashref :param= # requerido e vai para um hashref :@param~ # opcional e vão para uma arrayref dentro de um hashref :@param= # requerido e vão para uma arrayref dentro do hashref * # tudo vai para um hashref @* # todos os parametros que não foram capturados antes, vão para um hashref
Note que se você mandou caputrar um parâmetro como múltiplo, você sempre vai recebê-lo via array, mesmo se for apenas um item ?foo. No outro caso, se você mandou capturar apenas um, e o parâmetro aparece várias vezes, apenas a última vez é considerada.
Exemplo de como receber o order-by e o número da página pela URL:
sub (?page=&order_by~) { my ($self, $page, $order_by) = @_; return unless $page =~ /^\d+$/; ... }
Exemplo de como receber todos os parâmetros para um hashref de arrayref:
sub(?@*) { my ($self, $params) = @_; use Data::Dumper; [ 200, [ 'Content-type', 'text/plain; charset=utf-8' ], [ Dumper $params ] ] },
Acessando http://0.0.0.0:5000/qualquerurl?abc=23&34=3&foo=1&foo=2 vai gerar:
$VAR1 = { '34' =>; [ '3' ], 'abc' =>; [ '23' ], 'foo' =>; [ '1', '2' ] };
Combinando regras
Os três diferentes tipos de regras podem ser combinados usando +. Os tipos são:
- por método
- por path
- parêmetros query/body (e/ou upload)
sub (GET + /user/*) { captura o /user/* apenas em requests GET
sub (GET|POST) { GET ou POST, todos os paths
sub ((GET|POST) + /user/*) { GET ou POST + path
sub (!/user/foo + /user/*) { Path negado, por exemplo, todos /user/ mas não /user/foo
sub ( !(POST|PUT|DELETE) ) { Captura o que nao for nem POST, nem PUT, nem DELETE, ou seja, GET|OPTIONS|HEAD e os que inventarem em outras versões de HTTP.
Note que você pode utilizar espaços à vontade para facilitar a leitura.
Fim!
O Web::Simple tem sistema de regras muito legal, que facilita para você criar aplicações que ficam no meio de outras, usando Plack::Middleware. Você pode dar uma olhada em Otimizando o Uso de Memória e CPU em Servidores Web com Starman; no final dele, existe um exemplo de como subir várias aplicações sob um mesmo PSGI.
Na versão em que o Pendant se encontrava no dia 26/02/2013, ele utilizava Web::Simple para traduzir arquivos de Markdown/POD/etc para HTML. Você pode ver isso neste tree do github.
***
Artigo de Renato CRON, publicado no Equinócio 2013 do grupo São Paulo Perl Mongers – http://sao-paulo.pm.org/equinocio/2013/mar/02-comecando-com-web-simple
Texto sob Creative Commons – Atribuição – Partilha nos Mesmos Termos 3.0 Não Adaptada, mais informações em http://creativecommons.org/licenses/by-sa/3.0/