Author: Christian.Queinnec@lip6.fr

## Servlets HTTP

Un servlet est un << module >> à intégrer dans une application serveur pour répondre aux requêtes des clients. Bien qu'un servlet ne soit pas spécifique à un protocole, on utilisera le protocole HTTP pour la communication (voir la figure 21.1). Dans la pratique le terme servlet correspond à un servlet HTTP.
Le moyen classique de construire des pages HTML dynamiques sur un serveur HTTP est d'utiliser des commandes CGI (Common Gateway Interface). Celles-ci prennent en argument une URL pouvant contenir des données provenant d'un formulaire. L'exécution produit alors une page HTML qui est envoyée au client. On trouvera aux liens suivants la description du protocole HTTP et des CGI.

### Lien

http://www.eisti.fr/eistiweb/docs/normes/rfc1945/1945tm.htm

### Lien

http://hoohoo.ncsa.uiuc.edu/docs/cgi/overview.html

C'est un mécanisme un peu lourd car il lance à chaque requête un nouveau programme.
Les servlets HTTP sont lancés une fois pour toutes, et peuvent décoder les arguments du format CGI pour exécuter une requête. Les servlets permettent de profiter des possibilités des navigateurs WEB pour construire l'interface graphique d'une application.

> Figure 21.1 : communication entre un navigateur et un serveur Objective CAML

Nous définissons dans la suite un serveur pour le protocole HTTP. Nous ne traiterons pas l'ensemble des spécifications de ce protocole, mais nous nous limiterons aux quelques fonctions nécessaires à l'implantation d'un serveur mimant le comportement d'une application CGI.
Dans un premier temps, nous définissons un module générique de serveur `Gsd`. Puis nous donnons les fonctions utiles à la définition d'une application de ce serveur générique pour traiter une partie du protocole HTTP.

### Formats HTTP et CGI

Nous voulons obtenir un serveur qui imite le comportement d'une application CGI. Une des premières tâches à réaliser est de décoder le format des requêtes HTTP avec les extensions CGI pour le passage des arguments.
Les clients de ce serveur pourront donc être des navigateurs tels Netscape ou Windows Explorer.

#### Acquisition des requêtes

Les requêtes qui transitent selon le protocole HTTP ont essentiellement trois composantes : une méthode, une URL et des données. Les données répondent à un format particulier.
Nous réalisons dans ce paragraphe un ensemble de fonctions permettant la lecture, le découpage et le décodage des composantes d'une requête. Ces fonctions pourront déclencher l'exception :

    # exception Http_error of string ;;
    exception Http_error of string

##### Décodage

La fonction `decode`, qui utilise l'auxiliaire `rep_xcode`, a pour but de rétablir les caractères qui ont été encodés par le client HTTP : les espaces (qui ont été remplacés par `+`) et certains caractères réservés qui ont été remplacés par leur code hexadécimal.

    # let rec rep_xcode s i =
       let xs = "0x00" in
         String.blit s (i+1) xs 2 2;
         String.set s i (char_of_int (int_of_string xs));
         String.blit s (i+3) s (i+1) ((String.length s)-(i+3));
         String.set s ((String.length s)-2) '\000';
         Printf.printf"rep_xcode1(%s)\n" s ;;
    val rep_xcode : string -> int -> unit = <fun>

    # exception End_of_decode of string ;;
    exception End_of_decode of string

    # let decode s =
       try
         for i=0 to pred(String.length s) do
           match s.[i] with
             '+' -> s.[i] <- ' '
           | '%' -> rep_xcode s i
           | '\000' -> raise (End_of_decode (String.sub s 0 i))
           | _ -> ()
         done;
         s
       with
        End_of_decode s -> s ;;
    val decode : string -> string = <fun>

##### Fonctions de manipulation de chaînes

On définit dans le module `String_plus` les fonctions de découpage de chaînes de caractères :

- `prefix` et `suffix` qui extraient une sous-chaîne à partir d'un index;
- `split` qui retourne la sous-chaîne jusqu'au caractère séparateur;
- `unsplit` qui concatène deux chaînes en ajoutant un caractère séparateur entre elles.

    # module String_plus =
      struct
       let prefix s n =
        try String.sub s 0 n
        with Invalid_argument("String.sub") -> s

       let suffix s i =
        try String.sub s i ((String.length s)-i)
        with Invalid_argument("String.sub") -> ""

       let rec split c s =
        try
         let i = String.index s c in
         let s1, s2 = prefix s i, suffix s (i+1) in
          s1::(split c s2)
        with
         Not_found -> [s]

       let unsplit c ss =
        let f s1 s2 = match s2 with "" -> s1 | _ -> s1^(Char.escaped c)^s2 in
         List.fold_right f ss ""
      end ;;

##### Découpage des données d'un formulaire

Les requêtes sont souvent émises depuis une page HTML contenant un formulaire. Le contenu d'un formulaire est transmis comme une chaîne de caractères contenant les noms et les valeurs associées aux champs du formulaire. La fonction `get_field_pair` transforme cette chaîne en une liste d'association.

    # let get_field_pair s =
       match String_plus.split '=' s with
         [n;v] -> n,v
       | _ -> raise (Http_error ("Bad field format : "^s)) ;;
    val get_field_pair : string -> string * string = <fun>

    # let get_form_content s =
       let ss = String_plus.split '&' s in
         List.map get_field_pair ss ;;
    val get_form_content : string -> (string * string) list = <fun>

##### Lecture et découpage

La fonction `get_query` extrait de la requête la méthode désignée ainsi que l'URL associée qu'elle stocke dans un tableau de chaînes de caractères. On peut ainsi utiliser une application CGI qui récupère ces arguments dans le tableau des arguments de la ligne de commande. La fonction `get_query` utilise l'auxiliaire `get`. La taille maximale d'une requête est arbitrairement limitée, par nous, à 2555 caractères.

    # let get =
       let buff_size = 2555 in
         let buff = String.create buff_size in
           (fun ic -> String.sub buff 0 (input ic buff 0 buff_size)) ;;
    val get : in_channel -> string = <fun>

    # let query_string http_frame =
       try
         let i0 = String.index http_frame ' ' in
         let q0 = String_plus.prefix http_frame i0 in
           match q0 with
             "GET"
             -> begin
                  let i1 = succ i0 in
                  let i2 = String.index_from http_frame i1 ' ' in
                  let q = String.sub http_frame i1 (i2-i1) in
                  try
                    let i = String.index q '?' in
                    let q1 = String_plus.prefix q i in
                    let q = String_plus.suffix q (succ i) in
                      Array.of_list (q0::q1::(String_plus.split ' ' (decode q)))
                  with
                    Not_found -> [|q0;q|]
                 end
           | _ -> raise (Http_error ("Non supported method: "^q0))
       with e -> raise (Http_error ("Unknown request: "^http_frame)) ;;
    val query_string : string -> string array = <fun>

    # let get_query_string ic =
      let http_frame = get ic in
        query_string http_frame;;
    val get_query_string : in_channel -> string array = <fun>

#### Le serveur

Pour obtenir un pseudo-serveur CGI, qui ne sait en l'état traiter que la méthode `GET`, on écrit la fonction `http_service` dont l'argument `fun_serv` est une fonction de traitement des requêtes HTTP telle qu'elle aurait pu être écrite pour une application CGI.

    # module Text_Server = Server (struct type t = string
                                         let to_string x = x
                                         let of_string x = x
                                   end);;

    # module P_Text_Server (P : PROTOCOL) =
     struct
       module Internal_Server = Server (P)

       class http_servlet  n np fun_serv =
         object(self)
           inherit [P.t] Internal_Server.server n np

           method receive_h fd =
             let ic = Unix.in_channel_of_descr fd in
               input_line ic

           method treat fd =
             let oc = Unix.out_channel_of_descr fd in (
               try
                 let request = self#receive_h fd in
                   let args = query_string request in
                     fun_serv oc args;
               with
                 Http_error s -> Printf.fprintf oc "HTTP error : %s <BR>" s
               | _ -> Printf.fprintf oc "Unknown error <BR>" );
               flush oc;
               Unix.shutdown fd Unix.SHUTDOWN_ALL
         end
     end;;

Comme il n'est pas prévu de faire communiquer, via le servlet, de valeurs internes Objective CAML spéciales, on choisit le type string comme type du protocole. Les fonctions `of_string` et `to_string` ne font rien.

    # module Simple_http_server =
       P_Text_Server (struct type t = string
                             let of_string x = x
                             let to_string x = x
                      end);;

On construit alors la fonction principale de lancement du service en construisant une instance de la classe http_servlet.

    # let cgi_like_server port_num fun_serv =
      let sv =   new Simple_http_server.http_servlet port_num 3  fun_serv
      in sv#start;;
    val cgi_like_server : int -> (out_channel -> string array -> unit) -> unit =
      <fun>

#### Test du servlet

Il est toujours utile en cours de développement de pouvoir tester les parties déjà réalisées. Pour cela on réalise un petit serveur HTTP qui envoie tel quel le fichier inscrit dans la requête HTTP qui lui a été adressée. La fonction `simple_serv` envoie le fichier dont le nom suit la requête GET (deuxième élément du tableau des arguments). La fonction `simple_serv` trace les différents arguments passés dans la requête.

    # let send_file oc f =
         let ic = open_in_bin f in
         try
           while true do
             output_byte oc (input_byte ic)
           done
         with End_of_file -> close_in ic;;
    val send_file : out_channel -> string -> unit = <fun>

    # let simple_serv oc args =
       try
         Array.iter (fun x -> print_string (x^" ")) args;
         print_newline();
         send_file oc args.(1)
       with _ -> Printf.printf "erreur\n";;
    val simple_serv : out_channel -> string array -> unit = <fun>

    # let run n = cgi_like_server n simple_serv;;
    val run : int -> unit = <fun>

L'appel run`` `4` `0` `0` `3` lance ce servlet sur le port 4003. Par ailleurs, on lance un navigateur effectuant la requête de chargement de la page `baro.html` sur le port 4003. La figure 21.2 montre l'affichage du contenu de cette page dans le navigateur.

> Figure 21.2 : requête HTTP sur un servlet Objective CAML

Le navigateur a envoyé la requête `GET /baro.html` pour le chargement de la page, et ensuite la requête de chargement de l'image `GET /canard.gif`.

### Interface HTML pour un servlet

Nous utilisons le serveur à la CGI pour construire une interface HTML pour la base de données du chapitre 6 (voir page ??).
Le menu de la fonction `main` est ici affiché sous forme d'une page HTML proposant les mêmes choix. Les réponses aux requêtes sont aussi des pages HTML dynamiquement construites par le servlet. La construction dynamique de pages fait appel à l'utilitaire que nous définissons ci-dessous.

#### Protocole de l'application

Nous utilisons dans notre application plusieurs éléments de plusieurs protocoles :

1. Les requêtes transitent entre un navigateur WEB et notre serveur d'application selon le format des requêtes HTTP.
2. Les données constituant les requêtes obéissent au format de codage des applications CGI.
3. Le contenu des réponses est donné selon le format des pages HTML.
4. Enfin, la nature des requêtes est donnée selon un format spécifique à cette application.

Nous voulons répondre à trois requêtes : demande de la liste des adresses postales, demande de la liste des adresses électroniques et demande de l'état des cotisations entre deux dates données. À chacune d'elles, nous associons respectivement les noms :
`postal_addr`, `email_addr` et `etat_cotis`. Dans ce dernier cas, nous transmettrons également deux chaînes de caractères contenant les dates souhaitées. Ces deux dates correspondent aux valeurs des champs `debut` et `fin` d'un formulaire HTML.
À la première connexion d'un client la page de garde suivante est envoyée. Les noms des requêtes y sont codés sous forme d'ancres HTML.

    <HTML>
    <TITLE> association </TITLE>
    <BODY>
    <HR>
    <H1 ALIGN=CENTER> L'association</H1>
    <P>
    <HR>
    <UL>
    <LI> Liste des
    <A HREF="http://freres-gras.ufr-info-p6.jussieu.fr:12345/postal_addr">
    adresses postales
    </A>
    <LI> Liste des
    <A HREF="http://freres-gras.ufr-info-p6.jussieu.fr:12345/email_addr">
    adresses &eacute;lectroniques
    </A>
    <LI> &Eacute;tat des cotisations<BR>
    <FORM
     method="GET"
     action="http://freres-gras.ufr-info-p6.jussieu.fr:12345/etat_cotis">
    Date de d&eacute;but : <INPUT type="text" name="debut" value="">
    Date de fin : <INPUT type="text" name="fin" value="">
    <INPUT name="action" type="submit" value="Envoyer">
    </FORM>
    </UL>
    <HR>
    </BODY>
    </HTML>

Nous supposerons que cette page est contenue dans le fichier `assoc.html`.

#### Primitives pour HTML

Les fonctionnalités de l'utilitaire HTML sont réunies au sein de la seule classe `print`. Elle possède un champ indiquant le canal de sortie. Elle peut donc aussi bien être utilisée dans le cadre d'une application CGI (où le canal de sortie est la sortie standard) que d'une application utilisant le serveur HTTP défini au paragraphe précédent (où le canal de sortie est une socket de service).
Les méthodes proposées permettent essentiellement d'encapsuler du texte dans des balises HTML. Ce texte est, soit passé directement en argument aux méthodes sous forme de chaîne de caractères, soit produit par une fonction. Par exemple, la méthode principale `page` prend en premier argument une chaîne correspondant à l'en-tête de la page1, et en second argument une fonction affichant le contenu de la page. La méthode `page` produit les balises correspondantes du protocole HTML.
Le nom des méthodes reprend le nom des balises HTML correspondantes en y ajoutant parfois quelques options.

    # class print (oc0:out_channel) =
       object(self)
         val oc = oc0
         method flush () = flush oc
         method str =
           Printf.fprintf oc "%s"
         method page header (body:unit -> unit) =
           Printf.fprintf oc "<HTML><HEAD><TITLE>%s</TITLE></HEAD>\n<BODY>" header;
           body();
           Printf.fprintf oc "</BODY>\n</HTML>\n"
         method p () =
           Printf.fprintf oc "\n<P>\n"
         method br () =
           Printf.fprintf oc "<BR>\n"
         method hr () =
           Printf.fprintf oc "<HR>\n"
         method hr () =
           Printf.fprintf oc "\n<HR>\n"
         method h i s =
           Printf.fprintf oc "<H%d>%s</H%d>" i s i
         method h_center i s =
           Printf.fprintf oc "<H%d ALIGN=\"CENTER\">%s</H%d>" i s i
         method form url (form_content:unit -> unit) =
           Printf.fprintf oc "<FORM method=\"post\" action=\"%s\">\n" url;
           form_content ();
           Printf.fprintf oc "</FORM>"
         method input_text =
           Printf.fprintf oc
             "<INPUT type=\"text\" name=\"%s\" size=\"%d\" value=\"%s\">\n"
         method input_hidden_text =
           Printf.fprintf oc "<INPUT type=\"hidden\" name=\"%s\" value=\"%s\">\n"
         method input_submit =
           Printf.fprintf oc "<INPUT name=\"%s\" type=\"submit\" value=\"%s\">"
         method input_radio =
           Printf.fprintf oc "<INPUT type=\"radio\" name=\"%s\" value=\"%s\">\n"
         method input_radio_checked =
           Printf.fprintf oc
             "<INPUT type=\"radio\" name=\"%s\" value=\"%s\" CHECKED>\n"
         method option =
           Printf.fprintf oc "<OPTION> %s\n"
         method option_selected opt =
           Printf.fprintf oc "<OPTION SELECTED> %s" opt
         method select name options selected =
           Printf.fprintf oc "<SELECT name=\"%s\">\n" name;
           List.iter
             (fun s -> if s=selected then self#option_selected s else self#option s)
             options;
           Printf.fprintf oc "</SELECT>\n"
         method options selected =
           List.iter
             (fun s -> if s=selected then self#option_selected s else self#option s)
       end ;;

Nous supposerons que cet utilitaire est fourni par le module `Html_frame`.

### Pages dynamiques pour la gestion d'associations

Pour chacune des trois requêtes de l'application, il faut construire une page en réponse. Nous utilisons pour cela l'utilitaire `Html_frame` donné ci-dessus. Ce qui signifie que les pages ne sont pas réellement construites, mais que leurs différents composants sont émis séquentiellement sur le canal de sortie.
Nous ajoutons une page (virtuelle) supplémentaire qui est retournée en réponse à une requête erronée ou incomprise.

##### Page d'erreur

La fonction `print_error` prend en argument une fonction d'émission de page HTML (i.e. : une instance de la classe `print`) et une chaîne de caractères contenant le message d'erreur.

    # let print_error (print:Html_frame.print) s =
      let print_body() =
       print#str s; print#br()
      in
       print#page "Erreur" print_body ;;
    val print_error : Html_frame.print -> string -> unit = <fun>

Toutes nos fonctions d'émission d'une réponse à une requête auront en paramètre un premier argument contenant la fonction d'émission d'une page HTML.

##### Liste des adresses postales

La page composant la réponse à la demande de la liste des adresses postales est obtenue en formatant la liste des chaînes de caractères obtenue par la fonction `adresses_postales` définie pour la base d'adhérents (voir page ??). Nous supposons que cette fonction, et toute autre concernant directement les requêtes sur la base de données, ont été définies dans un module nommé `Assoc`.
Pour émettre cette liste, nous utilisons une fonction de sortie de lignes simples :

    # let print_lines (print:Html_frame.print) ls =
      let print_line l =  print#str l; print#br() in
       List.iter print_line ls ;;
    val print_lines : Html_frame.print -> string list -> unit = <fun>

La fonction de réponse à la demande des adresses postales est :

    # let print_adresses_postales print db =
       print#page "Adresses postales"
                  (fun () -> print_lines print (Assoc.adresses_postales db))
      ;;
    val print_adresses_postales : Html_frame.print -> Assoc.data_base -> unit =
      <fun>

Outre la fonction d'émission de page, la fonction `print_adresses_postales` prend en second paramètre la base de données.

##### Liste des adresses électroniques

Cette fonction est construite sur le même principe que celle donnant la liste des adresses postales sauf qu'elle fait appel à la fonction `adresses_electroniques` du module `Assoc` :

    # let print_adresses_electroniques print db =
       print#page "Adresses &eacute;lectroniques"
                  (fun () -> print_lines print (Assoc.adresses_electroniques db)) ;;
    val print_adresses_electroniques :
      Html_frame.print -> Assoc.data_base -> unit = <fun>

##### État des cotisations

C'est encore le même principe qui gouverne la définition de cette fonction : récupérer les données correspondant à la requête (qui ici est un couple), puis émettre les chaînes de caractères correspondantes.

    # let print_etat_cotisations print db d1 d2 =
      let ls, t = Assoc.etat_cotisations db d1 d2 in
      let page_body() =
       print_lines print ls;
       print#str ("Total : "^(string_of_float t));
       print#br()
      in
       print#page "&Eacute;tat des cotisations" page_body ;;
    val print_etat_cotisations :
      Html_frame.print -> Assoc.data_base -> string -> string -> unit = <fun>

### Analyse des requêtes et réponse

Nous définissons deux fonctions de production de réponse en fonction d'une requête HTTP. La première (`print_get_answer`) répond à une requête supposée formulée par une méthode GET du protocole HTTP. La seconde aiguille la production de la réponse selon la méthode de requête utilisée.
Ces deux fonctions prennent en second argument un tableau de chaînes de caractères contenant les éléments de la requête HTTP tels qu'ils ont été analysés par la fonction `get_query_string` (voir page ??). Le premier élément du tableau contient la méthode et le second le nom de la requête sur la base.
Dans le cas d'une demande d'état des cotisations, les dates de début et de fin composant la requête sont contenues dans les deux champs du formulaire associé à cette demande. Les données du formulaire sont contenues dans le troisième champ du tableau qui doit être décomposé par la fonction `get_form_content` (voir page ??).

    # let print_get_answer print q db =
       match q.(1) with
       | "/postal_addr" -> print_adresses_postales print db
       | "/email_addr" -> print_adresses_electroniques print db
       | "/etat_cotis"
         -> let nvs = get_form_content q.(2) in
            let d1 = List.assoc "debut" nvs
            and d2 = List.assoc "fin" nvs in
              print_etat_cotisations print db d1 d2
       | _ -> print_error print ("Unknown request: "^q.(1)) ;;
    val print_get_answer :
      Html_frame.print -> string array -> Assoc.data_base -> unit = <fun>

    # let print_answer print q db =
       try
         match q.(0) with
          "GET" -> print_get_answer print q db
         | _ -> print_error print ("Unsupported method : "^q.(0))
       with
         e
         -> let s = Array.fold_right (^) q "" in
            print_error print ("Some thing wrong with request: "^s) ;;
    val print_answer :
      Html_frame.print -> string array -> Assoc.data_base -> unit = <fun>

### Programme principal et application

Le programme principal de l'application est un exécutable autonome paramétré par le numéro de port du service. La base de données est lue avant le lancement du serveur. La fonction de service est obtenue à partir de la fonction `print_answer` définie ci-dessus et de la fonction générique de serveur HTTP `cgi_like_server` définie au paragraphe précédent (voir page ??). Cette dernière est donnée par le module `Servlet`.

    # let get_port_num() =
       if (Array.length Sys.argv) < 2 then 12345
       else
         try int_of_string Sys.argv.(1)
         with _ -> 12345 ;;
    val get_port_num : unit -> int = <fun>

    # let main() =
       let db = Assoc.read_base "assoc.dat" in
       let assoc_answer oc q = print_answer (new Html_frame.print oc) q db in
         Servlet.cgi_like_server (get_port_num()) assoc_answer ;;
    val main : unit -> unit = <fun>

Pour obtenir l'application complète, nous rassemblons dans un fichier `httpassoc.ml` les définitions des fonctions d'affichage. Ce fichier se termine par un appel à la fonction `main` :

    main() ;;

Nous pouvons alors produire un exécutable nommé `assocd` par la commande de compilation :

    ocamlc -thread -custom -o assocd unix.cma threads.cma \
           gsd.cmo servlet.cmo html_frame.cmo string_plus.cmo assoc.cmo \
           httpassoc.ml -cclib -lunix -cclib -lthreads

Ne reste plus alors qu'à lancer le serveur, charger la page HTML2 contenue dans le fichier `assoc.html` donné au début de ce paragraphe (page ??) et cliquer.
La figure 21.3 montre un exemple d'utilisation.

> Figure 21.3 : requête HTTP sur un servlet Objective CAML

Le navigateur effectue une première connexion sur le servlet qui lui envoie la page de menu. Une fois les champs de saisie remplis, l'utilisateur envoie une nouvelle requête qui contient les champs saisis. Ceux-ci sont décodés, et le serveur fait un accès à la base de données de l'association pour récupérer l'information demandée qui est traduite en HTML, envoyée au client qui affiche cette nouvelle page.

### Pour en faire plus

Cette application a de nombreux prolongements. Tout d'abord le protocole HTTP utilisé est trop simple par rapport aux nouvelles versions qui ajoutent un en-tête informatif sur le type, le contenu et la longueur de la page envoyée. De même la méthode `POST`, qui autorise une modification sur le serveur, n'est pas intégrée3
Pour pouvoir décrire le type et le contenu des pages renvoyées, il est nécessaire d'intégrer dans les servlets la convention MIME utilisée pour la description des documents comme pour les documents attachés dans les courriers électroniques.
La transmission d'images, comme à la figure 21.2, permet aussi de construire des interfaces pour les jeux à 2 joueurs (voir chapitre 17), où l'on associe des liens aux dessins des cases à jouer. Comme l'arbitre du jeu connaît les coups légaux, seules les cases valides sont associées à un lien.
L'extension MIME autorise aussi à définir de nouveaux types de données. On intègre alors le protocole interne des valeurs Objective CAML comme un nouveau type MIME. Ces valeurs ne sont réellement compréhensibles que par un programme Objective CAML utilisant le même protocole interne. Ainsi une requête d'un client sur une valeur Objective CAML distante est effectuée via une requête HTTP. On peut ainsi passer dans la page HTML une fermeture sérialisée qui sera passée en tant qu'argument de la requête. Celle-ci, une fois reconstruite du côté du serveur s'exécute pour fournir le résultat escompté.