OpenScheme : Script CGI en Scheme (suite)
Par :
Guilhem de Wailly (gdw@erian-concept.com)

et :
Fernand Boéri (boeri@unice.fr)

Résumé
Notre connaissance du langage Scheme nous permet maintenant d'entrevoir toute la puissance de ce langage en concevant des applications plus complexes. Cela est rendu possible en exploitant les possibilités d'OpenScheme couvrant un large spectre de fonctionnalités.
Ce mois-ci, nous continuons à examiner les fonctionnalités d'OpenScheme pour écrire des scripts CGI. Nous verrons dans un premier temps la méthode par remplacement de mots clefs. Le mois prochain, nous verrons comment écrire des pages HTML avec du code Scheme dedans de telle manière quelles soient considérées par le serveur WEB comme des scripts CGI.
L'environnement OpenScheme est disponible sur le CDROM de Linux Magazine. Il est aussi disponible sur www.open-scheme.com. Cet environnement existe en version libre ou commerciale.
Description
Dans le dernier article, nous avons mis en place le serveur HTTP de manière qui soit capable d'exécuter des scripts CGI. Le premier script de test était écrit en shell afin de nous assurer que tous les éléments du serveur étaient en place. Puis nous avons écrit un premier script simple en OpenScheme qui affichait un message avec la date du jour. Ce premier script ressemblait fortement au premier script en shell.
Ce mois-ci, nous allons examiner une première méthode pour écrire des scripts CGI. Nous avons maintenant un serveur opérationnel.
OpenScheme possède deux manières de traiter les CGI : La première permet de placer des mots clefs dans une page WEB. Ces mots clefs sont remplacés par leur valeur lorsque le script s'exécute. La seconde manière de traiter les CGI est de placer directement du code Scheme dans la page WEB ; dans ce cas, la page elle-même devient un script CGI. Nous la verrons le mois prochain.
Remplacement de mots clefs
OpenScheme facilite la récupération des arguments CGI issus des formulaires et la conversion du texte en sortie au format HTML :
Récupération des arguments
La fonction (net:cgi:parse-input) récupère les arguments passés au script CGI et retourne une liste de couples identificateur-valeur de ces arguments. Les identificateurs sont des symboles représentant les noms des champs d'un formulaire HTML, et les valeurs sont les valeurs de ces champs, sous la forme de chaînes de caractères. Cette fonction est compatible avec les deux méthodes pour invoquer les CGI, la méthode POST et la méthode GET, ou une combinaison des deux. Nous verrons plus tard qu'elle traite aussi les documents ayant plusieurs parties (multipart document encoding) et qu'elle gère la transmission de fichiers (upload).
Pour utiliser cette fonction, nous allons écrire le script /home/httpd/cgi-bin/test.cgi qui affiche cette liste de couples :
#! /usr/local/bin/osm-cgi --banner=off --exec
; OpenScheme lancé sans affichage de la bannière
; pour exécuter le fichier donnée sur la ligne
; de commande par le serveur WEB.

(use 'osm)
(use 'net)

; Récupérations des couples identificateur-valeur.
(define arguments (net:cgi:parse-input))

; Affichage de l'entete HTML.
(display
"Content-type: text/html

<HTML><BODY>
<br><b>ARGUMENTS</b>\n")

; Affichage des couples.
(for-each (lambda (couple)
           (format #t
                   "<br>~a = ~s\n"
                   (car couple)
                   (cdr couple)))
         arguments)

; Affichage du bas de page HTMl.
(display "</BODY></HTML>\n")
Nous pouvons dès à présent tester ce script en entrant l'URL http://localhost/cgi_bin/test1.cgi dans le navigateur.
Si tout se passe bien, nous devrions voir une page WEB avec le mot ARGUMENTS écrit. En cas d'erreur, vous pouvez consulter les journaux du serveur dans les fichiers /var/log/httpd/access_log et /var/log/httpd/error_log. Vérifiez que le programme test1.cgi est bien accessible en lecture et exécution pour tous (sinon, tapez chmod 755 /home/httpd/cgi-bin/test1.cgi) et qu'il se situe dans le répertoire /home/httpd/cgi-bin/. Vérifiez aussi que OpenScheme se situe dans le répertoire /usr/local/bin/osm-cgi et qu'il est bien exécutable pour tous. Pour obtenir OpenScheme, vous pouvez télécharger librement la version Light (qui est parfaitement compatible CGI) sur le site www.open-scheme.com. Décompressez le fichier obtenu osm-*.gz et placez le dans le répertoire /usr/local/bin sous le nom osm-cgi. Rendez le exécutable pour tous (chmod 755 /usr/local/bin/osm-cgi). 
Maintenant, nous allons écrire un formulaire HTML dans /home/httpd/html/test1.html :
<html>
 <body>
  <! Début do formulaire.
     Le script spécifié par action sera
     invoqué lorsque l'on pressera le bouton.>
  <form action=/cgi-bin/test1.cgi
        method=post>
   <! Zone d'édition.>
   <input type=text
         name=nom
         value=valeur
         size=10>
   <! Bouton OK.>
   <input type=submit
          value=ok>
  </form>
 </body>
</html>
C'est un formulaire à une seule entrée, une zone d'édition, qui invoque le script /cgi_bin/test1.cgi lorsque l'on presse sur son bouton 'ok'. A l'exécution, on voit maintenant que des arguments sont bien associés à une valeur.
Le protocole CGI stipule que des variables sont communiquées aux scripts par l'intermédiaire de variables d'environnement. La liste de ces variables est disponible dans les RFC et les diverses documentations. Pour lire ces variables, OpenScheme dispose de la fonction (os:getenv variable). Pour compléter le script test1.cgi, nous pouvons afficher quelques unes des principales variables d'environnement en ajoutant le code suivant avant la dernière ligne de /home/http/cgi_bin/test1.cgi :
; A insérer avant la dernière ligne
; de /home/httpd/cgi_bin/test1.cgi

; Affiche le nom et la valeur d'une
; variable d'environnement.
(define (affiche-env variable)
 (let ([valeur (os:getenv variable)])
  (format #t
          "<br>~a = ~s\n"
         variable
         valeur)))

; Quelques sauts de ligne ...
(display "<br><br>\n")
(display "<b>VARIABLES D'ENVIRONNEMENT</b>\n")

; Affichage des principales variable
; d'environnement passées aux scripts CGI.
(affiche-env "AUTH_TYPE")
(affiche-env "CONTENT_LENGTH")
(affiche-env "CONTENT_TYPE")
(affiche-env "GATEWAY_INTERFACE")
(affiche-env "PATH_INFO")
(affiche-env "PATH_TRANSLATED")
(affiche-env "QUERY_STRING")
(affiche-env "REMOTE_ADDR")
(affiche-env "REMOTE_HOST")
(affiche-env "REMOTE_IDENT")
(affiche-env "REMOTE_USER")
(affiche-env "REQUEST_METHOD")
(affiche-env "REQUEST_URI")
(affiche-env "SCRIPT_NAME")
(affiche-env "SERVER_NAME")
(affiche-env "SERVER_PORT")
(affiche-env "SERVER_PROTOCOL")
(affiche-env "SERVER_SOFTWARE")
Nouse obtenons maintenant l'affichage :
ARGUMENTS 
nom = "valeur" 

VARIABLES D'ENVIRONNEMENT 
AUTH_TYPE = #f 
CONTENT_LENGTH = "10" 
CONTENT_TYPE = "application/x-www-form-urlencoded" 
GATEWAY_INTERFACE = "CGI/1.1" 
PATH_INFO = #f 
PATH_TRANSLATED = #f 
QUERY_STRING = "" 
REMOTE_ADDR = "200.201.202.203" 
REMOTE_HOST = #f 
REMOTE_IDENT = #f 
REMOTE_USER = #f 
REQUEST_METHOD = "POST" 
REQUEST_URI = "/cgi-bin/osm1.cgi" 
SCRIPT_NAME = "/cgi-bin/osm1.cgi" 
SERVER_NAME = "localhost" 
SERVER_PORT = "80" 
SERVER_PROTOCOL = "HTTP/1.0" 
SERVER_SOFTWARE = "Apache/1.3" 
Emission de code HTML
Lorsqu'un programme CGI affiche un résultat au format HTML, il doit veiller à ce que les caractères accentués soient remplacés par le code HTML correspondant. Par exemple, le code HTML de la lettre 'é' est &eacute;. Certains navigateurs se comportent correctement lorsqu'ils rencontrent des caractères accentués. Mais certains autres navigateurs ne supportent ces caractères accentués et il est nécessaire les convertir avant l'affichage.
Pour effectuer cette conversion, OpenScheme propose la fonction (net:cgi:parse-output chaîne) qui remplace les caractères accentués par leur code HTML et retourne une nouvelle chaîne de caractères convertie. Cette fonction remplace aussi les retours chariots de la chaîne par le code HTML <br> qui provoque un saut de ligne dans la page, la séquence (c) par ©, la séquence (r) par ® et la séquence (e) par le symbole de l'euro.
Si on souhaite convertir certaines des sorties effectuées, il faut les passer à la fonction net:cgi:parse-output avant l'affichage :
#! /usr/local/bin/osm-cgi --banner=off --exec

(use 'osm)
(use 'net)

(define arguments (net:cgi:parse-input))

(display "Content-type: text/html\n\n")
(display "<HTML><BODY>\n")

; Un racourci.
(define & net:cgi:parse-output)

; Affichage avec conversion.
(display (& "\n\n(e) Linux Magazine\n"))
(display (& "\n\nARGUMENTS\n"))

(for-each (lambda (couple)
            (format #t
                   (& "~a = ~s\n")
                   (car couple)
                   (cdr couple)))
         arguments)

(display "</BODY></HTML>\n")
En exécutant ce programme via le navigateur et en affichant le texte source de la page, vous apercevrez les conversions qui ont été effectuées automatiquement.
Mots clefs
Le remplacement de mots clefs dans une chaîne de caractères n'est pas vraiment une fonction du module NET, mais appartient au module STR traitant les chaînes de caractères.
La fonction (str:replace-tokens chaine items) retourne une nouvelle chaîne de caractères à partir de celle passée en argument, dans laquelle les mots clefs spécifiés dans les items ont été remplacés par leur valeur.
Les items sont un vecteur de couples, où chaque couple est formé d`un mot clef et d'une valeur de remplacement. La valeur de remplacement est soit une chaîne de caractères, soit une promesse Scheme (construite avec la fonction standard delay) qui sera évaluée lorsque elle devra effectivement être remplacée.
La fonction s'utilise de la manière suivante :
Osm> (str:replace-tokens
      "123"
      #(("1" . "un ")
        ("2" . "deux ")
        ("3" . "trois")))
  => "un deux trois"
Il est donc possible de traiter l'ensemble d'un fichier HTML pour y remplacer tous les mots clefs qu'il contient. Les mots clefs ne sont bien sûr pas imposés et n'importe quelle portion de texte peut être définie comme un mot clef.
Nous donnons un exemple de programme qui définit une fonction génère attendant comme argument un nom de fichier HTML dans lequel on souhaite remplacer les mots clefs, et un vecteur de mots clefs ; elle lit le fichier HTML ligne par ligne et remplace dans chaque ligne les mots clefs, puis affiche le résultat dans la sortie standard :
#! /usr/local/bin/osm-cgi --banner=off --exec

(use 'osm)
(use 'net)

; Fonction génératrice
(define (génère fichier.html mots-clef)
 ; Entrée à partir du fichier HTML
 (with-input-from-file
  fichier.html
 (lambda ()
    ; itération sur toutes les lignes
    (let itère ([ligne (read-line)])
    (if (not (eof-object? ligne))
     (begin
       ; Remplacement des mots clefs et
       ; affichage.
      (display (str:replace-tokens
                 ligne
                 tokens))
      (newline)
       ; Ligne suivante...
      (itère (read-line))))))))

; Les mots clefs
(define mots-clef
        #(("mot-clef-1" . "valeur-1")
          ("mot-clef-2" . "valeur-2")
          ...
          ("mot-clef-n" . "valeur-n")))

; Entete HTML
(display "Content-type: text/html\n\n")
(display "<HTML><BODY>\n")

; Génération de la sortie par remplacement
; des mots clefs.
(génère "fichier-avec-mots-clef.html"
        mots-clefs)

; Bas de page HTML
(display "</BODY></HTML>\n")
Il est possible d'automatiser toutes les étapes de la production pour la rendre plus puissante, comme en ajoutant un entête et un bas de page HTML qui sera partagé par toutes les pages produites. La valeur des items est soit une chaîne de caractères constante, soit une promesse Scheme qui sera évaluée que lorsque le remplacement sera nécessaire.
Par exemple :
Osm> (str:replace-tokens
      "123"
      #(("1" . (delay
                (string-append
                 "expression"
                 " évaluée "
                 " seulement "
                 " lorsque "
                 " nécessaire "))
        ("2" . "")
        ("3" . ".")
        ("4" . (delay
                "Jamais évalué"))))
  => "expression évaluée seulement lorsque nécessaire."
Méthode GET ou méthode POST
Dans notre premier formulaire, nous avons placé le texte method=POST. Nous aurions aussi pu écrire method=GET. La différence entre les deux est la manière avec laquelle les valeurs de champs du formulaire sont passées aux scripts CGI. Dans la première, les informations sont passées dans l'entête de la requête HTTP. Avec cette méthode, le nombre des champs n'est pas limité.
Dans le cas de la méthode GET, les valeurs des champs sont passées sur la ligne de commande du script CGI, avec la forme script?champ1=valeur1:champs2=valeur2...
Le problème est que la ligne de commande de certains systèmes d'exploitation (en est-ce vraiment ?) est limitée, et il se peut que la ligne de commande soit tronquée. Dans Linux, la ligne de commande peut occuper jusqu'à 64 ko.
Pour cette raison, on préfère utiliser la méthode POST.
OpenScheme reconnaît les deux méthodes, et il permet que les deux modes de passage des arguments soient utilisés simultanément. On pourra par exemple écrire le formulaire :
<html>
 <body>
  <form action=/cgi-bin/test1.cgi?ident=valeur
        method=post>
   <input type=text
         name=nom
         value=valeur
         size=10>
   <input type=submit
          value=ok>
  </form>
 </body>
</html>
Nous avons donc maintenant terminé la première partie liée à la production de documents HTML en utilisant le remplacement de mots clefs. Le mois prochain, nous aborderons l'inclusion de code Scheme dans les pages HTML, à la manière de PHP ou des ASP.
Les auteurs

Guilhem de Wailly, directeur de la société Erian Concept : support, formations, configurations, administration, développements Linux. Environnement OpenScheme.
http://www.erian-concept.com

Pr Fernand Boéri, Professeur à l'Université de Nice - Sophia-Antipolis, membre de l'unité SPORTS du laboratoire I3S, spécialiste en modélisation, évaluation de performances et architectures parallèles.
http://www.i3s.unice.fr
Références
WEB
¨	TCP/IP, Architecture, protocoles, applications
Douglas Comer
InterEdition
·	W3C
http://www.w3c.org
·	CGI
http://www.jmarshall.com/easy/cgi/
·	CGI
http://hoohoo.ncsa.uiuc.edu/cgi/
·	CGI
http://www.tsden.org/ryutaroh/fileupload-e.shtml
·	CGI en Français
http://www.scripts-fr.com/
·	CGI+Scheme
http://www.lh.com/~oleg/ftp/Scheme/web.html
·	FastCGI
http://www.fastcgi.com/
·	HTTP en Français
http://webbo.enst-bretagne.fr/ActiveWebFr/eg-uk-tut.book_29.fr.html
·	HTTP 1.0
http://www.ietf.org/rfc/rfc1945.txt
·	HTTP 1.1
http://www.ietf.org/rfc/rfc2616.txt
Scheme
¨	Structure et Interprétation des programmes informatiques
H. Abelson, GJ. Sussman
InterEdition
¨	The Scheme Programming Languages - Ansi Scheme 
R. Kent Dybvig
Prentice Hall
¨	Les langages Lisps - Christian Queinnec
InterEdition
¨	Programmer avec Scheme - J. Chazarin
Thomson Publishing
¨	Revised4 Report on the Algorithmic Language Scheme
W. Clinger, J. Rees
ftp://ftp.nj.nec.com/pub/kelsey
Environnements Scheme Free
¨	Bigloo - Manuel Serrano
http://kaolin.unice.fr
Environnement de programmation Scheme.
¨	DrScheme - Rice University
http://www.cs.rice.edu/CS/PLT/
Environnement Scheme libre très avancé.
¨	PCScheme - Texas Instrument
ftp://cui.unige.ch/public/pcs/pcscheme.exe
Un très bon environnement de programmation Scheme pour DOS, avec éditeur intégré.
¨	Scm - A. Jaffer
http://www-swiss.ai.mit.edu/~jaffer
La référence des interprètes Scheme. Très petit, rapide, pour beaucoup de plates-formes, extensible.
¨	Stk - Erik Gallesio
http://kaolin.unice.fr
Interprète Scheme avec la bibliothèque TK.
D'autres liens sont visibles sur le site www.schemers.org.
Environnements Scheme commerciaux
¨	ChezScheme - Cadence, Inc
http://www.scheme.com/
Environnement Scheme très performant.
¨	EdScheme, 3Dscheme - Scheme, Inc
http://www.schemers.com/
Environnement de programmation Scheme pour Windows.
¨	Inlab Scheme - Inlab Software GmbH
http://www.munich.net/inlab/scheme/
Environnement commercial
¨	OpenScheme - Erian Concept
http://www.open-scheme.com
Environnement professionnel de programmation Scheme comprenant un interprète, un compilateur et un débogueur symbolique. 
Existe en version libre et commerciale, pour Linux, FreeBSD, Solaris, Windows et BeOS, sur systèmes Intel.