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 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. Nous verrons comment rafraîchir les pages périodiquement, comment récupérer les valeurs des champs des formulaires, comment télécharger des fichiers sur le serveur, comment gérer les cookies. Nous verrons aussi comment OpenScheme facilite grandement toutes ces gestions en proposant un environnement puissant et facile d'accès.

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 les deux derniers articles, nous avons mis en place le serveur HTTP de manière qui soit capable d'exécuter des scripts CGI que nous avons testé avec un petit script CGI en shell. Puis nous avons réalisé un script CGI en Scheme en utilisant le remplacement de mots clefs qui est une technique très souple proposée par OpenScheme pour réutiliser des pages WEB existantes en les ``agrémentant'' de certaines parties dynamiques.

Ce mois-ci, nous allons examiner la seconde méthode pour écrire des scripts CGI. Dans cette méthode, le code Scheme est écrit directement dans la page WEB qui devient toute entière le script CGI. Pour utiliser cette fonctionnalité, OpenScheme est invoqué avec des arguments particuliers sur sa ligne de commande. Une fois reconnue cette option, on se trouve dans un mode de fonctionnement adapté aux scripts CGI et facilitant grandement leur conception. Pour simplifier, on appellera ce mode OHTML.

Cette méthode est très souple et assez efficace. C'est notamment celle que nous avons utilisée pour réaliser le site WEB de ForceSud, une association de réinsertion de Nice, partenaire de Carrefour. Son site est www.force-sud.com.

Cette association propose une galerie marchande organisée en rayons. Le site utilise les cadres (frames) gérés par des programmes en OpenScheme, en mode OHTML. Le moteur base de donnée utilisé est celui d'OpenScheme, qui est extrêmement rapide. Le site propose à ses administrateurs un module de gestion élémentaire qui sera étendu dans les prochains mois.

Ce module d'administration permet de modifier le thème de campagne qui est composé d'un nom, d'un texte et/ou d'une photo. Il est aussi possible de gérer les promotions liées au thème de campagne ; les promotions sont affichées de manière cyclique dans une zone prévue à cet effet dans le magasin vituel, est dans un rayon dont le nom est celui du thème de campagne.

Les utilisateurs remplissent un caddie et peuvent passer commande. La commande est acheminée par courrier électronique chez ForceSud est elle est ensuite traitée (Nous avons proposé un module de paiement sécurisé, mais il n'a pas encore été accepté, le marché étant jugé encore peu réceptif). Le caddie est sauvegardé par un cookie sur le site du client qui le retrouvera à l'identique lors de sa prochaine connexion. Un module permettant aux utilisateurs de gérer plusieurs caddies sera prochainement réalisé ; ce sera bien pratique pour faire des courses types.

Nous étudierons ici les briques nécessaires pour réaliser une telle application.

Code inclus dans la page

La possibilité d'inclure du code Scheme dans une page OHTML repose sur plusieurs caractéristiques :

ï‚· Rendre sous Unix n'importe quel fichier exécutable, en changeant ses droits d'accès (chmod a+x fichier);

ï‚· Spécifier dans un fichier quelle est la ligne de commande à invoquer pour interpréter ce fichier, en la plaçant au début du fichier après la balise #! ;

ï‚· Démarrer OpenScheme dans un mode spécial où il considère que son fichier d'entrée et une page WEB dans laquelle il est susceptible de trouver du code Scheme entre les balises <osm ... osm>. C'est le mode OHTML.

Pour lancer OpenScheme dans le mode OHTML, il est nécessaire de placer sur sa ligne de commande les options --call --cgi. C'est une option générale d'OpenScheme qui permet d'invoquer n'importe quelle fonction au démarrage ; ici, la fonction est --cgi, définie dans le plugin NET de l'environnement.

Nous pouvons même exécuter des pages OHTML en dehors du serveur WEB, mais attention, les résultats seront de l'HTML !

Essayons en écrivant le fichier /home/httpd/cgi-bin/test.cgi suivant :

#! /usr/local/bin/osm-cgi --banner=off --call --cgi

<html>
<body>
Bonjour tout le monde !
</body>
</html>

Rendons cette page exécutable en tapant la commande Unix :

$ chmod a+x /home/httpd/cgi-bin/test.cgi

et exécutons la ; nous obtenons l'affichage :

$ /home/httpd/cgi-bin/test.cgi
Content-type: text/html

<!"Content-type: text/html">
<html>
<body>
Bonjour, tout le monde!
</body>
</html>

<! OpenScheme - 1.3.5 [2000.10.03:08h37]

Copyright (C) 1993 to 2000 by Erian Concept
http://www.open-scheme.com
mail:osm.support@erian-concept.com
>

Maintenant, on peut voir le résultat avec le navigateur WEB netscape ou autre en tapant :

$ netscape http://localhost/cgi-bin/test.cgi

On devrait avoir un bonjour !

Nous allons maintenant ajouter au programme précédent du code Scheme directement dans le script :

#! /usr/local/bin/osm-cgi --banner=off --call --cgi

<html>
<body>
Bonjour tout le monde !

<! balise reconnue par OpenScheme, et replacée par les >
<! informations affichées par le fragment de programme >
<! en utilisant les fonctions d'affichage standard >

<osm
  (let ([aujourdhui (date->vector (date))])
    ; affichage du jour
    (format #t
            "<br>Ajourdhui, on est le ~a/~a/~a"
            (vector-ref aujourdhui 2)
            (vector-ref aujourdhui 1)
            (vector-ref aujourdhui 0))
    ; affichage de l'heure
    (display "<br>il est ")
    (format #t "~a heures,   " (vector-ref aujourdhui 3))
    (format #t "~a minutes,  " (vector-ref aujourdhui 4))
    (format #t "~a secondes, " (vector-ref aujourdhui 5)))
osm>
</body>
</html>

Nous devrions avoir une page WEB qui affiche le jour et l'heure ! Tient, pendant que nous y sommes, y-a-t-il un moyen que cette page soit mise à jour périodiquement, sans aucune intervention de l'utilisateur ? Pour cela, on peut placer dans l'entête une balise HTML spéciale pour automatiser le rafraîchissement :

#! /usr/local/bin/osm-cgi --banner=off --call --cgi

<html>
<head>
  <! spécifie un rafraîchissement automatique >
  < de la page toutes les trois secondes >

  <meta http-equiv="refresh"
        content="3;url=/cgi-bin/test.cgi">
</head>
<body>
 Bonjour tout le monde !

  <osm
   (let ([aujourdhui (date->vector (date))])
    (format #t
            "<br>Aujourd'hui, on est le ~a/~a/~a"
            (vector-ref aujourdhui 2)
            (vector-ref aujourdhui 1)
            (vector-ref aujourdhui 0))
    (display "<br>il est ")
    (format #t "~a heures,   " (vector-ref aujourdhui 3))
    (format #t "~a minutes,  " (vector-ref aujourdhui 4))
    (format #t "~a secondes, " (vector-ref aujourdhui 5)))
  osm>
 </body>
</html>

Le nombre 3 dans ``...content=''3;...`` indique que le rafraîchissement doit avoir lieu toutes les 3 secondes. Nous avons donc maintenant une belle horloge WEB ! (xclock pourra aussi être utilisé :-)

Variables prédéfinies

L'environnement OHTML récupère des variables d'environnement pour les transformer en variables Scheme. Parmi ces variables, on trouve auth-type, content-length, content-type, gateway-interface, path-info, path-translated, query-string, remote-addr, remote-host, remote-ident, remote-user, request-method, request-uri, script-name, server-name, server-port, server-protocol et server-software.

Le lecteur pourra se référer à toutes les sources d'informations données en annexes sur le protocole HTTP pour connaître précisément la signification de ces variables.

Arguments des formulaires

Le mois dernier, nous avons vu que les valeurs des formulaires étaient retournées par la fonction (net:cgi:pase-input) sous la forme d'une liste de couples variable-valeur. Dans le mode de fonctionnement OHTML que nous sommes en train d'examiner, les arguments des formulaires sont placés dans des variables Scheme dont le nom est celui donné aux champs dans le formulaire HTML.

Considérons par exemple le formulaire /home/httpd/html/form.html suivant :

<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.>
   <br>nom:
   <input type=text
    name=nom
    size=10>
   <br>Pr&eacute;nom:
   <input type=text
    name=prenom
    size=10>
   <br>Age:
   <input type=text
    name=age
    size=10>
   <! Bouton OK.>
   <br>
   <input type=submit
          value=ok>
  </form>
 </body>
</html>

Le script /home/httpd/cgi-bin/test1.cgi est invoqué lorsque l'on presse sur le bouton OK :

#! /usr/local/bin/osm-cgi --banner=off --call --cgi

<html>
<body>
 R&eacute;ponse au formulaire !

  <osm
   (format #t "<br>nom           = ~s" nom)
   (format #t "<br>pr&eacute;nom = ~s" prenom)
   (format #t "<br>age           = ~s" age)
  osm>
 </body>
</html>

Nous voyons que le système a su récupérer automatiquement les valeurs des champs et les placer dans les variables Scheme correspondantes. Cependant, cela peut parfois causer un problème d'existence de variables ; en effet, en Scheme, référencer une variable qui n'existe pas est une erreur. OpenScheme propose dont la nouvelle forme spéciale (defined? variable) qui retourne vrai si la variable est définie et faux sinon. Notez que cette nouvelle forme spéciale n'est définie que dans l'environnement OHTML.

Affichage des erreurs

Il arrive assez rarement que les programmeurs commettent des erreurs :-) Cependant, parfois, il est nécessaire de pouvoir afficher de manière correcte ces messages. Par défaut, OpenScheme écrit sur le flux standard d'erreur tous les messages d'erreur. Le serveur WEB Apache collecte ce flux et le redirige vers le fichier de logs associé au serveur. Le nom de ce fichier est par défaut /var/log/httpd/error_log. Ceci peut être modifié avec la variable ErrorLog du fichier de configuration de Apache, situé dans /etc/httpd/conf/httpd.conf.

OpenScheme permet aussi d'installer des filtres et une continuation pour les messages d'erreur. Les filtres sont rassemblés dans une liste de fonctions Scheme destinées soit à traiter le message, soit à restaurer un environnement particulier avant l'affichage du message. Les filtres sont des fonctions ayant un nombre de paramètres variable dont le premier est une chaîne de format et les autres sont les arguments de la chaîne de format (voir la fonction format).

La continuation est une fonction Scheme appelée lorsque tous les filtres ont été préalablement invoqués. En général, il s'agit d'une continuation Scheme replaçant le programme à un endroit connu. La continuation est une fonction ayant un argument entier, le code de retour de l'erreur.

Les filtres se situent dans la liste *error:hooks* et la continuation dans la variable *error:continuation*. Il est possible d'insérer un filtre ``à la main'' en écrivant :

; ajout d'un filtre à la main
(set! *error:hooks*
      (cons ; le nouveau filtre
            (lambda (chaine-de-format . args)
              ; affichage du message sur le port
              ; d'erreur standard

              (vformat *current-error-port*
                       chaine-de-format
                       args))
            *error:hooks*))

Il est aussi possible d'utiliser les fonctions (add-error-hook function) pour ajouter un filtre dans la liste des filtres et (del-error-hook function) pour supprimer un filtre de cette liste.

Si nous souhaitons afficher les erreurs directement dans la page WEB, nous pouvons écrire :

; ajouter le filtre
(add-error-hook
  (lambda
(chaine-de-format . args)
   ; créer le message sous la forme d'une
   ; chaîne de caractères
   (let ([message (vformat
                   #f
                   chaine-de-format
                   args)])
    ; affichage du message au format HTML
    (format #t
            "<br><font color=red>
            Erreur: ~a<font>"
            message))))

Dans l'environnement de traitement automatique des CGI OHTML, un filtre standard est déjà mis en place. On peut s'en convaincre en écrivant dans le fichier /home/httpd/cgi-bin/test2.cgi un programme qui commet une erreur :

#! /usr/local/bin/osm-cgi --banner=off --call --cgi

<html>
<body>
Cette page contient une erreur !

<! la ligne suivante sera un message >
<! d'erreur en rouge >
<osm (+ #\a 1) osm>
</body>
</html>

En affichant la page dans le navigateur, nous obtenons un message d'erreur indiquant une erreur sur l'addition.

Téléchargement

Les pages WEB permettent, bien sûr, d'obtenir des informations à partir d'un serveur. Mais il existe aussi la possibilité de déposer des fichiers dans un serveur. Cette possibilité est connue sous le doux nom d'upload. Cette caractéristique est très utile pour mettre à jour un site.

Le protocole HTML utilisé est quelque peu compliqué, et utilise des documents composites (Multiparts documents). Dans l'environnement OHTML, ceci est géré automatiquement par OpenScheme qui cache à l'utilisateur toute la complexité de l'opération.

Avant de commencer, le téléchargement de fichiers est contrôlé par un formulaire (balise <form> ... </form>) contenant une entrée de type file, comme par exemple <input type=file name=nom>. L'endroit où sera déposé le fichier téléchargé et son nom dépendent de la valeur du champ name de la balise. En fonction du nom donné, nous avons :

ï‚· chemin/ : le nom de fichier entré dans le champ de saisie est conservé, et le fichier est placé dans le répertoire chemin/ dans le serveur. La variable chemin/ est définie dans l'environnement OHTML du script attaché au formulaire et vaut la chaîne de caractères représentant le nom de fichier original.

ï‚· chemin/nom : le fichier téléchargé est placé dans le répertoire chemin/ et nommé nom ; la variable chemin/nom est définie dans l'environnement OHTML avec le nom original du fichier.

ï‚· nom : le fichier téléchargé garde son nom original, et la variable nom vaut le nom original du fichier téléchargé.

Par exemple, concevons un petit outil pour visionner d'images en HTML :

#! /usr/local/bin/osm-cgi --banner=off --call --cgi

<html>
 <body>
  <! Début do formulaire.
     Le script spécifié par action sera
     invoqué lorsque l'on pressera le bouton.

     Enctype détermine le type d'encodage,
     ici, multipart>
  <form enctype='multipart/form-data'
        action=/cgi-bin/test3.cgi
        method=post>
   <! Image précédente.>
   <br>
   <img src=/image>
   <! Champ de saisie du nouveau fichier.>
   <input type=file
    name=../html/image
    size=10>
   <! Bouton OK.>
   <br>
   <input type=submit
          value=ok>
  </form>
 </body>
</html>

Le nom choisi pour le champs de saisie du fichier est ../html/image : dans ce cas, le fichier téléchargé perd son nom original, et il est appelé image dans le répertoire ../html. Attention, ce répertoire est donné par rapport au répertoire d`exécution des scripts CGI.

Les cookies

Les cookies sont des informations libres qui sont déposées dans votre système par votre navigateur. Ces informations sont composées d'un nom de serveur, d'un texte libre et d'une date d'expiration. Certains serveurs s'en servent pour déposer des informations permettant, lors de votre prochaine connexion, de retrouver un certain contexte.

Les cookies ont été très fortement réprouvés par les utilisateurs car ils étaient perçus comme une tentative de violation de leur vie privée. Il faut quand même savoir que les cookies sont des informations construites à partir du serveur, en fonction des pages que vous visualisez. Il n'est pas possible, avec les cookies, d'espionner votre ordinateur et d'y voler des informations.

Dans le site de ForceSud, pas exemple, nous nous servons des cookies pour la gestion du caddie : le caddie est en permanence sauvegardé sur le poste client, avec l'avantage de permettre de retrouver le caddie que l'on avait lors de la dernière connexion.

Le contenu du cookie correspondant à votre serveur est placé par le serveur WEB dans la variable d'environnement HTTP_COOKIE. Pour récupérer la valeur du cookie, il suffit donc d'utiliser la fonction (os:getenv HTTP_COOKIE).

Pour écrire le cookie correspondant à votre serveur, il faut placé sous l'entête :

Content-type: text/html

la ligne :

Set-Cookie: nom=valeur; path=/; expires=Mon, 01-Jan-2001 00:00:00 GMT

Pour modifier l'entête HTML, nous devons positionner la variable *net:cgi:header* de l'environnement OHTML. Voici maintenant un petit programme qui récapitule tout cela :

#! /usr/local/bin/osm-cgi --banner=off --call --cgi

<osm
 ; récupération du cookie
 (define cookie (os:getenv "HTTP_COOKIE"))
osm>

<osm
 ; fabrication du nouveau cookie
(define nouveau (date->string (date)))
osm>

<osm
 ; modification de l'entete de la page pour
 ; positionner le cookie
(set! *net:cgi:header*
       (format #f
               "~a\"~a\"~a"
               "Content-type: text/htm\nSet-Cookie: "
               nouveau
               "; path=/; expires=Mon, 01-Jan-2001 00:00:00 GMT"))
osm>

<html>
 <body>
  <br>
  l'ancien cookie était :
  <osm (display  cookie) osm>
  <br>
  le nouveau est :
  <osm (display nouveau) osm>
 </body>
</html>

La lecture du cookie se fait au travers de la variable d'environnement HTTP_COOKIE, et son écriture, en modifiant l'entête du résultat. Dans une prochaine version d'OpenScheme, le cookie sera automatiquement placé dans la variable Scheme http-cookie. Pour modifier le cookie, il suffira de modifier cette variable avec une nouvelle chaîne de caractère, en utilisant l'opérateur set!.

La première fois qu'on lance le script au travers du navigateur, on devrait y lire :

l'ancien cookie était : #f
le nouveau est : "Tue Dec 5 16:16:41 2000"

En effet, il y a peu de chance pour que vous ayez déjà un cookie pour ce serveur, ce qui explique la valeur fausse de l'ancien cookie. A la seconde exécution, on devrait lire :

l'ancien cookie était : "\"Tue Dec 5 16:16:41 2000\""
le nouveau est : "Tue Dec 5 16:17:14 2000"

Maintenant, si on examine le fichier ~/.netscape/cookies, on devrait y trouver la ligne suivante :

localhost FALSE / FALSE 978307201 "Tue Dec 5 16:16:41 2000"

Si vous utilisez un autre navigateur, vous devriez trouver le cookie dans un autre fichier. Vous vous apercevez aussi, sans doute, que le fichier contient un nombre de lignes impressionnant, retraçant l'histoire de vos balades sur le réseau. Un bon coup de ménage, et vous serez un nouveau-né !

Nous avons maintenant à notre disposition une panoplie d'outils puissants et simples d'utilisation pour écrire des scripts CGI. Ils sont indépendants du système d'exploitation, car OpenScheme l'est aussi.

Nous les utiliserons prochainement pour écrire une petite application WEB complète.

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.