%BEGINLATEX%

Principes de base de MVC et du développement Web rapide avec Catalyst


Revision: r10 - 22 Nov 2012 - 13:55:13 - MarcSCHAEFER

Principes de MVC. Framework Catalyst. Développement rapide.Templates. Exemples participatifs. CRUD. ORM.

%STOPLATEX% %STOPPUBLISH%

Start presentation

%STARTPUBLISH%

%STARTLATEX%

Slide 1: Cette présentation

Principes de base de MVC et du développement Web rapide avec Catalyst


Marc SCHAEFER
HE-Arc Ingénierie / ISIC

Slide 2: Vers MVC

La création d'applications Web modernes a des nouveaux besoins

  • intégration de designs avancés

  • affichage sur plusieurs types de terminaux (PC, mobile, ...)

  • intégration de toolkits pour des fonctions riches (Web 2.0): AJAX (JQuery, Google API, ...)

  • sécurité, réutilisabilité du code

  • rapidité de développement

  • évolution vers l'approche service REST

framework == bibliothèque standard + conventions et structuration + inversion du flot + comportement par défaut

Slide 3: Principes des frameworks de type MVC

  • indépendance de la présentation (View)

  • séparation logique métier (Model) de l'agencement des flux

  • contrôleur léger/lourd (fat controller)

  • fonctions standards intégrées
    • validation et échappement des données (sécurité, transparence)
    • sessions et contexte (suivi)
    • routage d'URL géré globalement
    • contrôle d'accès global
    • centralisation de la configuration
    • test unitaire
    • déploiement

Slide 4: Motivation du choix du framework Catalyst (1)

  • langage Perl
    • langage universel (non spécifique au Web)
    • multiplateforme (préinstallé sur les systèmes GNU/Linux)
    • framework orienté objet post-moderne Moose
    • large bibliothèque de logiciels (CPAN)
    • licence GPL ou Artistic
    • implémenté en C
    • large communauté, assez populaire (suivant les classements, arrive proche de Python et PHP, pas loin de C++ et toujours avant Ruby)

Slide 5: Motivation du choix du framework Catalyst (2)

  • framework Catalyst
    • framework mi-lourd, basé sur l'intégration de nombreuses bibliothèques existantes
      • ORM (Model): DBIx (p.ex.)
      • templating (View): Template Toolkit (p.ex.)
    • certaines fonctions sont implémentées par des extensions du langage Perl (p.ex. le routage d'URLs)
    • à peu près la seule implémentation Perl complète d'un framework MVC
    • grande communauté

  • alternatives similaires (MVC)
    • Ruby (on Rails)
    • PHP (p.ex. Synfony)
    • Java Struts (ou quelque chose de plus récent)

Comments

Un travail de semestre a été effectué à la HE Arc ingénierie, comparant 3 frameworks Web MVC (G. Schindelholz, 12INF-TP206).

Même si certains aspects de ces frameworks sont difficilement comparables (p.ex. leurs outils de templating et d'ORM sont différents, leur système de déploiement et de packaging également), leurs principes de base sont similaires.

La comparaison donne en très grand résumé les résultats suivants:

framework hébergement / déploiement performance communauté, popularité choix de BD documentation tutoriels, exemples modules
PHP Symfony X   X   X X  
Ruby on Rails     X X   X X
Perl Catalyst   X   X X   X

Slide 6: Exemple: création d'une application Catalyst Hello

pour l'exemple, depuis le GUI de la machine virtuelle Catalyst proposée:

  • lancer un terminal: ALT-F2, puis xterm

  • créer un répertoire de travail: mkdir work

  • entrer dans ce répertoire: cd work

création d'une application Catalyst Hello
  • catalyst.pl Hello
  • cd Hello

Slide 7: Découverte de l'application simpliste Hello

  • promenez-vous dans les fichiers générés par le framework et identifiez ce qui est du domaine de la configuration, de la gestion de version, de scripts prégénérés, du test, du code, et des données statiques.
    • p.ex. avec nautilus . &

  • identifiez dans lib/Hello les 3 bibliothèques de composants MVC
    • la plupart sont vides pour le moment, mais consultez lib/Hello/Controller/Root.pm

  • en revenant à la racine de l'application Hello, lancez-la en observant les messages puis testez-la en observant à nouveau les messages
    • script/hello_server.pl -r (serveur interne rapide)
    • sur la machine réelle allez à l'URL http://IP:3000/ (avec IP l'adresse IP de la VM)
      • vous devriez voir l'exécution de la requête, son routage et le résultat dans le client WWW

  • à partir de là, il vaut la peine de lancer un nouvel xterm comme précédemment et d'y travailler.

Slide 8: Routage d'URL

  • actuellement Root.pm définit les méthodes index, default et end
    • index: correspond à l'URL relatif /
    • default: exécuté lorsqu'il n'y a pas de routage défini
    • end: triggers d'actions par défaut

  • Catalyst propose plein de façons de faire cela, la plus simple est le qualificateur :Global dans Root.pm

Comments

Il faut ajouter la méthode suivante à lib/Hello/Controller/Root.pm, le plus simple est en double-cliquant depuis Nautilus:
 sub hello :Global {
        my ( $self, $c ) = @_;
    
        $c->response->body("Hello, World!");
    }

On peut sauver, puis directement tester: http://IP:3000/hello

Attention: rien que ce texte est retourné (plus quelques entêtes classiques HTTP). Il manque toute la "glue" HTML.

Slide 9: Indépendance de la présentation (View)

Produire une sortie complexe indépendante du reste de l'application

langages
  • XHTML 1.0
  • HTML 5
  • Javascript
  • e-mail, SVG, LaTeX, PDF, ...

plateformes
  • client WWW classique, embarqué, mobile, ...

solution
  • langage intermédiaire paramétré (templating)
  • exemples: PHP, JSP, Perl::Mason, Template::Toolkit, XSLT XML, ...

Slide 10: Exemple: création d'une vue en Template::Toolkit

création d'une vue nommée HTML, de type TT
  • script/hello_create.pl view HTML TT
  • consulter le fichier lib/Hello/View/HTML.pm et voir ce qu'il définit

création d'un template proprement dit
  • gedit root/hello.tt
  • contenu
    <p>This is a TT view template, called '[% template.name %]'.</p>
    

utilisation de ce template
  • éditer Root.pm, méthode hello()
  • remplacer la définition du body() par
            $c->stash(template => 'hello.tt');
    

test de cette vue

(à nouveau il n'y a pas encore la glue HTML qu'il faudrait dans un cas réel)

Comments

Il y a de la magie interne

  • la méthode end() de Root.pm se charge d'afficher le template, s'il est défini et que l'on n'a pas assigné $c->response- >body()

Slide 11: Template par défaut

But
  • assurer la glue HTML

Idée de Template::Toolkit
  • inclusion de templates via un wrapper

Principes
  • configurabilité du wrapper par variables
  • valeurs par défaut intéressantes

Comments

Tout d'abord créer le wrapper
  • gedit root/wrapper.tt
  • contenu
    [% IF no_wrapper %][% content %][% ELSE %]<?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
       <head>
          <title>[% (title or template.title or "Demo") | html %]</title>
       </head>
       <body>
          [%# Your logo could go here -%]
          <img src="[% c.uri_for('/static/images/btn_88x31_powered.png') %]" alt="Catalyst-powered" />
          [%# Insert the page title -%]
          <h1>[% (title or template.title or site.title) | html %]</h1>
          [%# Status and error messages %]
          <span class="message">[% message | html %]</span>
          <span class="error">[% error | html %]</span>
          <p />
          [%# This is where TT will stick all of your template's contents. -%]
          [% content %]
          <p />
        
          <div id="footer">Copyright &copy; 2012 Demo Developer</div>
          <p /><a href="http://validator.w3.org/check?uri=referer"><img
                  src="http://www.w3.org/Icons/valid-xhtml10-blue"
                  alt="Valid XHTML 1.0 Strict" height="31" width="88" /></a>
       </body>
    </html>
    [% END %]
    
  • ensuite, faire en sorte que Catalyst l'utilise en ajoutant
         WRAPPER => 'wrapper.tt',
    
    dans les paramètres du config() de lib/Hello/View/HTML.pm
  • tester (http://IP:3000/hello/)
  • on peut bien sûr ajouter un CSS de base, des ajouts conditionnels de JQuery, des div correspondant à chacune des régions, etc.

Références

Slide 12: Paramétrisation d'un template

But

A faire
  • modifier votre template hello.tt pour qu'il contienne l'expansion d'une variable du stash, par exemple [% username or "inconnu" %]
  • modifier votre méthode hello() de Root.pm pour obtenir ce paramètre et le stocker dans le stash: la façon la plus générale -- et la moins contrainte -- Catalyst est la suivante:
       sub hello :Global :Args {
            my ($self, $c, %args) = @_;
        
            $c->stash(username => $args{"username"},
                      template => 'hello.tt');
       }
    
  • tester votre template, p.ex. avec (http://IP:3000/hello/username/marc)

Sécurité

Slide 13: Indépendance métier / données: Model

  • interface générique pour accéder des données (CRUD: créer, lire/rechercher, modifier, effacer)

  • par exemple pour des BD relationnelles
    • indépendance du dialecte de chaque BD
    • implémentation de fonctions manquantes éventuelles
    • souvent utilisation d'une correspondance objet-relationnel (ORM)

  • avec un contrôleur léger, l'essentiel du travail est fait dans le modèle (couche métier)

  • Catalyst peut générer les ORM en fonction du schéma de la BD
    • parfois, on peut/doit compléter certains détails (p.ex. relations IS-A)

Slide 14: Exemple: petite base de données et début de CRUD

Cet exemple est simplifié; on ne montrera pas par exemple de liens entre tables (qui sont gérables par l'ORM).

  • création d'une BD SQLite (terminer par CTRL-D)
    sqlite3 hello.db
    CREATE TABLE personne(id INTEGER PRIMARY KEY, nom TEXT NOT NULL, pw TEXT NOT NULL);
    INSERT INTO personne(nom, pw) VALUES('marc', 'demo');
    INSERT INTO personne(nom, pw) VALUES('alex', 'toto');
    SELECT * FROM personne;
    

  • utiliser cette BD dans Catalyst (créer le modèle)
    script/hello_create.pl model DB DBIC::Schema Hello::Schema create=static dbi:SQLite:hello.db
    

Slide 15: Exemple: affichage des données de la BD

Vue d'affichage d'une liste de tuples

Etapes
  • créer le modèle (slide précédent)
  • créer le contrôleur Personne (grouper toutes les fonctions agissant sur une personne)
  • créer un template listant les tuples
  • créer la méthode lister() dans le contrôleur personne, utilisant le modèle et exportant les données vers le template (pour affichage HTML)

Comments

Commençons par créer le contrôleur Personne
script/hello_create.pl controller Personne

Ensuite, définissons un nouveau template permettant de voir la liste des tuples: root/personne/lister.tt (mkdir root/personne avant)
[% IF personnes.size < 1 %]
<p>Aucun utilisateur à afficher</p>
[% ELSE %]
[% cols = [ 'id', 'nom', 'pw' ] %]
<table class="stdlist">
<tr>[% FOREACH h IN cols %]<th>[% h %]</th>[% END %]</tr>
[% # Display each user in a table row %]
[% FOREACH u IN personnes -%]
  <tr>
    [% FOREACH h IN cols %]<td>[% u.$h | html %]</td>[% END %]
    <td>
       <a href="[% c.uri_for(c.controller.action_for('modifier'), [u.id]) %]">E</a><a href="[% c.uri_for(c.controller.action_for('supprimer'), [u.id]) %]">D</a>
    </td>
  </tr>
[% END -%]
</table>
[% END %]

Le template ci-dessus propose déjà des fonctions d'effacement ou de mise à jour, non implémentées, dont les URLs seront générés en fonction de la structure du contrôleur, lorsque les méthodes ad-hoc seront implémentées (plus loin).

Mais tout d'abord, créons la méthode lister() dans le contrôleur Personne, qui correspond à l'URL /lister relativement à /personne:
=head2 lister

=cut

sub lister :Local {
   my ($self, $c) = @_;

   $c->stash(personnes => [ $c->model('DB::Personne')->all ],
             title => 'Liste des personnes',
             template => 'personne/lister.tt');
}

On peut maintenant tester cette fonction avec http://IP:3000/personne/lister

Slide 16: Exemple avancé CRUD: suppression par chaînage

Les méthodes d'effacement et de modification ont besoin de trouver la personne par son ID.

Le chaînage Catalyst permet de regrouper cette fonctionnalité dans le routage d'URL.

Principes
  • on ajoute une dépendance à la déclaration de la méthode
  • on implémente la dépendance
  • Catalyst fait le reste

Par exemple, via un URL générique http://IP:3000/personne/id/ID/operation

Effacement

Comments

Implémentons tout d'abord la méthode qui trouve une personne avec son id:
=head2 object

Cherchons la personne par son id.

=cut

sub object :Chained('base') :PathPart('id') :CaptureArgs(1) {
   my ($self, $c, $id) = @_;

   $c->stash(object => $c->stash->{resultset}->find($id));

   if (!$c->stash->{object}) {
      $c->detach('/error_404');
   }
}

Mais cette méthode a besoin notamment du modèle dans le stash sous resultset. Tout cela peut s'initialiser par la méthode base() qui est d'ailleurs déjà chaînée ci-dessus.
=head2 base

Lier le Model (commun)

=cut

sub base :Chained('/') :PathPart('personne') :CaptureArgs(0) {
   my ($self, $c) = @_;

   # instancier le Model    
   $c->stash(resultset => $c->model('DB::Personne'));
}

Implémentons maintenant la méthode d'effacement
sub supprimer :Chained('object') :PathPart('supprimer') :Args(0) {
   my ($self, $c) = @_;

   $c->stash->{object}->delete;

   $c->stash->{message} = 'Suppression OK';

   $c->detach('lister');
}

L'effacement est une fonction très simple. Pour le moment nous ne demandons pas de confirmation. La confirmation pourrait être ajoutée côté client (Javacript).

Notons que plutôt que de faire un detach(), qui permet de voir l'URL d'effacement -- ce qui n'est pas idéal vu l'effet de bord de la fonction -- une redirection HTTP est recommandée. Dans ce cas, le message de status devrait être passé soit comme paramètre, soit dans la session comme dans l'exemple ci-dessous:
      $c->flash->{message} = 'Effacement effectué';
      $c->res->redirect($c->uri_for("lister"));
      $c->detach;

Ce qui précède suppose qu'une session soit maintenue.

Slide 17: Exemple avancé CRUD: insertion

Etapes

  • utiliser le chaînage pour effectuer l'instanciation du modèle dans base (pas traité ici!)

  • la validation des données peut être automatisée (pas traité ici!)
    • du côté client: Javascript, HTML5 (facultatif)
    • du côté serveur: divers modules Catalyst (obligatoire)

Comments

Tout d'abord, implémentons le bas niveau, soit l'insertion proprement dite, dans le contrôleur Personne. Cette insertion a un effet de bord, c'est pour cela que nous agissons ici sur le cache du client WWW. Une autre possibilité serait d'utiliser un POST, ou d'effectuer une redirection HTTP.
=head2 ajouter

Ajouter une personne

=cut

sub ajouter :Local {
   my ($self, $c, $nom, $pw) = @_;

   my $personne = $c->model('DB::Personne')
                     ->create({ nom => $nom, pw => $pw });

   # Suppression du cache
   $c->response->header('Cache-Control' => 'no-cache');

   $c->stash->{message} = 'Ajout OK';

   $c->detach('lister');
}

Testez avec http://IP:3000/personne/ajouter/NOM/PW

Slide 18: Exemple avancé CRUD: insertion par formulaire

  • si NOM ou PW ne sont pas spécifiés, présenter un formulaire (nouveau template) qui envoie les données via POST sur une action

  • ajouter l'URL d'ajout par exemple dans le template de listage et tester!

Comments

Il suffit d'ajouter le traitement si NOM et PW ne sont pas spécifiés: soit afficher un formulaire d'ajout:
sub ajouter :Local {
   my ($self, $c, $nom, $pw) = @_;

   if (!defined($nom) || !defined($pw)) {
      $c->stash(template => 'personne/ajouter.tt',
                title => 'Ajouter une personne',
                nom => $nom || '',
                pw => $pw || '');
   }
   else {
      my $personne = $c->model('DB::Personne')
                        ->create({ nom => $nom, pw => $pw });

      # Suppression du cache
      $c->response->header('Cache-Control' => 'no-cache');

      $c->stash->{message} = 'Ajout OK';

      $c->detach('lister');
   }
}

Le template lui-même (root/personne/ajouter.tt):
<form action="[% c.uri_for('ajouter_action') %]" method="post">
   <label for="nom">Nom</label>
   <input type="text" name="nom" id="nom" value="[% nom | html %]" />
   <label for="pw">Mot de passe</label>
   <input type="password" name="pw" id="pw" value="[% pw | html %]" />
   <input type="submit" value="Ajouter" />
</form>

Il faut maintenant implémenter la méthode ajouter_action() dans le contrôleur Personne, cette fois en utilisant le chaînage:
=head2 ajouter_action

Traiter un formulaire POST d'ajout d'une personne.

=cut

sub ajouter_action :Chained('base') :PathPart('ajouter_action') :Args(0) {
   my ($self, $c) = @_;

   my $nom = $c->request->params->{nom} || 'N/A';
   my $pw = $c->request->params->{pw} || 'N/A';

   $c->stash->{resultset}->create({ nom => $nom, pw => $pw });

   $c->stash->{message} = 'Ajout OK';
   $c->detach('lister');
}

Il manque certes le traitement d'erreur. Nous le laissons de côté car il existe des validateurs automatiques du côté Catalyst.

Enfin, il suffit de modifier le template de l'affichage de la liste de tuples (root/personne/lister.tt) pour y ajouter le lien à ajouter:
<p><a href="[% c.uri_for(c.controller.action_for('ajouter')) %]">ajouter</a>

Slide 19: Exemple avancé CRUD: modifier

Pour modifier, il faut afficher (un formulaire) sur le tuple puis traiter la modification dans le modèle.

Réflexions
  • le chaînage semble utile ici (pour trouver l'objet)
  • pourquoi ne pas réutiliser le template du formulaire d'ajout ?
  • il y a peut-être des similarités avec l'ajout, à réfléchir (mais pas traité ici)
  • visualiser un tuple est une version dégradée de la modification (pas traité ici)

Comments

Notre méthode:
=head2 modifier

Modifier une personne.

=cut

sub modifier :Chained('object') :PathPart('modifier') :Args(0) {
   my ($self, $c) = @_;

   $c->stash(template => 'personne/ajouter.tt',
             title => 'Modifier une personne',
             action => $c->uri_for($c->controller->action_for('modifier_action'), [$c->stash->{object}->id]),
             nom => $c->stash->{object}->nom,
             pw => $c->stash->{object}->pw);
}

Il faut cependant modifier root/personne/ajouter.tt:
<form action="[% action or c.uri_for('ajouter_action') %]" method="post">
(et éventuellement modifier aussi la value du bouton)

Et traiter le formulaire:
=head2 modifier_action

Traitement du formulaire POST.

=cut

sub modifier_action :Chained('object') :PathPart('modifier_action') :Args(0) {
   my ($self, $c) = @_;

   my $nom = $c->request->params->{nom} || 'N/A';
   my $pw = $c->request->params->{pw} || 'N/A';

   $c->stash->{object}->update({ nom => $nom, pw => $pw });

   $c->stash->{message} = 'Modification OK';
   
   $c->detach('lister');
}

Slide 20: Validation des données

Idéalement: les données sont typées, et Catalyst les valide du côté serveur, présentant en rouge les valeurs incorrectes.

Pistes

Slide 21: Sessions

Session == contexte

Implémentations possibles
  • cookie référençant un fichier ou une base de données
  • paramètres de GET et champ cachés de POST, maintenus manuellement

Catalyst
  • propose un stash persistant (flash)
  • copiable (et effacé) à chaque requête
  • quasi plug-and-play

Principes
  • ajouter les modules Catalyst pour la gestion de la session
  • ajouter flash_to_stash 1 dans le fichier de configuration

Références

Comments

Modifions la méthode ajouter() du contrôleur Personne pour effectuer une redirection
sub ajouter :Local {
   my ($self, $c, $nom, $pw) = @_;

   if (!defined($nom) || !defined($pw)) {
      $c->stash(template => 'personne/ajouter.tt',
                title => 'Ajouter une personne',
                nom => $nom || '',
                pw => $pw || '');
   }
   else {
      my $personne = $c->model('DB::Personne')
                        ->create({ nom => $nom, pw => $pw });

      $c->stash->{message} = 'Ajout OK';

      $c->response->redirect($c->uri_for('lister'));
      return 0;
   }
}

Tester avec

A partir de là, on voit que le message "Ajout OK" ne s'affiche plus. Par contre, l'URL affiché par le navigateur est bien maintenant la fonction lister.

Mettons en place les sessions
  • ajouter Session, Session::State::Cookie et Session::Store::FastMmap dans lib/Hello.pm (longue multiligne use Catalyst)
  • ajouter à hello.conf
    <session>
       flash_to_stash 1
    </session>
    
  • changer stash en flash dans la méthode ajouter() du contrôleur Personne

En testant à nouveau, on verra que le message est bien affiché malgré la redirection.

Pour info, par défaut les informations de sessions seront stockées dans /tmp/hello/session_data.

Slide 22: Contrôle d'accès (login)

  • agir sur les routes (plutôt que d'abuser des "if")

  • la gestion est effectuée par un module générique Catalyst (session, login, logout) à particulariser pour notre application
    • man Catalyst::Plugin:Authentication

Références

Slide 23: Exemple: authentification et autorisation

  • page de login (contrôleur, template, traitement du formulaire)

  • utilisation de notre table personne pour l'authentification

  • limitation globale de l'accès au contrôleur Personne si pas authentifié (redirection sur page de login) via la méthode auto().

Non traités
  • stockage des mots de passe de manière hashée
  • gestion de rôles plus fine (Authorization::Roles)

Comments

Tout d'abord, créer un contrôleur Login:
script/hello_create.pl controller Login

Ensuite créer la vue contenant le formulaire root/login/login.tt:
[% META title = 'Login' %]
 <!-- Login form -->
 <form method="post" action="[% c.uri_for('/login') %]">
   <table>
     <tr>
       <td>Login:</td>
       <td><input type="text" name="username" size="40" /></td>
     </tr>
     <tr>
       <td>Mot de passe:</td>
       <td><input type="pw" name="pw" size="40" /></td>
     </tr>
     <tr>
       <td colspan="2">
          <input type="submit" name="submit" value="Se connecter" />
       </td>
     </tr>
   </table>
 </form>
(oh, quelle horrible mise en page avec un tableau! n'hésitez pas à faire mieux en CSS)

Implémenter le contrôleur et la logique de base
=head2 login

Check the login is valid, if yes, redirect to /personne/lister.
Else, display an error message and this form again.

=cut

sub login : Global {
   my ($self, $c) = @_;

   my $username = $c->request->params->{username} || '';
   my $pw = $c->request->params->{pw} || '';

   if ($username && $pw) {
      if ($c->authenticate({ nom => $username,
                             pw => $pw })) {
         $c->response->redirect($c->uri_for('/personne/lister'));
         return;
      }
      else {
         $c->stash(error => 'Login incorrect.');
      }
   }

   $c->stash(template => 'login/login.tt');
}

=head2 logout

=cut

sub logout : Global {
   my ($self, $c) = @_;

   $c->logout;

   $c->response->redirect($c->uri_for('/'));
}

Ne pas oublier de supprimer la méthode index() du contrôleur Login pour que l'URL /login/ soit assignée à la méthode login() -- ce qui est ce que nous avons prévu dans le template ci-dessus (action du form).

Nous utilisons des méthodes génériques (authenticate(), user_exists()) qui se trouvent dans le module Authentication. Il faut donc l'ajouter dans lib/Hello.pm (multi-ligne use Catalyst). Sinon il y aura une erreur. De plus, il faut configurer ce module. Ajouter à hello.conf (pourrait aussi se faire dans lib/Hello.pm)
<authentication>
    default_realm dbic
    <realms>
        <dbic>
            <credential>
                # Specify that we are going to do password-based auth
                class Password
                # This is the name of the field in the users table with the
                # password stored in it
                password_field pw
                # We are using an unencrypted password for now
                password_type clear
            </credential>
            <store>
               class DBIx::Class
               user_class DB::Personne
            </store>
        </dbic>
    </realms>
</authentication>

Tester que la vue s'affiche: http://IP:3000/login/login, que le login et le logout fonctionnent: http://IP:3000/logout -- logout est :Global.

Implémenter la limitation globale de l'accès (si pas authentifié, l'accès à autre chose que le contrôleur Login est impossible) (à implémenter dans lib/Hello/Controller/Root.pm):
=head2 auto

Auto hook to enforce access restrictions (login).
Note that 'auto' runs after 'begin' but before your actions and that
'auto's "chain" (all from application path to most specific class are run)
See the 'Actions' section of 'Catalyst::Manual::Intro' for more info.

=cut

sub auto :Private {
   my ($self, $c) = @_;

   # Allow unauthenticated users to reach the login page.  This
   # allows unauthenticated users to reach any action in the Login
   # controller.  To lock it down to a single action, we could use:
   #   if ($c->action eq $c->controller('Login')->action_for('index'))
   # to only allow unauthenticated access to the 'index' action we
   # added above.
   if ($c->controller eq $c->controller('Login')) {
      return 1;
   }

   # If a user doesn't exist, force login
   if (!$c->user_exists) {
      # Dump a log message to the development server debug output
      $c->log->debug('***Root::auto User not found, redirecting to /login');
      # Redirect the user to the login page
      $c->response->redirect($c->uri_for('/login'));

      # Return 0 to cancel 'post-auto' processing and prevent use of application
       return 0;
    }

   return 1;
}

Tester que lorsqu'on accède à /login, cela fonctionne, mais si l'on accède autre chose, nous sommes redirigés au login.

Modifier le template wrapper pour que l'on affiche le nom de l'utilisateur loggué si c'est le cas. Ajouter aussi un lien pour la fonction de logout:
      [% IF c.user_exists %]
      <a href="[% c.uri_for('/logout') %]">Logout [% c.user.nom | html %]</a>
      [% END %]
.

Tester maintenant que l'on est bien redirigé si l'on n'est pas loggué, et que le login (y compris l'affichage du nom) puis le logout via le lien fonctionnent.

NB: si un "mock up" est nécessaire pour le test par exemple, il est possible de le faire de manière compatible avec Catalyst::Plugin::Authentication via la configuration de l'application. Il y a un exemple dans la manpage, utilisant un Perl hash.

Slide 24: Configuration

  • important pour le déploiement / test
  • plusieurs modules de configuration disponibles avec divers formats (YAML, simple, XML, ...)

par exemple
  • configurer la BD de production ou de test
  • utilisation de "mock up" pour un test partiel avant l'implémentation complète
  • plusieurs instances de l'application, par entreprise

Slide 25: Test

Jusqu'ici, nos tests étaient manuels, mais pourquoi ne pas les automatiser, au moins partiellement ?

Types de tests
  • unitaires (un module Perl, une méthode spécifique)
  • applicatifs
  • applicatifs avec interaction (cookies, etc)

Justification
  • les tests automatiques peuvent se lancer ... automatiquement (à chaque release? à chaque commit?)
  • plus vous implémentez de test, plus vous avez confiance dans votre application
  • aussi: plus vous penserez que mettre à jour les dépendances de votre application est facile
  • pas de test: peur du changement

Catalyst crée des tests simplifiés par défaut (que nous avons déjà cassés!)

Réalisons un test simple pour notre application.

Méthode extrême: TDD (Test-driven Development)

Comments

Le premier problème que nous allons rencontrer est l'authentification: la plupart des tests générés par Catalyst et lançables via prove --lib lib t vont planter. Il faudrait soit travailler en "mock up", soit effectivement effectuer l'authentification. Nous laissons cela au lecteur smile

On peut effectuer des tests unitaires, ou des tests complets de l'application (incluant des interactions avec le client), simulées, voire mécanisées (avec du vrai trafic Web).

Références

Création d'un test assez global (dans t/all_app.t) mais sans véritable trafic Web
#! /usr/bin/perl

use strict;
use warnings;

use Test::More;

BEGIN { use_ok("Test::WWW::Mechanize::Catalyst" => "Hello") }

my $ua1 = new Test::WWW::Mechanize::Catalyst();
my $ua2 = new Test::WWW::Mechanize::Catalyst();

$_->get_ok("http://localhost/", "Check redirect of base URL") for $ua1, $ua2;
$_->title_is("Login", "Check for login title") for $ua1, $ua2;
# pas ideal: serait mieux de tester un texte qui dirait que l'on n'est
# pas loggue; ou modifier login.tt pour si deja loggue ne pas presenter
# le formulaire.
$_->content_contains("Se connecter",
    "Check we are NOT logged in") for $ua1, $ua2;

# Log in as each user
# Specify username and password on the URL
# THIS IS WHERE A TEST DATABASE COULD BE HANDY (in config e.g.)
$_->submit_form(
    fields => {
        username => 'toto',
        pw => 'tutu'
    }) for $ua1, $ua2;

# Go back to the login page and it should show that we are already logged in
$_->get_ok("http://localhost/login", "Return to '/login'") for $ua1, $ua2;
$_->title_is("Login", "Check for login page") for $ua1, $ua2;
$_->content_contains("Logout ",
    "Check we ARE logged in" ) for $ua1, $ua2;

$_->get_ok("http://localhost/personne/lister", "personne list") for $ua1, $ua2;
$_->content_contains("Liste des personnes", "Check for personne list title") for $ua1, $ua2;
$_->content_contains("Logout ",
    "Both users should have a 'User Logout'") for $ua1, $ua2;

# 'Click' the 'Logout' link (see also 'text_regex' and 'url_regex' options)
$_->follow_link_ok({n => 1}, "Logout via first link on page") for $ua1, $ua2;
$_->title_is("Login", "Check for login title") for $ua1, $ua2;
# voir commentaire ci-dessus
$_->content_contains("Se connecter",
    "Check we are NOT logged in") for $ua1, $ua2;

# on pourrait ajouter une personne, tester qu'elle existe, etc.

done_testing;

Exécution du test
  • CATALYST_DEBUG=0 prove -wl t/all_app.t

Slide 26: Documentation

  • si la documentation n'est pas dans le code
    • pas forcément à jour
    • pas forcément dans un contrôle de version
    • pas forcément un format exploitable (diff, etc)

première idée
  • documenter dans un Wiki et/ou un suivi de tickets

idée usuellement mise en pratique
  • pragma d'auto-documentation dans le code
    • en Perl: par exemple POD (Plain Old Documentation)
    • autres langages: phpdoc, javadoc, ...

Différencier la documentation de développement et d'exploitation de la documentation initiale de conception

Comments

Perl POD: si vous avez utilisé les fameux pragmas head, cut, etc directement dans votre code Perl, alors vous pouvez utiliser par exemple pod2text lib/Hello/Controller/Personne.pm pour générer un fichier texte; il y a d'autres formats possibles (LaTeX, *roff/man, ou HTML).

Slide 27: Modules

  • installation de modules (packages ou CPAN)
  • documentation (man, perldoc, Internet)

Slide 28: Debugging

  • classique (sortie et logs, avec la fonction log de Catalyst et les niveaux de debug filtrable)

  • avec le debugger Perl (breakpoints, ..)

  • de bons tests unitaires, de scénarii ou d'application peuvent aussi aider!

Slide 29: Références

  • hello-corrige.tar.gz: Réalisation jusqu'à l'authentification, non comprise.
  • Hello-patch-pour-Authentication.patch.gz: Patch pour ajout fonctionnalité authentification: désarchiver l'archive de base sans entrer dans le répertoire, puis faire zcat Hello-patch-pour-Authentication.patch.gz | patch -p0

Slide 30: Questions et errata

  • pourquoi des fois des tirets à la fin des expansions de TT ?
    • la véritable raison est le comportement d'envoi de saut de ligne/caractères blancs
    • documentation

  • fonctionnement de l'exemple stash/flash douteux: est-ce vraiment suffisant de mettre dans le stash ?

  • le premier argument d'une méthode d'un contrôleur est l'objet lui-même (le contrôleur, souvent $self), le deuxième (souvent $c) est l'application Catalyst.

%STOPLATEX%

-- MarcSCHAEFER - 01 Aug 2012

Topic attachments
I Attachment Action Size Date Who Comment
Hello-patch-pour-Authentication.patch.gzgz Hello-patch-pour-Authentication.patch.gz manage 2.3 K 11 Aug 2012 - 20:08 MarcSCHAEFER Patch pour ajout fonctionnalité authentification: désarchiver l'archive de base, entrer dans le répertoire, puis faire zcat Hello-patch-pour-Authentication.patch.gz | patch -p1
hello-corrige.tar.gzgz hello-corrige.tar.gz manage 43.6 K 09 Aug 2012 - 17:51 MarcSCHAEFER Réalisation jusqu'à l'authentification, non comprise.
Topic revision: r10 - 22 Nov 2012, MarcSCHAEFER
 

Copyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding Foswiki? Send feedback