CGI

Luc Didry, à partir des cours de Sébastien Jean. Licence CC-BY-SA
Brilliant. Take me to your leader. I've always wanted to say that!
Le dixième docteur

Télécharger le PDF

Rappel : le cours contient des notes de bas de page, et elles ne sont pas là que pour faire joli1 et dire des sottises.

Les serveurs Web ne sont pas seulement des serveurs capables de fournir des pages statiques, ils sont également à l’interface des utilisateurs et des langages de script et de programmation qui permettent, eux, de fournir des pages dynamiques.

1 CGI

NB : Nginx ne supporte pas le CGI.

CGI (Common Gateway Interface) est une interface permettant à un serveur Web d’appeler un programme extérieur. L’objectif, outre l’exécution du programme en question, est alors de récupérer la sortie de ce programme et de la renvoyer comme réponse à une requête. Tout programme pouvant être exécuté en ligne de commande peut être utilisé avec CGI. Ce mécanisme offre quelques avantages : CGI est indépendant de l’architecture du serveur et des langages de programmation, ce qui en fait une solution flexible, et chaque programme CGI s’exécute dans un processus indépendant.

Le principe de fonctionnement est le suivant : CGI crée un processus permettant d’exécuter le programme considéré, tout en laissant connectée la sortie standard de ce processus au serveur Web.

Le programme correspondant peut être écrit en n’importe quel langage, compilé ou interprété. Voici un exemple d’un tel programme écrit en shell :

#!/bin/sh
echo Content-type: text/plain
echo
env

Il existe ensuite plusieurs manières de configurer Apache pour qu’il puisse exécuter ce script.

1.1 Les directives ScriptAlias et ScriptAliasMatch

Les directives ScriptAlias2 et ScriptAliasMatch permettent de désigner les répertoires pouvant contenir des exécutables. L’extrait suivant est un exemple de configuration de CGI avec la directive <ScriptAlias>. La directive ScriptAliasMatch fonctionne selon le même principe que la directive ScriptAlias mais avec des expressions régulières (voir cours précédents).

ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
<Directory "/usr/lib/cgi-bin">
    AllowOverride None
    Options ExecCGI -MultiViews +SymLinksIfOwnerMatch
    Require all granted
</Directory>

1.2 La définition de fichiers au moyen de AddHandler

Pour définir les extensions des fichiers exécutables/interprétables, plutôt que les répertoires où se trouvent les programmes exécutables, il est possible d’utiliser la directive AddHandler3. Cette directive s’utilise de la manière suivante :

AddHandler *gestionnaire* *extension* [*extension*]

Le gestionnaire est la composante d’Apache qui va gérer les fichiers dont la liste d’extensions est fournie à la suite. Pour les scripts CGI, ce gestionnaire est cgi-script. Voilà ainsi un exemple de définition au moyen de cette directive (déclarant ainsi tous les fichiers à extension .cgi ou .pl comme étant des scripts exécutables) :

AddHandler cgi-script .cgi .pl

1.3 L’association de scripts à des types MIME

Enfin, il est également possible d’associer des scripts à des types MIME grâce à la directive Action4. Voici un exemple d’utilisation :

Action text/html /cgi-bin/test.cgi

Toute requête demandant un résultat de type MIME text/html sera traitée par /cgi-bin/test.cgi.

(/cgi-bin/test.cgi est un chemin d’une URL, pas un chemin du système de fichiers)

1.4 Combinaison d’AddHandler et Action

On pourra combiner AddHandler et Action ainsi :

AddHandler foo-bar-baz .foo
Action foo-bar-baz "/cgi-bin/foo.cgi"

Ainsi, une requête pour un fichier dont l’extension est .foo sera traitée par /cgi-bin/foo.cgi.

1.5 Exemples

1.5.1 Formulaire

La récupération des paramètres des formulaires est prévue par l’interface CGI. Dans le cas de passage de paramètres via un HTTP GET la norme définit les caractères ? et & comme séparateurs respectivement du nom du script avec les paramètres et entre les paramètres.

Voici un exemple de script Shell affichant la chaîne des différents arguments passés dans l’URL (de la forme /cgi-bin/args.sh?argument1&argument2).

#!/bin/sh
echo Content-type: text/html
echo
cat <<EOF
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html><head><title>View args CGI shell script</title></head>
<body><h1>HTTP GET argument string ?</h1><p>
EOF
[ ! -z "$*" ] && echo "Got '$*'" || echo "None :("
echo "</p></body></html>"

1.6 Scripts en Perl et en Ruby

Voici maintenant un exemple plus complet en Perl5, notez le champ QUERY_STRING lorsque l’on passe des paramètres via HTTP GET sur ce script.

#!/usr/bin/env perl
use warnings;
use strict;
use 5.10.0;

# load standard CGI routines
use CGI qw/:standard/;
# create the HTTP header
print header(-expires => '+3d'),
    # start the HTML
    start_html(
        -title => 'Sample echo env. Perl CGI',
        -dtd => 'html4'
    ),
    # level 1 header
    h1('Sample echo env. Perl CGI'),
    pre(
        join ("\n", map { "$_ --> ".escapeHTML($ENV{$_}) } sort keys %ENV)
    ),
    end_html;

Voici quand même le script en Ruby6 :

#!/usr/bin/env ruby
require 'cgi'

# Create an instance of CGI, with HTML 4 output
cgi = CGI.new("html4")
cgi.header('expires' => Time.now + (3 * 24 * 60 * 60))
# Send the following to the CGI object’s output
cgi.out do
  cgi.html do
    cgi.head { cgi.title { "Sample echo env. Ruby CGI" } } + cgi.body do
      cgi.h1 { "Sample echo env. Ruby CGI" } + cgi.pre do
        ENV.collect do |key, value|
          key + " --> " + CGI.escapeHTML(value.chomp) + "\n"
end; end; end; end; end

2 FastCGI

L’exécution d’un programme CGI nécessite la création d’un nouveau processus à chaque requête, ce qui est souvent pénalisant au niveau du temps d’exécution. C’est pour répondre à ce problème que FastCGI a été développé. Il permet de conserver en mémoire un programme après une requête et ainsi d’améliorer les performances relatives aux autres requêtes destinées à ce programme.

Les modifications des programmes destinés à être utilisés par FastCGI sont généralement limitées : une simple ligne de code à ajouter dans la plupart des cas, dépendant du langage en question.

2.1 Apache

Pour Apache, le module idoine est mod_fcgid7 et pour Nginx, il s’agit de ngx_http_fastcgi_module8.

Pour Apache, le fonctionnement est très proche de ce qu’on a vu pour le CGI. Par exemple :

AddHandler fcgid-script .fcgi

2.2 Nginx

Nginx a cette particularité de ne pas être capable de lancer lui-même un script fastcgi. Il faut donc le lancer pour lui avec, par exemple, spawn-fcgi et le faire écouter sur un port ou un socket accessible à Nginx.

location ~ /munin/ {
    include fastcgi_params;
    fastcgi_split_path_info ^(/munin)(.*);
    fastcgi_param PATH_INFO $fastcgi_path_info;
    fastcgi_pass unix:/var/run/munin/fastcgi-html.sock;
}

Il existe de nombreuses directives pour l’utilisation du fastcgi dans Nginx. On les regroupe généralement dans un fichier à part afin de pouvoir les réutiliser rapidement. C’est le sens de la directive include ci-dessus. Le fichier fastcgi_params inclus ci-dessus est généralement fourni par le paquet de nginx de votre distribution.

La lecture de l’article https://www.nginx.com/resources/wiki/start/topics/examples/fastcgiexample/ ainsi que de la documentation du module ngx_http_fastcgi_module vous aidera à utiliser fastcgi avec Nginx.

2.3 Exemples

Et pour finir des exemples de script FastCGI9 :

En Perl :

#!/usr/bin/env perl
use warnings;
use strict;

use CGI::Fast qw(:standard);

sub print_env() {
    my ($title, $e) = @_;
    $title.'<br/><pre>'.join ("\n", map { "$_=$e->{$_}"} sort keys %{$e})."\n";
}

my %init_env = %ENV;
my $counter  = 0;

while (my $q = new CGI::Fast) {
    $counter++;
    print $q->header(),
          '<title>FastCGI echo</title><h1>FastCGI echo</h1>',
          sprintf("Request number %d, Process ID: %d<p>\n", $counter, $$),
          &print_env('Request environment', \%ENV),
          &print_env('Initial environment', \%init_env);
}

En Ruby :

#!/usr/bin/env ruby -w
require 'cgi'
require 'fcgi'

def print_env(title,env)
  str = "#{title}:<br>\n<pre>\n";
  env.each { |k,v| str<<"#{k}=#{v}\n" }
  str<< "</pre><p>\n"
end

count = 0

FCGI.each_cgi do |cgi|
  count += 1
  content = "<title>FastCGI echo</title><h1>FastCGI echo</h1>\n"
  content<< sprintf("Request number %d, Process ID: %d<p>\n", count, Process.pid)
  content<< print_env("Request environment", cgi.env_table)
  content<< print_env("Initial environment", ENV)
  cgi.out{content}
end

3 Exercices

Les exercices sont à rendre sur https://lstu.fr/asrall-web-tp5. Envoyez vos réponses dans un fichier appelé tp5_votre_nom.txt.

Vous pouvez envoyer un seul fichier pour un binôme, mais pensez bien à mettre vos deux noms dans le nom du fichier.

Les exercices ne sont à effectuer qu’avec Apache, sauf mention contraire.

  1. Saisissez10 les scripts CGI11 shell, Perl et Ruby proposés dans le cours et exécutez-les de 2 manières différentes : en console et via le serveur Web. À vous de rechercher dans quels contextes les directives correspondantes peuvent être utilisées.
  2. Par rapport à votre environnement, quelles sont les variables d’environnement définies spécifiquement par Apache ? Par quelles techniques peut-on personnaliser ces variables ?
  3. mod_actions : À quel moment la directive Action est-elle utilisée ? Ainsi, si une action est définie sur un type HTML et qu’un script génère du HTML, cette directive intervient-elle ?
  4. Installez FastCGI sur votre serveur, saisissez les scripts FastCGI et faites des tests de performances entre CGI et FastCGI sur 1000 requêtes par exemple (siege est votre ami).
  5. Installez spawn-fcgi et faites fonctionner un des scripts fastcgi avec Nginx.
  6. À partir de l’arborescence :

    td5/nimp
    td5/nimp/echo.cgi
    td5/nimp/echo.fcgi
    td5/nimp/args.sh
    td5/cgi-bin
    td5/cgi-bin/args.sh -> ../nimp/args.sh
    td5/cgi-bin/echo.cgi -> ../nimp/echo.cgi
    td5/fcgi-bin
    td5/fcgi-bin/echo.fcgi -> ../nimp/echo.fcgi
    td5/logs

    Réalisez la configuration d’un VirtualHost avec les propriétés suivantes :

    1. td5/logs devra contenir :
    • access.log (accès au virtualhost)
    • error.log (erreurs du virtualhost)
    • cgi.log (erreurs des scripts CGI et FastCGI)
    1. http://localhost liste le contenu du répertoire nimp
    2. http://localhost/cgi-bin et http://localhost/fcgi-bin interdits
    3. http://localhost/cgi-bin/args.sh, http://localhost/cgi-bin/echo.cgi et
      http://localhost/fcgi-bin/echo.fcgi exécutent les scripts respectifs
    4. http://localhost/args.sh, http://localhost/echo.cgi et
      http://localhost/echo.fcgi affichent le contenu des scripts dans le navigateur et n’en proposent pas le téléchargement.
    5. le répertoire /cgi-bin ne devra pas exécuter de FastCGI (.fcgi) et réciproquement le répertoire /fcgi-bin ne devra pas exécuter de CGI (.cgi, .sh, .pl ou .rb)
    Pour réaliser cette configuration vous utiliserez les directives suivantes :
    • Options, Indexes et SymLinksIfOwnerMatch
    • RemoveHandler
    • AddType
    • ScriptAlias
    • ScriptLog
    • ErrorLog
    • CustomLog
    • LogLevel

  1.  mais c’est vrai que c’est quand même plus joli avec :p

  2.  https://httpd.apache.org/docs/2.4/fr/mod/mod_alias.html#scriptalias

  3.  http://httpd.apache.org/docs/current/mod/mod_mime.html#addhandler

  4.  https://httpd.apache.org/docs/2.4/fr/mod/mod_actions.html

  5.  cela devait initialement être en Ruby, mais faut pas déconner quand même !

  6.  bon sang, ce que c’est laid le Ruby tout de même

  7.  http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html

  8.  http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html

  9.  pensez à bien installer le module Apache kivabien, ainsi que les bibliothèques fastcgi des langages si vous voulez les tester

  10.  Ou plutôt recopiez-les depuis le fichier .md disponible sur https://lstu.fr/asrall

  11.  pas les scripts FastCGI


https://asrall.fr