Apache et mod_perl

Introduction
Apache::Registry
Variantes de Apache::Registry
Comment utiliser un module tout fait ? Comment écrire le sien ?
Modules divers, ponctuellement utiles
Modules utiles lors du débugguage
Web applications frameworks
Services Web
Modules que je n'ai pas réussi à classer ailleurs
Divers
Divers

Introduction

Si vous devez concevoir un site Web, vous pouvez faire l'un des choix suivants.

Un site web statique, soit créé à la main (mais il risque d'être hétérogène), soit à l'aide d'outils adéquats, comme WML (Website Modelling Language).

http://thewml.org/

Un site web dynamique, utilisant des modules comme CGI ou CGI::Application, basé sur des CGI ou (ce qui revient en fait au même, sauf que c'est plus rapide et que ça ne s'appelle plus CGI), Apache::Registry.

Un site Web dynamique, construit autour d'un module mod_perl que vous aurez écrit, ou d'un module qui fera exactement ce que vous voulez (slashcode, Apache::MP3, etc.)

http://slashcode.com/

Un site Web dynamique construit à l'aide de HTML::Mason, HTML::Template ou AxKit.

Apache::Registry

C'est le module qui permet de lancer des scripts Perl à partir d'un serveur web Apache, comme si c'étaient des CGI. Mais en fait, ce ne sont pas des CGI : ils ne sont compilés qu'une seule fois, ils restent en mémoire, et leurs variables globales sont réellement globales, elles sont préservées entre deux exécutions. (Attention, toutefois : comme il y a plusieurs serveurs web (Apache se forke), il y aura une copie différente du programme dans chaque serveur.)

Pour que tous les fichiers d'un répertoire soient gérés par Apache::Registry, on peut mettre dans le httpd.conf

PerlModule Apache::Registry

<Location /perl>
  SetHandler perl-script
  PerlHandler Apache::Registry
  Options ExecCGI
</Location>

Pour que tous les fichiers ayant l'extension .perl soient gérés par Apache::Registry, on peut mettre dans httpd.conf

<Location /perl/*.pl>
  SetHandler perl-script
  PerlHandler Apache::Registry
  Options -Indexes ExecCGI
  PerlSendHeader On
</Location>

Les fichiers *.perl ainsi gérés par Apache::Registry peuvent être des programmes Perl tout à fait normaux.

#! perl -wT
use strict;
use Apache::Run;
print "Content-type: text/html\n\n";
print "Hi There!";

Mais on n'est pas obligé de mettre les premières lignes (car perl est déjà lancé et le module Apache::Run est déjà chargé).

print "Content-type: text/html\n\n";
print "Hi There!";

ou encore

my $r = Apache->request;
$r->content_type("text/html");
$r->send_http_header;
$r->print("Hi There!");

ou encore, en utilisant le module CGI (qui sert d'une part à récupérer les arguments envoyés par POST ou GET, d'autre part, pour les gens qui n'aiment pas le HTML, à remplacer le HTML par des appels de fonctions -- ça n'est pas parce qu'on utilise ce module qu'on fait du CGI).

#!/usr/local/bin/perl -Tw
use strict;
use CGI qw(:all);
$|++;
print header;
print start_html('Essai'), 
      h1('Essai'), 
      hr, 
      p('Ceci est un essai.'), 
      end_html;

Variantes de Apache::Registry

Apache::PerlRun

Le module Apache::Registry ne convient pas à tous les scripts : les plus mal écrits ne supporteront pas (entre autres) que les variables globales soient préservées entre deux exécutions. Le module Apache::PerlRun est à mi-chemin entre Apache::Registry et mod_cgi : les fichiers sont compilés à chaque fois (donc c'est plus lent qu'Apache::Registry), mais par contre, les modules qu'ils utilisent ne le sont qu'une seule fois (c'est donc plus rapide que mod_cgi).

Pour que tous les fichiers d'un répertoire soient traités de cette manière, on peut mettre les lignes suivantes dans les httpd.conf.

PerlModule Apache::PerlRun

<Location /cgi-perl>
  SetHandler perl-script
  PerlHandler Apache::PerlRun
  Options +ExecCGI
  PerlSendHeader On
</Location>

Apache::RegistryNG

C'est une variante de Apache::Registry.

Apache::RegistryNG -- Apache::Registry New Generation
                                      
Apache::RegistryNG is the same as Apache::Registry, aside
from using filenames instead of URIs for namespaces. It also
uses an Object Oriented interface.

Apache::RegistryBB

Une autre variante de Apache::Registry.

Apache::RegistryBB -- Apache::Registry Bare Bones
                                      
It works just like Apache::Registry, but does not test the x
bit (-x file test for executable mode), only compiles the
file once (no stat() call is made per request), skips the
OPT_EXECCGI checks and does not chdir() into the script
parent directory. It uses the Object Oriented interface.

Apache::RegistryLoader

Normalement, les scripts utilisant Apache::Registry sont compilés la première fois qu'il sont demandés. Ce module permet de les compiler dès le lancement du serveur.

Comment utiliser un module tout fait ? Comment écrire le sien ?

Dans le fichier de configuration d'Apache, on rajoute un bloc <location>, qui va charger le module en question. Généralement, un site Web ne contiendra qu'un seul tel bloc : il n'y en aura plusieurs que s'il y a des choses de nature très différente. Par exemple, ma page web sur http://www.math.jussieu.fr/~zoonek/ contient des documents HTML statiques (i.e., qui n'interagissent pas avec l'utilisateur), construits automatiquement à partir de fichiers texte, et parfois accompagnés de fichiers annexes (fichiers Perl ou PostScript). Si on voulait en faire un site dynamique (par exemple, si je voulais laisser les visiteurs commenter mes écrits, ou si je voulais ajouter un moteur de recherche), on utiliserait un seul module, qui se chargerait de rajouter une barre de navigation et de convertir les fichiers texte en HTML.

Le bloc <location> permet aussi de configurer le module (si je reprends l'exemple de mon site Web plutôt statique, on pourrait y mettre de nom des rubriques et des répertoires qui doivent figurer sur le site, ou les couleurs à utiliser).

Apache::Album

Ce module permet de gérer un album photo. Son bloc <location> ressemble à ceci.

<Location /albums>
  SetHandler perl-script
  PerlHandler Apache::Album
  PerlSetVar  AlbumDir            /albums_loc
  PerlSetVar  BodyArgs            BGCOLOR=white
  PerlSetVar  Footer              "<EM>Optional Footer Here</EM>"
</Location>

Le module ressemble à ceci (je simplifie) -- c'est un bon exemple pour comprendre comment en écrire un soi-même.

package Apache::Album;
use strict;
use vars qw($VERSION);

use Apache::Constants qw/:common REDIRECT/;
use Apache::Request;
use Apache::URI ();

$VERSION = '0.95';

sub handler {
  my $r = Apache::Request->new(shift);

  # Lecture des paramètres venant de httpd.conf
  my %settings;
  $settings{'AlbumDir'} = $r->dir_config('AlbumDir') || "/albums_loc";
  ...

  # Lecture des arguments
  my %params = ();
  %params = $r->method eq 'POST' ? $r->content : $r->args;

  # Utilisation des arguments
  if (defined $params{'AlbumName'}) {
    my $directory = $params{AlbumName};
    $directory =~ s,[^\w\d()],,g;
    ...
  }

  ...

}

C'est à peu près tout. On rappelle qu'il ne faut JAMAIS faire confiance aux données qui viennent de l'utilisateur : ici, par exemple, on vérifie que le nom de répertoire ne contient que des lettres ou des parenthèses. Il conviendrait même de se mettre en « taint mode », de manière à arrêter l'exécution du programme si jamais il fait quelque chose de dangereux avec des données venant de l'utilisateur qui n'ont pas été vérifiées.

Voir Apache::TaintRequest (un peu plus loin)

Apache::Gallery

Un autre album photo (qui utilise imlib2 à grands coups de use Inline 'C'...).

Apache::PhotoIndex

Un autre album photo

Apache::CVS

Interface à une archive CVS

Apache::ImageMagick

Pour manipuler automatiquement des images, par exemple avec des URL de la forme

http://localhost/images/whatever.gif/Annotate?font=Arial&x=5&gravity=west&text=Hello+world+!

Apache::MP3

Pour proposer des MP3 à télécharger ou à écouter en streaming.

Le module Apache::MP3::Skin permet de configurer l'apparence du site.

http://www.apachemp3.com

Apache::Perldoc

Transforme la documentation des modules installés en HTML.

Apache::PrettyText

Transforme des fichiers texte en HTML

Apache::NavBar

Transforme les fichiers HTML avant de les servir au client, en remplaçant <!--header-->, <!--start--> ou <!--footer--> par le contenu d'un fichier.

Apache::Sandwich

Ajoute le début et la fin des fichiers HTML.

Apache::SetWWWTheme

Ajoute le haut, le bas et le bord des fichiers HTML:

Weblogs

On qualifie de Weblog des sites dans lesquels des gens (les modérateurs) postent des messages (généralement, des informations) et dans lesquels les lecteurs peuvent répondre.

http://freshmeat.net/
http://linuxfr.org/

L'un des logiciels sous-jacents à ces sites est Slashcode.

http://slashcode.com/

C'est probablement un bon exercice que de lire le code d'un Weblog minimaliste :

http://www.raelity.org/apps/blosxom/

Modules divers, ponctuellement utiles

CGI

Ce module permet d'une part de récupérer les différents arguments d'un script (qu'il soit lancé par GET ou POST), d'autre part, pour les programmeurs qui n'aiment pas le HTML, de remplacer les <H1>, <TABLE> et autres <A> par des appels de fonction h1(...), table(...), a(...).

CGI::Application

Quand on conçoit un site, on a généralement plusieurs types de page : la page principale, une page contenant un formulaire pour ajouter des commentaires, une page contenant un formulaire pour effectuer une recherche, une page contenant les réponses de cette recherche, etc. Généralement, on programme cela avec plein de ifs.

if( $mode eq "main" ){
  ...
} elsif( $mode eq "add_comment" ){
  ...
} elsif( $mode eq "search_form" ){
  ...
} elsif( $mode eq "search_results" ){
  ...
} else {
  ...
}

Le module CGI::Application oblige à programmer plus proprement, en utilisant des « run modes » : à chaque type de page différent correspond une fonction particulière.

Ce module n'apporte rien de plus, il se contente de nous inciter à programmer lisiblement.

Apache::Session

Ce module permet de stocker des informations lors d'une session, i.e., lors de plusieures requètes provenant d'une même personne. On manipule simplement une table de hachage, liée à un fichier (à l'aide du module Storage) où à une base de données (via le module DBI).

Après avoir récupéré ou créé le numéro de la session dans le cookie (oui, il faut faire ça soi-même -- ne pas oublier de renvoyer le cookie au client), on se contente d'un

my %session;
tie %session, 'Apache::Session::DBI', $session_id,
    {DataSource => 'dbi:mysql:sessions',
     UserName   => $db_user,
     Password   => $db_pass
    };

et on peut ensuite stocker ou récupérer le contenu de cette table de hachage.

Apache::DBI

Ce module permet d'utiliser une connection persistente à une base de données. Quand un script a besoin d'accéder à une base de données, il doit tout d'abord se connecter, ce qui peut prendre un certain temps. Si le même script est appelé très souvent, ces connection et déconnections successivent constituent une perte de temps (et une charge inutile pour la base de données) : ce module permet de réutiliser une connection préalablement ouverte, si elle est toujours active. (Note : le serveur Apache se forke, et on pourra avoir une connection dans chaque processis fils).

Apache::TaintRequest

Ce module permet de donner automatiquement les chaines de caractères sales (tainted, en anglais) à escape_html avant de les renvoyer au client, pour éviter le CSS (Cross Site Scripting).

use Apache::TaintRequest ();

sub handler {
  my $r = shift;
  $r = Apache::TaintRequest->new($r);

  my $querystring = $r->query_string();
  $r->print($querystring);   # html is escaped...

  ...
}

Apache::AuthCookie

Ce module permet d'exiger que l'utilisateur s'autentifie avant d'accéder à certaines pages. S'il n'est pas autentifié, il est automatiquement redirigé vers une page de login.

Le module ne s'utilise pas directement : on écrit un module qui hérite de ce module, dans lequel on définit deux méthodes, une première pour vérifier login et mot de passe et créer une clef de session, une seconde pour vérifier l'autenticité de la clef de session.

Apache::Dispatch

Je viens de dire que si on voulait gérer un site en écrivant des modules, il fallait modifier le fichier de configuration d'Apache à chaque ajout de module. En fait, le module Apache::Dispatch rend cela superflu : quand on lui donne un URI, il va essayer de trouver le module correspondant.

Apache::Constants

Ce module contient les constantes dont on a besoin pour écrire des handlers, par exemple, les codes de retour OK ou DECLINED.

use Apache::Constants qw(:common);

Apache::File

Remplacement (plus rapide) de IO::File. Sa fonction la plus utile doit être tmpfile.

Apache::URI

Remplacement (plus rapide) de URI::URL. On peut l'utiliser pour décomposer un URI,

my $string = "http://www.foo.com/path/file.html?query+string"
my $uri = Apache::URI->parse($r, $string);

mais l'URI courant a déjà été décomposé et se trouve dans

my $uri = $r->parsed_uri;

On peut récupérer les différentes parties de l'URI.

my $scheme = $uri->scheme;
my $hostinfo = $uri->hostinfo;
my $user = $uri->user;
my $password = $uri->password;
my $hostname = $uri->hostname;
my $port = $uri->port;
my $path = $uri->path;
my $path = $uri->rpath;
my $query = $uri->query;
my $fragment = $uri->fragment;

On peut aussi reconstruire cet URI :

$string = $uri->unparse;

Apache::Util

Ce module définit des fonctions déjà présentes dans d'autres modules, mais plus rapides (car écrites en C et pas en Perl).

La fonction Apache::Util::escape_html est l'équivalent de HTML::Entities::encode et transforme les < ou & en &gt; ou &amp;.

La fonction Apache::Util::escape_uri est l'équivalent de URI::Escape::uri_escape et transforme les caractères qui ne doivent pas se trouver dans un URL en choses du genre %20 (où 20 est le code ASCII (hexadécimal) du caractère dont on ne veut pas, par exemple, un espace).

Il y a aussi des fonctions unescape_uri, unescape_uri_info (idem, mais elle transforme aussi les + en espaces, comme d'habitude dans les arguments passés par la méthode GET), parsedate, ht_time, validate_password, size_string.

Apache::SIG

Ce module permet (par exemple) d'afficher dans les logs quand un client a interrompu la connection.

PerlFixupHandler Apache::SIG LogFormat "%h %l %u %t \"%r\" %s %b %{SIGPIPE}e"

Modules utiles lors du débugguage

Apache::Status

Ce module affiche l'état du serveur (environement, modules chargés, handlers actifs, tables de symboles (voir aussi Apache::Symdump), etc.)

<Location /perl-status>
  SetHandler  perl-script
  PerlHandler Apache::Status
  Order deny,allow
  Deny from all
  Allow from .your-domain.com
</Location>

Dans le même ordre d'idée (mais ça n'est plus Perl) :

<Location /server-status>
  SetHandler server-status
  Order deny,allow
  Deny from all
  Allow from .your-domain.com
</Location>

Apache::Symdump

Ce module utilise Devel::Symdump et permet de sauvegarder la table des symboles dans un fichier, pour comparaison ultérieure, avec la commande diff.

Apache::Resource

Ce module permet de limiter la mémoire ou le temps de calcul d'un processus fils httpd (si on constate, sans parvenir à comprendre pourquoi, qu'il y a une fuite de mémoire ou que ça boucle).

PerlSetEnv PERL_RLIMIT_DEFAULTS On
PerlModule Apache::Resource

La page de manuel de setrlimit nous donne la liste des paramètres modifiables (ça dépend du système).

RLIMIT_CPU     /* CPU time in seconds */
RLIMIT_FSIZE   /* Maximum filesize */
RLIMIT_DATA    /* max data size */
RLIMIT_STACK   /* max stack size */
RLIMIT_CORE    /* max core file size */
RLIMIT_RSS     /* max resident set size */
RLIMIT_NPROC   /* max number of processes */
RLIMIT_NOFILE  /* max number of open files */
RLIMIT_MEMLOCK /* max locked-in-memory address space*/

Voir aussi Apache::SizeLimit.

Apache::SizeLimit

Module, appelé depuis le fichier de configuration

# sartup.pl:
use Apache::SizeLimit;
# sizes are in KB
$Apache::SizeLimit::MAX_PROCESS_SIZE  = 10000; # 10MB
$Apache::SizeLimit::MIN_SHARE_SIZE    = 1000;  # 1MB
$Apache::SizeLimit::MAX_UNSHARED_SIZE = 12000; # 12MB

# httpd.conf:
PerlFixupHandler Apache::SizeLimit

ou depuis un script CGI

use Apache::SizeLimit;
&Apache::SizeLimit::setmax(10000);          # Max size in KB
&Apache::SizeLimit::setmin(1000);           # Min share in KB
&Apache::SizeLimit::setmax_unshared(12000); # Max unshared size in KB

qui tue les processus trop gros.

Apache::PerlSections

On peut utiliser du code Perl dans le fichier de configuration d'Apache (httpd.conf), dans des blocs <Perl>. À des fins de débugguage, on peut vouloir vérifier quelles sont les valeurs effectivement calculées par ces morceaux de code : les méthodes dump et store envoient ces valeurs sur la sortie standard ou dans un fichier.

Apache::FakeRequest

Ce module permet de débugguer, quand on écrit des modules (qui vont servir de handlers) plutôt que des scripts isolés.

Apache::Watchdog::RunAway

This module monitors hanging Apache/mod_perl processes. You define the
time in seconds after which the process is to be counted as hanging or
run away.

Apache::VMonitor

Remplacement de mod_status

# Configuration in httpd.conf
<Location /sys-monitor>
  SetHandler perl-script
  PerlHandler Apache::VMonitor
</Location>

# startup file or <Perl> section:
use Apache::VMonitor();
$Apache::VMonitor::Config{BLINKING} = 0; # Blinking is evil
$Apache::VMonitor::Config{REFRESH}  = 0;
$Apache::VMonitor::Config{VERBOSE}  = 0;
$Apache::VMonitor::Config{SYSTEM}   = 1;
$Apache::VMonitor::Config{APACHE}   = 1;
$Apache::VMonitor::Config{PROCS}    = 1;
$Apache::VMonitor::Config{MOUNT}    = 1;
$Apache::VMonitor::Config{FS_USAGE} = 1;
$Apache::VMonitor::Config{NETLOAD}  = 1;

@Apache::VMonitor::NETDEVS    = qw(lo eth0);
$Apache::VMonitor::PROC_REGEX = join "\|", qw(httpd mysql squid);

Apache::GTopLimit

 This module allows you to kill off Apache processes if they grow too
 large or if they share too little of their memory. You can choose to
 set up the process size limiter to check the process size on every
 request:

Apache::LogSTDERR

Normalement, tout ce qui est envoyé sur STDERR est perdu : ce module l'envoie dans un fichier.

PerlModule Apache::LogSTDERR
HookStderr logs/stderr_log

Voir aussi CGI::Carp.

Module::Use

Ce module note les modules les plus utilisés. Il peut aussi regarder quels ont été les modules les plus utilisés récemment et les charger automatiquement.

Apache::DB

Hooks for the interactive Perl debugger

Apache::DProf

Hooks for Devel::DProf

Apache::SmallProf

Hooks for Devel::SmallProf

HTTPD::Bench::ApacheBench

Pour tester la montée en charge d'un serveur Apache/mod_perl.

Web applications frameworks

HTML::Template

On écrit d'une part des fichiers HTML, qui vont servir de moules, d'autre part du code en Perl, qui va servir) les remplir.

Voici par exemple une réécriture en Perl de

http://crookshanks.free.fr/

Voici le fichier principal, en Perl.

#! perl -w
use strict;
#  use Carp ();
#  $SIG{__WARN__} = \&Carp::cluck;
#  $SIG{__DIE__}  = \&Carp::confess;
#  $|++;
use Date::Calc qw/Today_and_Now/;
use HTML::Template;
use CGI;
my $query = new CGI;
  
############################################################
  
# Accès à la base de données
use DBI;
my $dbh = DBI->connect("dbi:Pg:dbname=zoonek",
		       'zoonek', '',
		       { RaiseError => 1, AutoCommit => 1 })
  or action_error("Cannot connect to db: $DBI::errstr");
  
# Schéma de la base
my $table_name = "dvdhk";
sub tmpl_values { return [ map {{ VALUE => $_ }} @_ ] }
my @schema = (
	      { field => 'titre',   name => 'Titre du livre', type => 'TEXT',
		validate => \&validate_require,
	      },
	      { field => 'auteur',  name => 'Auteur(s)', type => 'TEXT' },
	      { field => 'editeur', name => 'Editeur', type => 'TEXTAREA' },
	      { field => 'genre',   name => 'Genre', type => 'SELECT',
		values => tmpl_values(qw/roman theatre philosophie/),
	      },
	      { field => 'date',    name => 'Date', type => 'HIDDEN',
		value => sprintf("%s/%s/%s %s:%s:%s", Today_and_Now()),
	      },
	     );
my @fields = map { $_->{field} } @schema;
my $title_name = $fields[0];
my $field_names = join(', ', @fields);
my $schema_tmpl = [ map { { NAME => $_->{field},
                            VALUE => $_->{value},
			    $_->{type} => 1,
			    VALUES => $_->{values} || [],
			  } } @schema ];
  
use Data::Dumper;
print STDERR Dumper($schema_tmpl);
  
############################################################
  
# Propriétés que doivent vérifier les champs que l'on rajoute dans la base
sub validate_any { }
sub validate_require {
  print STDERR "Checking field $_[0] => '$_[1]'\n";
  action_error("Le champ \"$_[0]\" est obligatoire")
    unless defined $_[1] and $_[1] !~ m/^\s*$/sm ;
}
  
# Requètes SQL
sub get_five_last {
  my $sth = $dbh->prepare("SELECT $field_names FROM $table_name ORDER BY date DESC LIMIT 55");
  $sth->execute();
  return sql_to_tmpl($sth);
}
  
sub get_title_list {
  my $sth = $dbh->prepare("SELECT DISTINCT $title_name FROM $table_name ORDER BY $title_name");
  $sth->execute();
  my $result = [];
  while( my $row = $sth->fetchrow_arrayref ) {
    push @$result, { TITLE => $row->[0] };
  }
  return $result;
  # Should look like this:
  # return [ { TITLE => 'foo' }, { TITLE => 'bar' }, { TITLE => 'baz' } ] ;
}
  
sub get_search_results {
  my $search = shift;
  my $sql = "SELECT $field_names FROM $table_name WHERE $title_name LIKE ? ORDER BY date DESC";
  my $sth = $dbh->prepare($sql);
  $sth->execute( '%' . $search . '%' );
  return sql_to_tmpl($sth);
}
  
sub add_entry {
  my $sth = $dbh->prepare("INSERT INTO $table_name ($field_names) VALUES (".
			  join(',', map {"?"} @fields)
			  .")");
  $sth->execute(@_);
}
  
############################################################
  
sub sql_to_tmpl {
  my $sth = shift;
  my $result = [];
  while( my $row = $sth->fetchrow_arrayref ) {
    my @h;
    my $i=0;
    foreach my $col (@schema) {
      push @h, { NAME => $col->{field}, VALUE => $row->[$i] };
      $i++;
    }
    push @$result, { FICHE => \@h };
  }
  return $result;
  # Should look like this:
  return [ { FICHE => [ { NAME => "titre",   VALUE => "qjsdf" },
			{ NAME => "éditeur", VALUE => "ajhaf" },
			{ NAME => "auteur",  VALUE => "lkfdf" },
		      ] },
	   { FICHE => [ { NAME => "titre",   VALUE => "qjsdf" },
			{ NAME => "éditeur", VALUE => "ajhaf" },
			{ NAME => "auteur",  VALUE => "lkfdf" },
		      ] },
	   { FICHE => [ { NAME => "titre",   VALUE => "qjsdf" },
			{ NAME => "éditeur", VALUE => "ajhaf" },
			{ NAME => "auteur",  VALUE => "lkfdf" },
		      ] },
	 ];
}
  
# La liste des actions possibles et les fonctions correspondantes
my %actions;
my @actions = qw/derniers titres recherche formulaire envoi/;
my $default_action = $actions[0];
{
  no strict;
  %actions = map { $_ => \&{"action_$_"} } @actions;
}
  
# Aiguillage selon la valeur du paramètre "action"
foreach my $p ($query->param()){ print STDERR "param $p => ". $query->param($p) ."\n"; }
my $act = $query->param('action') || $default_action;
if( exists $actions{$act} ){
  &{ $actions{$act} }();
} else {
  action_error("No such action: $act");
}
  
# On vérifie que la page a bien été envoyée par POST et pas par GET.
sub require_POST {
  action_error("POST-only page") 
    unless $query->request_method eq "POST";
}
  
# Une fonction pour afficher les pages
sub print_template {
  print "Content-Type: text/html\n\n";
  my $name = shift;
  my $template = HTML::Template->new(filename => $name);
  while(@_){
    my $a = shift;
    my $b = shift;
    $template->param($a, $b);
  }
  print $template->output;
}
  
# La définition des différentes actions
sub action_derniers {
  print_template('derniers.tmpl', FICHES => get_five_last());
}
sub action_titres {
  print_template('titres.tmpl', LIST => get_title_list());
}
sub action_recherche {
  my $search = $_[0] || $query->param("search");
  my $res = get_search_results($search);
  print_template('recherche.tmpl',
		 NUMBER_RESULTS => (scalar @$res),
		 SEARCH => $search,
		 FICHES => $res,
		);
}
sub action_formulaire {
  print_template('formulaire.tmpl', SCHEMA => $schema_tmpl);
}
sub action_envoi {
  require_POST();
  foreach my $e (@schema) {
    my ($v, $f) = ($e->{validate}, $e->{field});
    print STDERR "about to check $f => '". $query->param($f) ."'\n";
    &{ $v }( $f, $query->param($f) ) if $v;
  }
  add_entry(map { $query->param($_->{field}) } @schema);
  action_recherche($query->param($title_name));
}
sub action_error {
  print_template('error.tmpl', MESSAGE => $_[0]);
  exit;
}

Le fichier bas.tmpl :

(date)
</body>
</html>

Le fichier blabla.tmpl :

<p>Bla bla</p>
<p><a href="dvdhk.pl?action=formulaire">Ajouter une nouvelle fiche</a></p>
<p>
  <form method="GET" action="dvdhk.pl">
    <input type="submit" value="Rechercher un titre">
    <input type="text"   name="search" size="60">
    <input type="hidden" name="action" value="recherche">
  </form>
</p>

Le fichier css.tmpl :

<style type="text/css">
<!--
  
body, table.plain td, table.plain th {
  background: white;
  color: black;
}
  
a:link {
  color: #00f;
  background: transparent;
}
  
a:visited {
  color: #800080;
  background: transparent;
}
  
a:active {
  color: green;
  background: #FFD700;
}
  
th {
  font-weight: normal;
  text-align: left;
  vertical-align: top
}
  
table {
  background: white;
  color: black;
  empty-cells: show;
}
  
table td, table th {
  background: #DDDDFF;
  color: black;
}
  
  
-->
</style>

Le fichier derniers.tmpl :

<TMPL_INCLUDE NAME="haut.tmpl">
<TMPL_INCLUDE NAME="blabla.tmpl">
<p>Voici les cinq dernières fiches :</p>
<TMPL_INCLUDE NAME="fiches.tmpl">
<TMPL_INCLUDE NAME="bas.tmpl">

Le fichier error.tmpl :

<html>
  <head>
    <title>Erreur</title>
    <TMPL_INCLUDE NAME="css.tmpl">
  </head>
  <body>
    <p>
      <TMPL_VAR NAME=MESSAGE>
    </p>
  </body>
</html>

Le fichier fiches.tmpl :

<TMPL_LOOP NAME=FICHES>
  <table>
    <TMPL_LOOP NAME=FICHE>
      <tr>
        <th><TMPL_VAR NAME=NAME></th>
        <td><TMPL_VAR NAME=VALUE></td>
      </tr>
    </TMPL_LOOP>
  </table>
</TMPL_LOOP>

Le fichier formulaire.tmpl :

<TMPL_INCLUDE NAME="haut.tmpl">
<form method="POST" action="dvdhk.pl">
  <table class="plain">
    <TMPL_LOOP NAME=SCHEMA>
      <tr>
         <td><TMPL_VAR NAME=NAME></td>
         <td>
           <TMPL_IF NAME=HIDDEN>
             <input type="hidden" name="<TMPL_VAR NAME>" value="<TMPL_VAR NAME=VALUE>" size=50>
           </TMPL_IF>
           <TMPL_IF NAME=TEXT>
             <input type="text" name="<TMPL_VAR NAME>" value="<TMPL_VAR NAME=VALUE>" size=50>
           </TMPL_IF>
           <TMPL_IF NAME=TEXTAREA>
             <textarea name="<TMPL_VAR NAME=NAME>" rows=1 cols=50>
               <TMPL_VAR NAME=VALUE>
             </textarea>
           </TMPL_IF>
           <TMPL_IF SELECT>
             <select name="<TMPL_VAR NAME>">
               <TMPL_LOOP VALUES>
                 <option value="<TMPL_VAR VALUE>"><TMPL_VAR VALUE></option>
               </TMPL_LOOP>
             </select>
           </TMPL_IF>
         </td>
      </tr>
    </TMPL_LOOP>
    <tr><td colspan=2><input type="submit" value="Ajouter la fiche"></td></tr>
  </table>
  <input type="hidden" name="action" value="envoi">
</form>
<TMPL_INCLUDE NAME="bas.tmpl">

Le fichier haut.tmpl :

<html>
  <head>
    <title>DVDHK</title>
    <TMPL_INCLUDE NAME="css.tmpl">
  </head>
  <body>
<h1>DVDHK</h1>

Le fichier recherche.tmpl :

<TMPL_INCLUDE NAME="haut.tmpl">
<TMPL_INCLUDE NAME="blabla.tmpl">
<p>Il y a <TMPL_VAR NAME="NUMBER_RESULTS"> fiche(s) contenant `<TMPL_VAR NAME="SEARCH">' :</p>
<TMPL_INCLUDE NAME="fiches.tmpl">
<TMPL_INCLUDE NAME="bas.tmpl">

Le fichier titres.tmpl :

<TMPL_INCLUDE NAME="haut.tmpl">
<TMPL_INCLUDE NAME="blabla.tmpl">
<p>Voici la liste des fiches :</p>
<p>
  <TMPL_LOOP NAME=LIST>
    <TMPL_VAR NAME=TITLE>
    <br>
  </TMPL_LOOP>
</p>
<TMPL_INCLUDE NAME="bas.tmpl">

Seul le fichier formulaire.tmpl est peu lisible. Il faudrait le rendre plus lisible en ajoutant quelques lignes en Perl (dans le fichier principal). Voir aussi la FAQ 11 dans le manuel.

Apache::PageKit

Ce module utilise le modèle de conception MVCC.

PageKit follows a Model/View/Content/Controller design pattern, which
is an adaption of the Model/View/Controller pattern used in many other
web frameworks, including Java's Webmacro and Struts.

Le contenu du site est dans des fichiers XML, qui seront convertis en HTML, WML ou PDF à l'aide de XSLT.

Le modèle (ou la « business logic »), ce sont les actions à faire lors des différentes requêtes, par exemple, ajouter un utilisateur, vérifier l'autentification d'un utilisateur, effectuer une recherche, vérifier le contenu des différents champs d'un formulaire, etc.

La vue est un ensemble de morceaux de HTML (début d'un tableau, etc.), dans lequel on peut utiliser des variables (exactement comme avec HTML::Template -- d'ailleurs, c'est HTML::Template). Il peut aussi s'agir de fichiers XSLT. Il peut y avoir plusieures vues : HTML avec plein d'images clignotantes pour empécher le client de lire le site, HTML imprimable, WML, PDF, etc.

http://pagekit.org/
http://take23.org/articles/2001/01/04/pagekit.xml/1

HTML::MASON

Le principe est le même que Zope : les pages sont constituées de composants qui peuvent être des morceaux de HTML ou des fonctions. À la différence de Zope, il n'y a pas de distinction très nette entre le code Perl et le HTML.

AxKit

http://axkit.org/
http://www.perl.com/pub/a/2002/03/12/axkit.html
http://www.perl.com/pub/a/2002/04/16/axkit.html
http://www.perl.com/pub/a/2002/07/02/axkit.html
http://www.perl.com/pub/a/2002/09/24/axkit.html

Apache::ASP

Permet de mettre du code Perl au milieu du HTML (exactement comme PHP, avec les mêmes défauts : le mélange du fond et de la forme).

EmbedPerl

À peu près comme Apache::ASP.

Apache::XPP

Comme EmbedPerl.

Services Web

SOAP::Lite

SOAP est l'un des protocoles sous-jacent aux services Web. Je rappelle que les services Web, c'est exactement comme les formulaires sur certains sites Web (recherche d'un livre sur Amazon, recherche d'un mot sur http://www.dict.org/ ), sauf que les services Web ne sont pas utilisés par des être humains, mais par d'autres machines. Ainsi, Google est aussi un service Web (il donnent même un exemple en Perl dans leur documentation, mais il faut bien chercher : alors que les exemples en Java ou C# occuppent des dizaines de fichiers, le code Perl (qui pourtant fait la même chose) est réduit à quatre malheureuses lignes perdues au milieu du fichier README...).

#! perl -w
use strict;
use SOAP::Lite
  service => 'http://services.xmethods.net/soap/urn:xmethods-delayed-quotes.wsdl',
print getQuote('MSFT'), "\n";

Si on insiste, ça peut tenir en une seule ligne...

perl "-MSOAP::Lite service=>'file:./quote.wsdl'" -le "print getQuote('MSFT')"

Cela oblige à interpréter le fichier WSDL à chaque fois. On peut lui demander de le lire une première fois pour écrire un module qui contiendra tout ce qu'il faut. Par la suite, on se contentera de charger ce module.

perl stubmaker.pl http://www.xmethods.net/sd/StockQuoteService.wsdl

#! perl -w 
use strict;
use StockQuoteService ':all';
print getQuote('MSFT'), "\n";

Voir aussi

mod_soap.
http://www.soaplite.com/
SOAP::Transport::*

(si on fait un serveur, il faut écrire le fichier WSDL à la main...)

XMLRPC::Lite

Modules que je n'ai pas réussi à classer ailleurs

Apache::OutputChain

Permet d'empiler les handlers (ils sont exécutés de droite à gauche).

<Files *.html>
  SetHandler perl-script
  PerlHandler Apache::OutputChain Apache::GzipChain Apache::PassFile
</Files>

<Files *.html>
  SetHandler perl-script
  PerlHandler Apache::OutputChain Apache::EmbperlChain Apache::SSIChain Apache::PassHtml
</Files>

Alias /foo /home/httpd/perl/foo
<Location /foo>
  SetHandler "perl-script"
  Options +ExecCGI
  PerlHandler Apache::OutputChain Apache::GzipChain Apache::Registry
</Location>

Voir plutôt Apache::Filter.

Apache::Filter

(Contrairement à Apache::OutputChain, les modules sont exécutés dans l'ordre dans lequel ils apparaissent.)

PerlModule Apache::Filter
<Files ~ "*\.html">
  SetHandler perl-script
  PerlSetVar Filter On
  PerlHandler Apache::Gzip
</Files>

PerlModule Apache::Filter
Alias /home/http/perl /perl
<Location /perl>
  SetHandler perl-script
  PerlSetVar Filter On
  PerlHandler Apache::RegistryFilter Apache::Gzip
</Location>

PerlModule Apache::Filter
<Files ~ "*\.blah">
  SetHandler perl-script
  PerlSetVar Filter On
  PerlHandler Filter1 Filter2 Apache::Gzip
</Files>

Apache::Include

Ce module permet de mélanger mod_perl et mod_include, i.e., d'inclure une page (ou n'importe quoi d'autre généré par le serveur) dans un script. (jamais utilisé)

#! perl -Tw
use Apache::Include ();
print "Content-type: text/html\n\n";
print "before include\n";
Apache::Include->virtual("/perl/env.pl");
print "after include\n";

Apache::StatINC

Normalement, les modules ne sont chargés et compilés qu'une seule fois. Ce module demande qu'ils soient rechargés si jamais ils ont changés sur le disque.

# Dans httpd.conf
PerlInitHandler Apache::StatINC

Apache::Request

Cookies

Apache::RequestNotes

Allow Easy, Consistent Access to Cookie and Form Data
Across Each Request Phase

Apache::PerlVINC

Permet d'avoir des modules de même nom à des endroits différents, par exemple une version de développement et une version de production (il y aura toujours une seule version du module en mémoire, et à chaque fois on regardera si c'est la bonne, sinon, on l'efface et on chargte la bonne).

PerlModule Apache::PerlVINC

<Location /status-dev/perl>
  SetHandler       perl-script
  PerlHandler      Apache::Status

  PerlINC          /home/httpd/dev/lib
  PerlFixupHandler Apache::PerlVINC
  PerlVersion      Apache/Status.pm
</Location>

<Location /status/perl>
  SetHandler       perl-script
  PerlHandler      Apache::Status

  PerlINC          /home/httpd/prod/lib
  PerlFixupHandler Apache::PerlVINC
  PerlVersion      Apache/Status.pm
</Location>

Apache::SubProcess

Quand on lance un processus (par exemple, avec la commande system) on s'attendrait à ce que la sortie standard soit envoyée au client : ce n'est pas le cas. Ce module rétablit le comportement attendu.

use Apache::SubProcess qw(system exec);
...

Apache::AuthenPasswd

Authentification à l'aide de /etc/passwd (ou, plus précisément, getpwnam).

Apache::AuthzPasswd

Comme Apache::AuthenPasswd, mais avec /etc/group.

Apache::AutoIndex

Remplacement de mod_autoindex et mod_dir (quand il n'y a pas de fichier index.html).

Apache::Gateway

Une « gateway », c'est comme un proxy, mais le client croit qu'il accède au site originel. On peut par exemple proposer un accès à CPAN en utilisant plusieurs miroirs de CPAN.

Apache::Motd

Pour informer les visiteurs que le site sera arrété pour maintenance dans quelques minutes.

Apache::RefererBlock

block request based upon "Referer" header

Apache::Throttle

Permet d'envoyer différentes choses au client selon la vitesse de sa connection, par exemple, des images (ou des sons) moins grosses.

Apache::Traffic

Exemple de PerlLogHandler

PerlFixupHandler Apache::Usertrack

mod_usertrack emulation

Apache::TimeIt

Benchmarking tool for Apache modules. See also some othe benchmarking tools:

ApacheBenchmark (ab)
httperf
http_load
crashme (see perl.com)

Divers

J'aurais dû évoquer les modules suivants.

Data::FormValidator

http://forum.swarthmore.edu/~ken/modules/Apache-AuthCookie/

Divers

J'avais initialement prévu d'évoquer aussi les modules suivants...

Apache::Embperl        - Embed Perl code in HTML documents
Apache::SSI            - Implement server-side includes in Perl
Apache::DBI            - Maintain persistent DBI connections
Apache::GzipChain      - Compress output on the fly
Apache::TransLDAP      - Translate URIs via LDAP lookups
Apache::ASP            - Implement "Active Server Pages"
Apache::AuthenDBI      - Authenticate against a database via DBI  
Apache::PHLogin        - Authenticate against a PH database

Apache::SessionX

Devel::Leak
Devel::Symdump
GTop (requires libgtop)
Regexp
MP3::Info
Ogg::Vorbis
XML::RSS
IPC::Shareable

CGI::Carp
CGI::Application
Apache::Session
Data::FormValidator
Apache::MP3

AxKit::XSP::Util
  Apache module that uses XML processing pipelines

OpenInteract
  OpenInteract is an apache / mod_perl based application 
  server that implements database connectivity and security 
  through the SPOPS object framework.

  SPOPS (Simple Perl Object Persistence with Security) 
  is an object-oriented application framework that provides 
  object persistency and security services to your applications.

XML::Parser::Expat (included with XML::Parser)
MIME::Base64                                  
URI (for HTTP/SMTP transport and autodispatch)
HTTP::Daemon (for daemon server implementation, included with libwww-perl) 
Apache (for mod_perl server implementation, included with mod_perl) 
Net::POP3 and MIME::Parser (for POP3 server implementation)
MIME::Lite (for SMTP client implementation)
IO::File (for FTP client and IO server)
Net::FTP (for FTP client)
MQSeries (for MQSeries transport)
Net::Jabber (for Jabber transport)
FCGI (for FastCGI server implementation)
IO::Socket::SSL (SSL support for TCP transport)
MIME::Parser (for MIME attachment support)
Compress::Zlib (for compression support)

LWP::UserAgent (included with libwww-perl) 
HTTP::Request (included with libwww-perl) 
HTTP::Headers (included with libwww-perl) 
HTTP::Status (included with libwww-perl) 
Crypt::SSLeay (for HTTPS/SSL transfer)

Wombat ?

Vincent Zoonekynd
<zoonek@math.jussieu.fr>
latest modification on jeu nov 14 08:44:32 CET 2002