Construindo um Website Dinâmico com Class::DBI, CGI::Application e Template Toolkit
Prefácio
Quando pensamos em aplicativos World Wide Web em Perl, normalmente nos vêem à cabeça aqueles programas gigantescos, desorganizados, cheios de problemas e praticamente impossíveis de se manter. Neste modesto e mal-escrito artigo, eu pretendo mostrar que é possível construir aplicativos dinâmicos, baseados nas tecnologias da World Wide Web, simples, rápidos, fáceis de manter e extensíveis, usando ferramentas de código fonte aberto, noções sobre o paradigma da Orientação a Objetos, e algumas técnicas de engenharia de software básica.
Molhando os pés: primeiro contato com as tecnologias utilizadas neste artigo
Para começar, vamos falar da aplicação que desejamos fazer: é simples, e chega a ser até meio besta, para facilitar a compreensão. Desta forma, ao invés de se prender aos detalhes e complexidades da aplicação que vamos construir, o leitor terá sua atenção presa aos detalhes e complexidades envolvidas com como construir a aplicação.
Vamos construir um Módulo de Autenticação de Usuários.
A responsabilidade principal de um Módulo de Autenticação de Usuários é se certificar de que um usuário de nosso website é realmente quem ele diz ser, e permitir a ele acesso aos demais recursos dinâmicos disponíveis em nosso website fictício.
Para construir esta aplicação, vamos utilizar um framework de desenvolvimento de aplicações dinâmicas para a World Wide Web chamado CGI::Application, combinado com um gerenciador de banco de dados relacional (eu escolhi usar o PostgreSQL, mas outros, como o MySQL e o Oracle se encaixam aqui perfeitamente e não precisam de adaptações) através do uso de um conjunto de objetos Perl chamado Class::DBI. Completam o nosso conjunto de bibliotecas-base um framework de visualização muito poderoso chamado Template Toolkit e um pouco de teoria sobre padrões de projeto e modelagem orientada a objetos.
O Projeto da Aplicação
Antes de continuarmos, vamos especificar e modelar rapidamente nossa aplicação. Isso, além de nos dar maior consciência sobre o que estamos construindo, ajuda as pessoas a compreender nosso trabalho (e a valorizá-lo).
Nosso Módulo de Autenticação de Usuários terá apenas um ponto de entrada: ele gerará uma página para coletar o nome e senha do usuário, tomará uma decisão baseado nos dados que o usuário lhe enviar e executará uma das duas únicas ações planejadas para ele: ou criará uma sessão para o usuário e o redirecionará para a parte restrita de nosso website dinâmico, ou enviará uma mensagem de erro, informando ao usuário que o par (nome, senha) apresentado não é válido. Em Portugol, para ficar simples:
# Processo 1: coleta de credenciais INÍCIO; constrói página de coleta de dados; envia página de coleta de dados; FIM. # Processo 2: autenticação de usuários. INÍCIO; <usuário preenche informações de autenticação>; recebe dados de autenticação de usuário; verifica dados; SE dados válidos, ENTÃO INÍCIO gera informação de sessão; registra informação de sessão; envia cookie identificador ao usuário; redireciona usuário autenticado para a aplicação; FIM SENÃO INÍCIO gera página de informação de erro; envia página ao usuário; FIM; FIMSE; FIM;
Agora que sabemos como deve ser nosso processo, vamos modelar rapidamente os dados necessários para autenticar um usuário. Precisamos apenas reter permanentemente algumas informações sobre os usuários, como um nome, e uma senha. Para isso, vamos criar uma tabela em nosso banco de dados relacional, utilizando este código SQL:
Isto vai criar uma tabela em nosso banco de dados, onde poderemos registrar nossos usuários.
-- Este script assume que você tem um banco de dados PostgreSQL, pode -- conectar-se a ele e tem permissões mínimas para realizar criação de -- tabelas e inclusão de registros nas tabelas criadas. -- Se você tiver qualquer problema com isso, converse com seu DBA ou -- Administrador de Sistemas local. -- Copyleft 2005 Luis Campos de Carvalho -- Copyleft 2005 Engenharia de Software Ltda -- Doado aos São Paulo Perl Mongers -- This script is free software; you can redistribute it and/or modify -- it under the term of the GNU Public License. CREATE SEQUENCE seq_usuario; CREATE TABLE usuario ( uid INTEGER PRIMARY KEY DEFAULT NEXTVAL( 'seq_usuario'::text ), login VARCHAR UNIQUE NOT NULL, senha VARCHAR NOT NULL ); -- EOF
A aplicação do SQL no banco de dados varia muito. Por isso, vou deixar a tarefa de descobrir como se conectar ao banco de dados e criar tabelas para um futuro artigo sobre DBI e Class::DBI.
Vamos passar agora à parte de modelagem de objetos e módulos necessários para a aplicação. Para isso, antes, precisamos conversar um pouco sobre MVC, Class::DBI, Template Toolkit e CGI::Application.
MVC, Class::DBI e CGI::Application
Agora vamos entrar na parte complicada deste tutorial. O leitor amigo possivelmente deve ter ouvido alguma coisa a respeito de MVC, ou, para os iniciados, Model-View-Controller. Este é um padrão de projeto (Design Pattern) que separa toda e qualquer aplicação em três componentes básicos: o Modelo(Model), a Visão (View) e o Controlador (Controller). Leiam um contraponto, por Andy Wardley.
Em poucas palavras, já que eu não pretendo nem de longe esgotar este assunto: o Modelo é responsável por manter nossos dados e manipulá-los de acordo com as regras de negócio (que implementamos neles). São os modelos (geralmente temos muitos) que sabem como e onde armazenar e recuperar os dados que captamos e utilizamos.
Enquanto os modelos são responsáveis pelos dados e pelas regras de negócio (a forma de manipular os dados), as Visões tem como responsabilidade a exibição dos dados encontrados nos modelos. Uma visão normalmente conhece um modelo e sabe como exibir os dados daquele modelo por alguma mídia específica. Por exemplo: suponha que temos uma lista de usuários (um Modelo representando usuários) e desejamos exibir estes usuários pela internet (com HTML) e em um telefone celular (com WAP). Para tornar isso possível, não precisamos mexer em nada relacionado com nossa lista de usuários: precisamos apenas implementar Visões capazes de exibir em HTML ou WAP. Assim, quando desejamos exibir a lista de usuários em HTML, utilizamos a visão capaz de exibir a lista em HTML e quando desejamos exibir nossos usuários em WAP, utilizamos a visão que implementa exibição WAP.
Finalmente, o Controlador tem um papel não muito intuitivo, mas muito importante, depois que se aprende a utilizá-lo corretamente: é dele a responsabilidade de decidir, para cada requisição enviada para nossa aplicação, qual modelo deve ser acionado para tratar dela. É também do Controlador a responsabilidade de verificar as credenciais de um usuário e determinar suas permissões de acesso. Normalmente, uma aplicação web tem apenas um controlador, não importa o quão grande ela seja.
A esta altura, você deve estar se perguntando qual a relação disso tudo com as bibliotecas Perl Class::DBI, Template Toolkit e CGI::Application. É hora de explicar: cada uma desta bibliotecas implementa um dos elementos do MVC que vamos utilizar em nossa aplicação: Class::DBI implementa modelos, Template Toolkit implementa visões e CGI::Aplication implementa nosso controlador. Vamos construir agora cada uma das diferentes partes do padrão de projeto que utilizaremos:
Implementação de Sessão de Usuário
A sessão é uma parte muito importante da implementação de aplicações web, já que o protocolo HTTP é stateless, quer dizer, sem estado, no sentido de que não há conservação de contexto para cada cliente quando uma nova requisição chega.
Para contornar este feature do protocolo, implementamos dentro da aplicação todo o processo de manutenção de estado necessária para implementar uma sessão.
Este processo consiste em obter e manter no servidor web onde nossa aplicação roda um objeto persistente (armazenado em disco ou outra mídia qualquer), capaz de nos oferecer todas as informações sobre o contexto em que uma requisição deve ser tratada.
Entre muitas outras informações importantes, vamos utilizar este objeto de sessão para armazenar as credenciais de nossos usuários, e vamos fazer com que elas expirem de tempos em tempos, para manter o sistema seguro.
Nossa implementação de sessão precisa de quatro métodos, rodados em locais diferentes do sistema, para ser efetiva. Infelizmente, como estamos implementando uma aplicação de autenticação de usuários, não poderemos demonstrar corretamente o uso dos quatro métodos, pois um deles não pode ser aplicado às requisições que vamos atender.
Nosso primeiro método para cuidar de sessão chama-se init_session(). Sua responsabilidade principal é inicializar e eventualmente recuperar o objeto CGI::Session que implementa nossas sessões.
Nosso segundo método será gerado automaticamente pela biblioteca Class::Accessors. Trata-se de nosso método de acesso ao objeto de sessão.
Nosso terceiro método chama-se save_session(). Sua responsabilidade é salvar corretamente o cookie de sessão de volta para o browser ao término de cada requisição.
Finalmente,
nosso quarto e último método não pode ser exemplificado aqui, pois
serve para se certificar de que um determinado usuário ainda possui uma
credencial válida para continuar acessando nossos módulos de aplicação,
redirecionando o usuário de volta à página de autenticação de usuário
conforme a necessidade. Ele chama-se validate_credentials(), e está implementado aqui por comodidade.
Implementação da Camada de Acesso a Dados com Class::DBI
Para implementar o modelo, vamos precisar de ajuda de mais um padrão de projetos, o Data Access Object, ou DAO, para os íntimos.
Este padrão de projeto consiste em manter objetos separados para cuidar das responsabilidades específicas de manipular e armazenar os dados. Fazemos isso para que, quando precisarmos alterar a forma ou o local de armanzenamento das informações, não precisemos alterar nossos processo de manipulação de dados, apenas o processo de armanzenamento e recuperação. Isto torna menor a possibilidade de introdução de novos erros em um sistema antigo por mudanças menos importantes para o negócio.
Para implementar o DAO, vamos utilizar uma biblioteca pronta. Isto é muito melhor que ser obrigado a "reinventar a roda" e construir nós mesmos.
Optamos pela biblioteca Class::DBI. Poderíamos ter optado por outras semelhantes, como a SPOPS, que tem funcionalidade semelhante, mas oferece muitos recursos extras.
Class::DBI deve ser implementada através da extensão da classe Class::DBI, e declaração de alguns métodos estáticos para informar à biblioteca sobre nosso banco de dados, nossas tabelas e nosso método preferido de carga de dados. Adicionalmente, podemos ir além e implementar métodos para desempenhar tarefas mais específicas, como algum tipo de query especial para nossa aplicação, ou outras funcionalidades semelhantes. Mas isso também está fora do nosso escopo de hoje. Vamos ver como deve ser nossa classe-base, devidamente comentada:
=head1 NAME
DAO::Base - Classe base para objetos de acesso a dados
=cut
package DAO::Base;
use strict;
use warnings;
use base qw( Class::DBI );
our $VERSION = sprintf '%d.%02d', q$Revision: 1.3 $ =~ m/(\d+)\.(\d+)/;
=head1 SYNOPSIS
use base qw( DAO::Base );
=head1 DESCRIPTION
Esta é a classe base dos Objetos de Acesso a Dados, derivados de
Class::DBI.
=cut
__PACKAGE__->connection( 'dbi:Pg:dbname',
'dbuser', 'secret',
{ AutoCommit => 1,
RaiseError => 1,
PrinError => 0,
}
);
=head1 AUTHOR
Luis Campos de Carvalho
Engenharia de Software Ltda.
=head1 REVISION
$Revision: 1.3 $
=head1 COPYRIGHT
Copyleft 2005 Engenharia de Software Ltda.
Doado aos São Paulo Perl Mongers.
This module is free software; you can redistribute it and/or modify it
under the term of the GNU Public License.
=head1 SEE ALSO
L<Class::DBI>
Este módulo é composto de algumas declarações interessantes: primeiramente, o nome do pacote package DAO::Base
foi escolhido com finalidade didática. É simples modificar isto quando
for utilizado em um projeto sério (sim, estou acreditando que as
pessoas vão se aproveitar deste código em projetos sérios!).
A
próxima coisa interessante a se notar é relacionada com herança: eu
declarei que este pacote tem como base (e herda funcionalidades de)
Class::DBI, com a asserção use base qw( Class::DBI ); isto é equivalente a dizer BEGIN{ require Class::DBI; push @ISA, 'Class::DBI'; }, mas é bem mais curto e fácil de entender.
Também
é neste módulo que devemos declarar os dados de conexão com nosso banco
de dados, para que ela possa ser compartilhada transparentemente entre
todos os módulos derivados deste. Declaramos nossa conexão utilizando o
método estático Class::DBI::connection(), mas o chamamos com __PACKAGE__->connection().
Isto é uma boa prática de programação orientada à objetos em Perl, já que não podemos assumir qual
o tipo que será retornado para nós na instanciação da classe. A diretriz __PACKAGE__ cuida
sozinha destes detalhes, fazendo a coisa certa por nós. Ela sempre vai
resolver para o nome do pacote correto, não importa o que aconteça.
Para saber mais sobre os parâmetros passados ao banco de dados, por
favor consultem a documentação do DBI.
Depois de declarar nossa conexão com o banco de dados, estamos prontos para extender esta classe e especializá-la para cada uma das tabelas que teremos.
A única tabela que teremos neste sistema é a tabela de usuários. Mas lembrem-se: para extender este sistema para um sistema completo e útil, basta seguir a mesma linha de pensamento que estamos desenvolvendo aqui e continuar implementando as funcionalidades restantes.
Nosso objeto de acesso a dados de usuário será declarado desta forma:
=head1 NAME DAO::Usuario - Objeto de Acesso a Dados da tabela 'usuario'. =cut package DAO::Usuario; use warnings; use strict; use base qw( DAO::Usuario ); our $VERSION = sprintf '%d.%02d', q$Revision: 1.3 $ =~ m/(\d+)\.(\d+)/; =head1 SYNOPSIS use DAO::Usuario; $user = DAO::Usuario->create( ... ); $user = DAO::Usuario->retrieve( ... ); $user = DAO::Usuario->search( ... ); =head1 DESCRIPTION Este é o módulo de acesso a dados da tabela de usuarios do sistema de autenticacao de usuarios desenvolvido para o mini-curso da UNISAL. =cut __PACKAGE__->table( 'usuario' ); __PACKAGE__->columns( All => qw[ id login senha ] ); =head1 AUTHOR Luis Campos de Carvalho Engenharia de Software Ltda. =head1 REVISION $Revision: 1.3 $ =head1 COPYRIGHT Copyleft 2005 Engenharia de Software Ltda. Doado aos São Paulo Perl Mongers. This module is free software; you can redistribute it and/or modify it under the term of the GNU Public License. =head1 SEE ALSO L<Class::DBI>
Reparem que este módulo se parece muito com o outro. Declaramos um pacote no mesmo nível, DAO::Usuario, para modelar nossos usuários. Em seguida, declaramos que herdamos as funcionalidades de DAO::Base, em especial duas muito importantes:
DAO::Base herda de Class::DBI
(lembra?), também nosso módulo herda as funcionalidades implementadas
lá. É este pequeno detalhe que faz com que tudo isso funcione
perfeitamente.As declarações que precisamos fazer aqui são
simples: precisamos declarar qual tabela este módulo acessa, e quais os
campos da tabela que estarão visíveis para nossa aplicação. Não, você
não precisa declarar todos os campos como visíveis. Mas não há como o Class::DBI conhecer (ou utilizar) os campos que você não declarar. Use isto com sabedoria.
Para declarar tabelas, utilizamos a diretriz auto-explicativa __PACKAGE__->table( 'usuario' ). Como todos já perceberam, o único parâmetro passado (o nome da tabela) é obrigatório.
Depois (lembrem-se: depois!) de declarar qual tabela desejamos acessar, podemos então declarar quais colunas desta tabela desejamos que nosso módulo veja. Note que isso implica que podemos ter tabelas com quantos atributos desejar, e podemos controlar quais módulos acessam que colunas através desta declaração.
Para declarar as colunas visíveis, utilizamos __PACKAGE__->columns( All => qw[ id login senha ] ); isto declara três colunas visíveis, e implicitamente declara que a primeira coluna da lista (id) é nossa chave primária.
Com isso, encerramos a parte de construção da camada de acesso a dados. O que temos até este momento são dois objetos simples: DAO::Base e DAO::Usuario.
Por agora, vamos apenas reservar estes módulos. Vamos precisar deles mais adiante.
Implementação das Visões com Template Toolkit
Agora que podemos contar com nossos objetos de acesso a dados, vamos implementar nossas visões. São elas que darão um rosto para nossa aplicação, e por isso mesmo devem ser tratadas com um certo cuidado. Como esta é uma aplicação reutilizável, devemos tomar alguns cuidados extras com a documentação dos templates, para que seja sempre possível a um desenvolvedor adaptá-lo às suas necessidades.
Nossa primeira visão precisa pedir ao usuário que entre com seu nome e senha em duas caixas de texto. Também precisa oferecer a ele um botão que vai nos levar de volta para a aplicação. Nossa segunda visão será utilizada apenas em caso de erros: caso o nome ou senha fornecidos pelo usuário não o identifiquem corretamente, precisamos informar isto a ele. Em caso de autenticação positiva, vamos utilizar um redirecionamento HTTP (código de resposta 302) para indicar ao browser do usuário para onde ir em seguida, e ao mesmo tempo transmitir a este programa as credenciais do usuário que ele conduz.
Com algum esforço de desenvolvimento, construimos a primeira visão, que vamos chamar de ask-credentials.tmpl. Ela se parece muito com isso:
O código fonte HTML utilizado para gerar isto é assim:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title></title>
<meta name="AUTHOR" content="Luis 'Champs' de Carvalho">
<style type="text/css">
form { font-family: monospace;
border: thin solid gray;
position: absolute;
top: 45%; left: 60%;
padding-left: 5%;
padding-right: 5%;
background-color: lightyellow;
}
form p:first-child { font-weight: bold; }
form p.submit { text-align: right; }
</style>
</head>
<body>
<form action="[% action %]" method="POST"
enctype="application/x-www-form-urlencoded">
<p>Entrada do Sistema</p>
<p>Login:
<input type="text" name="login"
size="16" tabindex="1" align="middle">
</p>
<p>Senha:
<input type="password" name="passwd"
size="16" tabindex="2" align="middle">
</p>
<p class="submit">
<input type="submit" name="mode" value="Login" tabindex="3">
</p>
</form>
</body>
</html>
Preste atenção a alguns pequenos detalhes: os nomes
dos campos que estamos utilizando são importantes. Vamos conferir: o
campo onde o usuário escreverá seu login chama-se login; o campo onde escreverá sua senha é especial (do tipo password, e chama-se passwd; e finalmente, o botão de envio chama-se mode. Isto é muito importante, pois é através deste campo que a aplicação determinará qual deve ser o próximo passo.
Note também que preenchemos o atributo action do tag form com uma espécie de tag
especial. Isto é um comando para o Template Toolkit. Vamos voltar a
estes detalhes em breve. Por enquanto, basta prestar atenção nisso.
Precisamos ainda de mais uma visão, para informar o usuário que ele não possui credenciais válidas. Ele deve ser assim:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<!-- $Id: 20050924.html,v 1.3 2005/09/24 02:10:55 champs Exp $ -->
<head>
<title>Erro na Autenticação!</title>
<meta name="AUTHOR" content="Luis 'Champs' de Carvalho">
<style type="text/css">
p.error { border: thin solid black;
background-color: red;
font-weight: bold;
text-align: center;
position: absolute;
left: 35%;
top: 35%;
}
</style>
</head>
<body>
<p class="error">[% error_message %]</p>
</body>
</html>
Note que voltamos a utilizar um daqueles comandos especiais para o Template Toolkit. Vamos falar sobre isso em alguns instantes. O importante agora é que isso liqüida com o assunto das visões, e nos permite começar a explicar como deve ser construío o núcleo da aplicação.
Introdução ao conceito de Stash
Stash é simplesmente um conceito. É um repositório de dados semi-público, que utilizamos para acumular informações sobre a requisição que estamos processando. Eu digo semi-público por que não podemos acessar o stash de uma requisição exceto se estivermos processando esta requisição.
Vamos implementar nosso stash com um design pattern chamado Singleton, que no nosso caso servirá para garantir que todas as referências para o stash sejam processadas pelo mesmo stash, dentro de uma determinada requisição.
Nosso método stash() é implementado automaticamente pela biblioteca Class::Accessor, que cria automaticamente métodos de acesso a estruturas de dados auxiliares, com declarações simples como "__PACKAGE__->mk_accessors( qw[ stash ] );".
Introdução ao Class::Accessor
Acabamos de falar que alguns métodos são gerados automáticamente pela biblioteca Class::Accessor. Por favor não se assustem. Gerar código em Perl em tempo de execução é uma coisa normal e até esperada. Esta é uma das principais vantagens e desvantagens das linguagens de programação interpretadas em tempo de execução: todas elas permitem que você execute trechos de código que acabou de gerar.
Para tornar curta uma história bem longa, o importante aqui é o seguinte: ao invéz de codificarmos um método assim:
sub stash{
my $self = shift;
my $new_stash = shift;
return $new_stash? $self->{stash} = $new_stash : $self->{stash};
}
Simplesmente solicitamos para a biblioteca Class::Accessor que gere um método para nós com esta "forma". Todos os métodos gerados fazem a mesma coisa (permitem acesso a um objeto ou estrutura de dados conhecido pelo objeto que estamos implementando), mas não precisam ser escritos. Isto economiza tempo e faz os programas mais simples de ler.
Introdução ao AppConfig
Esta biblioteca é uma das mais poderosas ferramentas de configuração de sistemas que eu conheço.
Entre as funcionalidades mais interessantes do AppConfig,
vou destacar duas que vamos utilizar bastante. A primeira é a
facilidade de verificação de consistência dos arquivos de configuração
processados: basta declarar quais palavras-chave são aceitas e quantos
argumentos elas podem ter para se obter este resultado. Por exemplo,
para aceitar como diretriz de configuração qualquer palavra-chave com
apenas um argumento associado, passamos ao construtor de nosso AppConfig os argumentos
my $cfg = new AppConfig
( { CASE => 1,
PEDANTIC => 1,
CREATE => 1,
GLOBAL => { EXPAND => 1,
ARGCOUNT => ARGCOUNT_ONE,
},
}
);
que essencialmente dizem "diferencie maiúsculas de minúsculas" (CASE=>1); "tenha tolerância zero com discrepâncias" (PEDANTIC=>1); "aceite qualquer palavra como diretriz de configuração" (CREATE=>1); "aplique expansão de variáveis (EXPAND=>1) a todas as diretivas"; e "aceite apenas um argumento associado a cada diretiva" (ARGCOUNT=>ARGCOUNT_ONE).
A segunda funcionalidade que utilizamos é a capacidade de transformar um conjunto de diretivas de configuração em uma estrutura de dados arbitrária, como um hash de hashes, assim:
{ error => { $cfg->varlist( '^error_message_', 1 ) },
template => { $cfg->varlist( '^template_', 1 ) },
};
Em poucas palavras, o método AppConfig::varlist()
retorna todas as diretivas de configuração que baterem com a expressão
regular passada como primeiro argumento em forma de um hash,
associando-as com os seus respectivos valores. Caso o segundo argumento
seja um valor verdadeiro, este método ainda transforma as diretivas,
removendo delas a expressão regular passada como primeiro argumento (de
forma que voc&eˆ não precisa ficar repetindo a string "error_"
sempre que deseja pegar uma mensagem de erro confirgurada pelo usuário).
Introdução ao Template Toolkit
Todo mundo que um dia pensou em programar Perl para a web com seriedade tentou escrever uma biblioteca capaz de gerar código HTML a partir de arquivos contendo a parte "fixa" do código e "substituindo" palavras-chave pré-determinadas por expressões calculadas dinâmicamente em tempo de execução. Um sujeito chamado Andy Wardley pensou nisso e escreveu a biblioteca para realizar este tipo de operação, o Template Toolkit. Trata-se de um interpretador para uma meta-linguagem de programação que pode ser utilizada até mesmo para gerar HTML para nossa aplicação de exemplo. Eu, particularmente, já a utilizei até para fazer manutenção em bancos de dados Oracle.
Para utilizar templates baseados em Template Toolkit em nosso sistema de exemplo, vai nos bastar umas poucas diretivas de configuração e uma instância do objeto Template, o interpretador da meta-linguagem dos templates:
my $options =
{ INCLUDE_PATH => $ROOTDIR.'/'.$self->config->{template}{include_path},
PRE_CHOMP => $self->config->{template}{pre_chomp} || 0,
POST_CHOMP => $self->config->{template}{post_chomp} || 0,
TRIM => $self->config->{template}{trim} || 0,
COMPILE_EXT => $self->config->{template}{compile_ext} || 'c',
COMPILE_DIR => $self->config->{template}{compile_dir} || '/tmp/ttc',
};
# Retorna um template prontinho para usar.
return new Template( $options );
Outra
vez, para tornar mais curta uma história bem longa, as diretivas dizem:
"localize templates dentro do diretório configurado pelo usuário" (INCLUDE_PATH); "remova automaticamente todos os espaços encontrados antes de um valor aplicado sobre o template" (PRE_CHOMP); "remova todos os espaços encontrados depois de um valor aplicado sobre o template" (POST_CHOMP); "remova linhas em branco antes e depois de um valor aplicado sobre o template" (TRIM); "armazene templates pré-compilados com a extensão definida em (COMPILE_EXT)"; e "armazene arquivos de templates pré-compilados no diretório (COMPILE_DIR)".
Depois de obter uma instância do objeto Template, basta utilizá-lo para processar os arquivos contendo código HTML mesclado com meta-código Template Toolkit para obter páginas HTML completas e prontas para enviar ao browser. Vamos falar sobre como fazer isto durante a análise do código fonte, mais adiante.
Introdução ao CGI::Application
Toda aplicação para a world-wide-web executa basicamente um ciclo de processamento idêntico para atender cada requisição enviada para ela:
Bem, não é preciso dizer que este processo, ao se misturar com o código da nossa aplicação, potencialmente causará problemas de manutenção do código já existente (como alterar, de maneira indesejada, trechos do código dependentes da funcionalidade que efetivamente precisa ser alterada).
Para evitar que este seja o destino de seus aplicativos para a web, alguns programadores criaram suas próprias bibliotecas de implementação de processamento de requisições, que realizavam as tarefas listadas de uma forma abstrata, permitindo a eles extender o código para implementar a lógica de negócio de seus programas separadamente, de uma forma mais organizada e simples de entender. Foi assim que nasceu a biblioteca CGI::Application, a mais veloz e flexível de todas as bibliotecas de processamento de requisições disponíve para a linguagem Perl. Ela será a base de nosso código fonte, nos permitindo concentrar nas particularidades que nos interessam, sem precisar nos preocupar com coisas menores, como saber qual a próxima ação que devemos tomar para continuar processando uma requisição enviada por um usuário.
Agora vamos falar brevemente do funcionamento da biblioteca CGI::Application. Esta biblioteca orientada a objeto é um framework que executa ciclicamente um pequeno algorítmo para determinar qual o próximo run-mode (ou estado) que a aplicação deve utilizar, a partir das informações enviadas juntamente com a requisição HTTP. Em seguida, executa um método especificado pelo usuário em uma lista de possíveis ações (declarada no estágio anterior). A página HTML dinâmica deve ser gerada neste ponto, ou, pelo menos, todo o seu conteúdo deve ser calculado aqui. Finalmente, o CGI::Application executa um processo breve e simples de finalização antes de enviar o resultado final ao usuário. Vamos aos métodos chamados em ordem:
CGI::Application::new()
- chamado para incializar a aplicação e criar a infra-estrutura de
dados básica necessária ao processo. Retorna um objeto do tipo CGI::Application, que deve ser utilizado para chamar o método CGI::Application::run().CGI::Application::run()
- segundo método a ser chamado pela aplicação, e o último que o usuário
deve chamar diretamente. Este método executa nossa aplicação
propriamente dita, e imprime o conteúdo que desejamos enviar ao browser
juntamente com os cabeçalhos adequados. Este método chama uma série de
métodos auxiliares e "ganchos" que podemos sobrecarregar com código
personalizado para nossa aplicação:CGI::Application::cgiapp_init() - chamado pelo CGI::Application::run()
logo depois que alguns pequenos processos de inicialização acontecem.
Este método pode ser sobrecarregado para preparar os recursos que vamos
utilizar para gerar conteúdo dinâmico.CGI::Application::setup() - método sobrecarregado para configuração de nossa aplicação. De acordo com a documentação do CGI::Application, é sobrecarregando este método que devemos chamar os métodos CGI::Application::mode_param(),
utilizado para configurar a biblioteca com o parâmetro da requisição
que deve conter o nome do nosso próximo estado de execuçl;ão,
e o método CGI::Application::run_modes(), que inicializa a lista de ações que nossa aplicação pode executar. Finalmente, somos obrigados a chamar o método CGI::Application::start_mode(), responsável por inicializar o módo inicial da aplicação (acionado caso o parâmetro inicado em mode_param() esteja vazio).CGI::Application::cgiapp_prerun() - chamado pelo CGI::Application::run()
imediatamente antes do método que implementa a ação que deve ser
executada para gerar a página dinâmica. Aqui, podemos preparar
informações auxiliares para nossa aplicação, entre outras coisas.CGI::Application::${action}
- neste momento, muitos métodos diferentes podem ser chamados, a maior
parte deles implementados pelo desenvolvedor do aplicativo. Cada um dos
métodos que pode ser chamado aqui deve ser mencionado na lista de
métodos passada para a biblioteca pelo método run_modes() (durante a execução do método setup(),
acima). Estes métodos implementam as ações que o desenvolvedor deseja
que esta aplicação implemente. Este método precisa retornar um escalar
contendo o conteúdo gerado para a aplicação.CGI::Application::cgiapp_postrun()
- executado imediatamente depois da geração de conteúdo (implementada
pelos métodos-ação, acima), este método pode desempenhar o papel de
"filtro" de conteúdo, tratando o resultado da ação. Por exemplo, vamos
utilizar este método para processar nossso Templates e gerar o conteúdo
HTML apenas aqui.Agora que temos uma vaga idéia de como deve fucnionar nossa aplicação, vamos analisar o código completo:
Implementação do Controlador com CGI::Application
Lembram-se de que falei sobre o padrão de projeto MVC, há pouco? Pois bem, vamos implementar o "C" do MVC, nosso controlador de processo. Ele será baseado na biblioteca CGI::Application, e será assim:
package Auth::Base;
use warnings;
use strict;
use base qw( CGI::Application Class::Accessor );
use Auth::RootDir qw( $ROOTDIR );
use AppConfig qw( :argcount );
use CGI::Cookie;
use CGI::Session;
use Template;
=head1 NAME
Auth::Base - Classe base da aplicação de autenticação de usuários.
=head1 SYNPOSIS
use base qw( Auth::Base );
=head1 DESCRIPTION
Classe base para o framework MVC utilizado para o desenvolvimento de
uma aplicação de autenticação de usuários didática.
=head1 ATTRIBUTES
=over 4
=item config
Campo para manter o nosso objeto de configuração. Para esta aplicação,
vamos utilizar um L<AppConfig>, by Andy Wardley, o meu favorito.
=item template
Campo para manter o nosso objeto de processamento de templates. Isto
ajuda muito a gerar HTML na saída. Felizmente, a maior parte dos
mortais pode simplesmente utilizar este framework e esquecer que isso
existe. Vamos usar um objeto L<Template>, do Template Toolkit, by Andy
Wardley. Este também é um dos meus módulos favoritos.
=item stash
Um Stash é nada mais, nada menos que um conceito. Um stash é um
"contexto", um conjunto de variáveis logicamente relacionadas e
agrupadas com a finalidade de facilitar seu uso e manutenção. Nada
mais de convenções de chamadas de métodos, ou de contagem de
argumentos para saber se todos os parâmetros foram
passados. Simplesmente inclua os dados relevantes no stash, baseados
na chave correta, e permita que o método interessado os utilize quando
e como bem entender.
O stash é inicializado a cada nova requisição com as informações
provenientes dela, e, depois do processamento, é passado na íntegra
para o objeto Template, que disponibilizará as informações contidas
nele para o template que será processado para a geração de conteúdo.
=item session
Método de acesso à sessão de um usuário.
=back
=cut
__PACKAGE__->mk_accessors( qw[ config template stash session ] );
=head1 METHODS
=over 4
=item init_config
Inicializa e carrega o subsistema de configuração.
Utilizamos um objeto AppConfig para interpretar o nosso arquivo de
configuração, e simplemente mantemos toda a configuração de que
precisamos em memória em tempo integral. Desta forma, economizamos
recursos mais escassos que memória RAM, Pois não precisamos mais ler e
interpretar o arquivo de configuração.
=cut
sub init_config{
my $self = shift;
my $cfg = new AppConfig
( { CASE => 1,
PEDANTIC => 1,
CREATE => 1,
GLOBAL => { EXPAND => 1,
ARGCOUNT => ARGCOUNT_ONE,
},
}
);
$cfg->file( $ROOTDIR.'/etc/auth-app.cfg' );
return { error => { $cfg->varlist( '^error_message_', 1 ) },
template => { $cfg->varlist( '^template_', 1 ) },
session => { $cfg->varlist( '^session_', 1 ) },
application => { $cfg->valist( '^application_', 1 ) },
};
}
=item init_template
Inicializa e instancia um objeto Template, interpretador de scripts de
template do Template Toolkit. Um único objeto Template dá conta de
interpretar e fazer funcionar todas as páginas dinâmicas baseadas em
templates da nossa aplicação.
=cut
sub init_template{
my $self = shift;
# inicializa o template engine, baseado na configuração incorporada.
my $options =
{ INCLUDE_PATH => $ROOTDIR.'/'.$self->config->{template}{include_path},
PRE_CHOMP => $self->config->{template}{pre_chomp} || 0,
POST_CHOMP => $self->config->{template}{post_chomp} || 0,
TRIM => $self->config->{template}{trim} || 0,
COMPILE_EXT => $self->config->{template}{compile_ext} || 'c',
COMPILE_DIR => $self->config->{template}{compile_dir} || '/tmp/ttc',
};
# Retorna um template prontinho para usar.
return new Template( $options );
}
=item init_session
Inicializa e instancia um objeto CGI::Session, cofigurado de acordo
com os parametros encontrados sob a chave "session" no arquivo de
configuração, e o disponibiliza sob a forma de um singleton para a
aplicação.
=cut
sub init_session{
my $self = shift;
my $driver = $self->config->{session}{driver} || 'file';
my $serializer = $self->config->{session}{serializer} || 'default';
my $id = $self->config->{session}{id_generator} || 'md5';
my $session = new CGI::Session
( "driver: $driver; serializer: $serializer; id: $id",
$self->query,
undef
);
unless( $session ){
# Situação complicada, mas possível: aconteceu um erro no setup da
# sessão, e temos de escolher entre continuar sem sessões ou parar
# por aqui. Eu optei por parar pois a aplicação conta com sessões
# para verificar a validade de credenciais obtidas pelos usuários
# na sua autenticação.
use CGI::Carp qw/ confess fatalsToBrowser /;
confess CGI::Session::errstr();
}
# acrescenta um tempo para expiração da sessão.
# ATENÇÃO: Este não é o tempo de expiração da credencial obtida pelo
# usuário ao se autenticar na aplicação!
$session->expire( $self->config->{session}{expire_time} || '+10m' );
return $session;
}
=item save_session()
Responsável pela persistência da sessão de um usuário e pelo envio do
cookie com a chave de identificação da sessão para o usuário.
=cut
sub save_session{
my $self = shift;
$self->header_add( -cookie => CGI::Cookie->new
( -name => $session->name,
-value => $session->id )
);
}
=item validate_credentials()
Método responsável por fiscalizar as credenciais dos usuários quando
da sua passagem por algum dos módulos da aplicação. Redireciona
automaticamente os usuários sem credenciais válidas para a página de
autenticação de usuários.
Deve ser chamado pela implementação de cgiapp_prerun, que é o único
local de onde podemos ter acesso à funcionalidade do CGI::Application
capaz de alterar o run-mode atual e permitir ao nosso método alterar a
linha de processamento.
=cut
sub validate_credentials{
my $self = shift;
my $session = $self->session;
my $param = $self->config->{session}{is_active_param_name} || 'active';
unless( $session->param( $param ) ){
# isto indica que o usuário não deveria estar navegando por aqui.
# vamos enviá-lo para nossa página de login, que é onde ele
# deveria navegar agora.
$self->prerun_mode( 'redirect' );
$self->stash->{redirect_to_url} = $self->config->{session}{auth_url};
}else{
# Caso as credenciais do usuário ainda estejam boas, continuamos
# normalmente, simplesmente acrescentando mais algum tempo à
# validade da sessão do usuário, para que ele continue autorizado
# a acessar a página.
$session->expire( $param,
$self->config->{session}{active_param_add_time}
);
}
}
=item cgiapp_init
Sobrecarga de método do CGI::Application para inicialização da nossa
aplicação. Este método é chamado automaticamente pelo CGI::Application
quando da inicialização do objeto de aplicação.
=cut
sub cgiapp_init{
my $self = shift;
$self->config( $self->init_config );
$self->template( $self->init_template );
$self->session( $self->init_session );
}
=item setup
Método sobrecarregado do objeto CGI::Application para permitir a
configuração do nosso objeto de aplicação.
Este método é chamado sempre que um novo objeto de aplicação é criado,
logo depois de cgiapp_init(), mas antes de cgiapp_prerun() / run() /
cgiapp_postrun().
&EACUTE; aqui que estabelecemos valores e comportamento default para nossa
aplicação.
=cut
sub setup{
my $self = shift;
$self->mode_param( 'mode' );
$self->start_mode( 'action' );
$self->run_modes( action => 'action' );
}
=item init_stash
Stash é o conceito de que tudo o que está relacionado com os
diferentes métodos da aplicação deve permanecer junto. Por isso,
preparamos uma referência para um hash, que inicializamos com os
diversos parâmetros provenientes da requisição e o disponibilizamos
para o método de geração de conteúdo. Assim, o método de geração de
conteúdo simplesmente não precisa mais se preocupar com a proveniência
ou o destino dos parâmetros iniciais ou do conteúdo gerado. Basta que
se posicione estas coisas em seus devidos lugares no "stash" e passar
o controle para o próximo método da nossa cadeia de responsabilidades.
=cut
sub init_stash{
my $self = shift;
# inicializa uma referência de hash caso não tenhamos uma...
my $stash = $self->stash || {};
# Recupera uma referência para um tied hash contendo os parâmetros
# passados para o script na requisição.
my $vars = $self->query->Vars;
# copia organizadamente estes parâmetros, tomando o cuidado de
# expandir novamente as listas de valores que alguns deles podem
# conter.
map { $stash->{$_} = $vars->{$_} =~ /\0/?
[ split qr/\0/, $vars->{$_} ] : $vars->{$_};
} keys %$vars;
# inicializa nosso stash em sua posição definitiva.
$self->stash( $stash );
}
=item cgiapp_prerun
Chamado automaticamente pelo CGI::Application a cada nova requisição,
antes que o método associado ao modo de operação determinado por
CGI::Application::run() tenha chance de rodar.
Vamos utilizar este "gancho" para inicializar o Stash de nossa
aplicação corretamente, de modo a permitir que os métodos geradores de
conteúdo ignorem a proveniência dos parâmetros utilizados em cada
requisição. Tudo o que precisam estará no Stash.
=cut
sub cgiapp_prerun{
my $self = shift;
# Prepara o stash que vai ser utilizado nesta requisição.
$self->init_stash;
return $self;
}
=item cgiapp_postrun
Chamado automaticamente pelo CGI::Application a cada nova requisição,
logo depois que o método de geração de conteúdo terminar de rodar.
Vamos utilizar este método para gerar efetivamente o conteúdo, já que
os métodos geradores de conteúdo apenas realizam seu processamento
interno e colocam todas as informações necessárias para a geração de
conteúdo nas variáveis definidas no Stash, juntamente com uma variável
especial chamada "template". Esta chave indica qual o template que
devemos passar ao Template Toolkit para obter o conteúdo que
desejamos.
Depois de rodar, este método substitui o que quer que tenha sido
gerado como "conteúdo" pelo método associado com o run-mode atual e
permite ao CGI::Application enviar corretamente o conteúdo gerado ao
browser.
=cut
sub cgiapp_postrun{
my $self = shift;
my $output;
my $out = shift;
if( UNIVERSAL::isa( $self->template, 'Template' ) ){
$self->template->process( $self->stash->{template},
$self->stash,
\$output
)
or $self->template->process( $self->config->{template}{error},
{ %{$self->stash},
error => $self->template->error()
},
\$output
);
}else{
$output = $self->config->{error}{template_not_defined};
$self->header_type( 'header' );
$self->header_add( type => 'text/plain' );
}
$$out = $output;
}
=item action
Finalmente, mas não menos importante, implementamos a "ação default"
de um módulo (ou seja, um erro, já que este módulo é considerado
"abstrato" e não deveria ser instanciado ou rodado como um módulo
qualquer...).
=cut
sub action{
my $self = shift;
$self->stash->{template} = $self->config->{template}{error};
$self->stash->{error_message}
= $self->config->{error}{action_not_defined_for_this_object};
}
=back
=head1 AUTHOR
Luis Campos de Carvalho - Engenharia de Software Ltda.
=head1 REVISION
$Revision: 1.3 $
=head1 COPYRIGHT
Copyleft 2005 Engenharia de Software Ltda.
Doado aos São Paulo Perl Mongers.
This module is free software; you can redistribute it and/or modify it
under the term of the GNU Public License.
=head1 SEE ALSO
L<CGI::Application>
=cut
1;
Como utilizar o Controlador implementado
Reutilizar código é a intenção principal. Para utilizar o Auth::Base, basta extender a classe, com uma declaração use base qw( Auth::Base ) no início da definição de sua classe, e implementar o método setup(), juntamente com suas ações específicas, como no exemplo abaixo:
package Auth::User;
use warnings;
use strict;
use lib qw( .. );
use base qw( Auth::Base );
our $VERSION = sprintf '%d.%02d', q$Revision: 1.3 $ =~ m/(\d+)\.(\d+)/;
=head1 NAME
Auth::User - Classe de implementação de autenticação de usuários em
banco de dados utilizando o framework disponibilizado por Auth::Base.
=head1 SYNOPSIS
use Auth::User;
my $authapp = new Auth::User;
$authapp->run;
__END__
=head1 DESCRIPTION
Esta classe implementa uma aplicação web para verificação de
autenticidade de usuários. Para isso, vamos coletar as credenciais de
um usuário e validá-las contra um banco de dados de credenciais.
=head1 METHODS
=over 4
=item setup()
Configuração da aplicação. Neste método inicializamos a configuração
da aplicação, e preparamos tudo o que for necessário para a validação
de credenciais.
Este método sobrecarrega o método homônimo em Auth::Base, que chamamos
apenas para completar nossa configuração, com SUPER::setup().
=cut
sub setup{
my $self = shift;
# primeira coisa a fazer: permitir ao Auth::Base se configurar.
$self->SUPER::setup();
# Agora vamos nos configurar:
# Primeiro, vamos informar ao CGI::Application qual o nome da nossa
# ação "default".
$self->start_mode( 'credentials' );
# em seguida, vamos passar o nome de todas as ações possívels.
$self->run_modes( qw[ credentials Login ] );
}
=item ask_credentials()
Método que implementa a ação que obtém as credenciais de um usuário.
Esta é a ação padrão do sistema.
=cut
sub credentials{
my $self = shift;
# para obter as credenciais de um usuário, precisamos enviar uma
# página para ele. Já construimos um template para isso, basta agora
# dizer qual o template e deixar que o resto da nossa aplicação
# envie a página pronta para o usuário.
# configuramos o template que desejamos...
$self->stash->{template} = $self->config->{template}{login};
# configuramos a url para onde esta requisição deve ir...
$self->stash->{action} =
$self->query->url( -full=>1, -query=>0, -path_info=>0 );
# enviamos tudo para o Auth::Base::cgiapp_postrun(), que faz a
# transformação em HTML.
return 1;
}
=item Login()
Método para implementar a ação de verificar as credenciais de um
usuário e ofercer a ele um "passe-livre" para o restante de nosso
sistema.
Note que este método tem um nome pouco usual: ele é utilizado
diretamente como "etiqueta" do botão que envia a requisição de
credenciais da página de coleta de informações. Há outros meios de se
implementar isso, mas este é o mais curioso, e foi escolhido
exatamente pelo seu valor "cultural". :-)
=cut
sub Login{
my $self = shift;
# Este método utiliza nossas facilidades de Acesso a Dados,
# implementadas nas classes DAO::Base e DAO::Usuario para obter as
# informações que desejamos sobre um determinado usuário.
# É baseado
# nestas informações e nos valores enviados juntamente com a
# requisição HTTP que vamos tomar a decisão de permitir ou negar a
# permissão de acesso solicitada.
use DAO::Usuario;
my $user;
eval{ # recuperar informações de um banco de dados é considerado uma
# operação perigosa (podemos gerar excessões). Por isso
# precisamos proteger nosso código com um bloco "eval".
$user = DAO::Usuario
->search( login => $self->stash->{login},
senha => $self->stash->{passwd}
);
};
if( $@ ){
# Caso aconteçam erros durante a nossa tentativa de acesso aos
# dados, vamos tratá-los aqui. Tratamento adequado de erros em
# aplicativos para a web não faz parte do escopo deste projeto, e
# por isso nosso tratamento será de qualidade insatisfatória.
$self->stash->{template} = $self->config->{template}{error};
$self->stash->{error_message}
= $self->config->{error}{database_error}." $@.";
}
# Neste ponto, temos as informações recuperadas do banco de dados
# sobre um usuário, e podemos utilizá-las para validar suas
# credenciais.
# Não deve ser complicado de notar, entretanto, que já utilizamos em
# nossa validação todas as informações que
# possuíamos ao fazer busca
# e recuperação de dados de usuário no banco de dados.
# Para este momento, resta apenas saber se o usuário existe (temos
# um registro em mãos ou apenas um valor undef()?).
if( $user->login =~ /\w+/ && $user->senha ){
# Se temos um nome de usuário não-vazio e uma senha diferente de
# undef(), então este sujeito apresentou credenciais válidas
# (conseguimos encontrá-las em nosso banco de dados) e devemos
# permitir a ele que acesse a aplicação.
# 1. Inicializa o parâmetro da sessão para um valor /true/, para
# indicar que o usuário está corretamente autenticado.
$self->session
->param( $self->config->{session}{is_active_param_name} => 1 );
# 2. Configura a sessão para expirar o parametro da sessão em um
# tempo determinado na configuração do sistema.
# Este tempo será automaticamente renovado quando da verificação
# das credenciais, mais tarde.
$self->session
->expire( $self->config->{session}{is_active_param_name},
$self->config->{session}{active_param_expire_time}
);
# 3. Aciona o mecanismo de redireção para encaminhar o usuário
# para dentro da nossa aplicação.
$self->header_type( 'redirect' );
$self->header_props( -url => $self->config->{application}{main_page} );
}else{
# Se não temos um nome de usuário e senha,
# então este usuário não
# está no banco de dados, e não deve ser autorizado a ter acesso
# ao nosso aplicativo.
$self->stash->{template} = $self->config->{template}{error};
$self->stash->{error_message}
= $self->config->{error_message}{invalid_user_or_passwd};
}
return 1;
}
=back
=head1 AUTHOR
Luis Campos de Carvalho Engenharia de Software Ltda.
=head1 REVISION
$Revision: 1.3 $
=head1 COPYRIGHT
Copyleft 2005 Engenharia de Software Ltda.
Doado aos São Paulo Perl Mongers.
This module is free software; you can redistribute it and/or modify
it under the term of the GNU Public License.
=cut
1;
Uma breve análise deste código:
Sobrecarregamos Auth::Base::setup()
para configurar a nossa aplicação de acordo com nossas conveniências.
Caso existam configurações adicionais a acrescentar (de um arquivo de
configuração em separado, ou específico desta aplicação) podemos
carregá-lo aqui, ou sobrecarregando os métodos Auth::Base::cgiapp_init(), Auth::Base::cgiapp_prerun() e Auth::Base::cgiapp_postrun. Lembre-se que, se você fizer isso, precisa permitir aos métodos originais uma chance de fazer seus trabalhos, chamando SUPER::,
para que o Perl saiba que ele deve rodar o método em questão da
classe-pai e não o que você sobrecarregou em sua classe derivada.
Depois
de inicializar e configurar adequadamente nossa aplicação,
implementamos os métodos necessários para nossas ações (neste caso, Auth::User::credentials() e Auth::User::Login()).
Finalmente, precisamos compreender como fazer para que nossa aplicação inicialize corretamente em um programa CGI (existem outras possibilidades mais sólidas e mais velozes, veja a seção de referências no final do artigo) e seja executada quando da recepção de uma requisição.
Como implementar a página dinâmica (CGI)
Para
fazer com que nossa aplicação rode como um script CGI (lembre-se: esta
não é a única possibilidade), precisamos
implementar um pequeno script CGI que faça o "trabalho sujo" de
instanciar nossa aplicação e executar nosso método run(). Uma possibilidade é implementar assim:
#!/usr/bin/perl use warnings; use strict; use lib qw( ../lib ); use Auth::User; ( new Auth::User )->run(); __END__
Construindo mais páginas com o mesmo controlador
Para continuar escrevendo esta (e outras!) aplicação, tudo o que precisamos fazer é extender Auth::Base
de modo a permitir que outros módulos de aplicação sejam implementados.
Interligando aplicativos
Interligar aplicações rodando em um mesmo servidor (ou servidores diferentes, mas sob um mesmo domínio DNS) é apenas uma questão de implementar todas estas aplicações utilizando sempre os mesmos cookies para passagem de mensagens e credenciais de usuário. Todo o restante pode ser obtido do servidor de autenticação ou de um banco de dados, simplesmente configurando adequadamente os serviços envolvidos.
Uma palavra de cautela
Lembre-se do planejamento, do cuidado com a implementação e da necessidade de testes e documentação! Sem estas coisas, suas chances de escrever sua aplicação são tão boas quanto às de que um macaco com uma máquina de escrever consiga datilografar a Ilíada, de Homero.
Créditos
Este artigo foi escrito por Luis Campos de Carvalho (também conhecido como "Monsieur Champs"). Champs é líder dos São Paulo Perl Monks, empresário e um fanático por Perl e bancos de dados Oracle.
Contato: monsieur [underscore] champs [at] yahoo [ponto] com [ponto ] br
A revisão deste artigo ficou por conta de Alceu Rodrigues de Freitas Junior (glasswalk3r [at] yahoo[ponto]com[ponto]br).
Licensa de Uso e Distribuição
Este artigo é um software livre; você pode redistribuí-lo e/ou modificá-lo dentro dos termos da Licença Pública Geral GNU como publicada pela Fundação do Software Livre (FSF); na versão 2 da Licença, ou (na sua opnião) qualquer versão. Este artigo é distribuído na esperança que possa ser útil, mas SEM NENHUMA GARANTIA; sem uma garantia implicita de ADEQUAÇÃO a qualquer MERCADO ou APLICAÇÃO EM PARTICULAR. Veja a Licença Pública Geral GNU para maiores detalhes. Você deve ter recebido uma cópia da Licença Pública Geral GNU junto com este programa. Se não, escreva para a Fundação do Software Livre(FSF) Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA