Ocsigen

1. The basics: main services, parameters, forms, cooperative programming

Base principles

Unlike many other Web programming techniques (CGI, PHP, ...), with Eliom, you don't write one file for each URL, but a caml module (cmo or cma) for the whole Web site.

The Eliom_services module allows to create new entry points to your Web site, called services. In general, services are attached to an URL and generate a Web page. They are represented by OCaml values, on which you must register a function that will generate a page. There are several ways to create pages for Eliom. This tutorial is mainly using Eliom_predefmod.Xhtml, a module allowing to register xhtml pages statically typed using OCaml's polymorphic variants. The XHTML.M module defines functions to construct xhtml pages using that type system. As the Eliom_predefmod.Xhtml redefines some functions of XHTML.M, open the modules in this order:


open Lwt
open XHTML.M
open Eliom_services
open Eliom_parameters
open Eliom_sessions
open Eliom_predefmod.Xhtml

Lwt (lightweight threads) is the cooperative thread library used by Ocsigen (see later).

Here is an example showing how to create a new service and register a page created with XHTML.M. Use the function Eliom_predefmod.Xhtml.register_new_service:


let coucou =
  register_new_service
    ~path:["coucou"]
    ~get_params:unit
    (fun _ () () ->
      return
        (html
           (head (title (pcdata "")) [])
           (body [h1 [pcdata "Hallo!"]])))

The same, written with fully qualified values:


let coucou =
  Eliom_predefmod.Xhtml.register_new_service
    ~path:["coucou"]
    ~get_params:Eliom_parameters.unit
    (fun _ () () ->
      Lwt.return
        (XHTML.M.html
          (XHTML.M.head (XHTML.M.title (XHTML.M.pcdata "")) [])
          (XHTML.M.body [XHTML.M.h1 [XHTML.M.pcdata "Hallo!"]])))

As you can see, return is a function from Lwt. Use it like this for now, and see later for more advanced use.

Now you can compile your file (here tutorial.ml) by typing :

ocamlc -thread -I /path_to/ocsigen/ -I /path_to/lwt/ -c tutorial.ml

If you use findlib, you can also use the following command line:

ocamlfind ocamlc -thread -package ocsigen -c tutorial.ml

Replace /path_to/ocsigen/ by the directory where Ocsigen libraries are installed (that contains eliom.cma, staticmod.cmo, etc.), usually something like /usr/lib/ocaml/3.09.3/ocsigen or /usr/local/lib/ocaml/3.09.3/ocsigen or /opt/godi/lib/ocaml/site-lib/ocsigen.

Add the following lines to Ocsigen's config file (/etc/ocsigen/ocsigen.conf most of the time):

<host> <site path="examples"> <eliom module="/path_to/tutoeliom.cmo" /> </site> </host>

Note that if your module has a findlib META file, it is also possible to do:

<host> <site path="examples"> <eliom findlib-package="package-name" /> </site> </host>

Then run ocsigen. You should see your page at url http://your_server/examples/coucou. See this example here.

NB: See the default config file to see how to set the port on which your server is running, the user who runs it, the path of the log files, etc.

Here is a sample Makefile for your modules.

Static typing of XHTML with XHTML.M

Typing of xhtml with XHTML.M and Eliom_predefmod.Xhtml is very strict and compels you to respect xhtml 1.1 standard (with some limitations). For example if you write:

(html
   (head (title (pcdata "")) [])
   (body [pcdata "Hallo"]))

You will get the following error message:

This expression has type ([> `PCDATA ] as 'a) XHTML.M.elt but is here used with type ([< XHTML.M.block ] as 'b) XHTML.M.elt Type 'a is not compatible with type 'b = [< `Address | `Blockquote | `Del | `Div | `Dl | `Fieldset

`Form `H1 `H2 `H3 `H4 `H5 `H6 `Hr `Ins
`Noscript `Ol `P `Pre `Script `Table `Ul ]

'b is the type of block tags (only tags allowed inside <body>), but PCDATA (i.e. raw text) is not a block tag.

In XHTML, some tags cannot be empty. For example <table> must contain at least one row. To enforce this, the XHTML.M.table function takes two parameters: the first one is the first row, the second one is a list containing all the other rows. (same thing for <tr> <form> <dl> <ol> <ul> <dd> <select> ...) This forces the user to handle the empty list case specially and thus make the output conform to the DTD.

A more detailed introduction to XHTML.M is available here. Take a quick look at it before continuing this tutorial.

Alternate syntax

If you prefer using a syntax closer to html, you can write:


let coucou1 =
  Eliom_predefmod.Xhtml.register_new_service
    ~path:["coucou1"]
    ~get_params:Eliom_parameters.unit
    (fun _ () () ->
      return
        << <html>
             <head><title></title></head>
             <body><h1>Coucou</h1></body>
           </html> >>)

To compile this syntax, you need a camlp4 syntax extension:

ocamlc -I /path_to/ocsigen/ -pp "camlp4o /path_to/ocsigen/xhtmlsyntax.cma -loc loc" -c tutorial.ml

(Replace /path_to/ocsigen/ by the directory where ocsigen is installed). See this example here.

As the syntax extension is using the same typing system as XHTML.M, You can mix the two syntaxes (see later).

Warning: The two syntaxes are not equivalent for typing. Using the syntax extension will do less checking. For example the following code is accepted but not valid regarding xhtml's dtd (because <head> must contain a title):

<< <html>
     <head></head>
     <body><h1>plop</h1></body>
   </html> >>

We recommend you to use the functions from XHTML.M, as you will (almost) always get valid xhtml. Use the syntax extension for example to enclose already created pieces of html, and check your pages validity with the W3C validator.

More info on XHTML.M.

More info on the syntax extension.

Eliom and OCamlDuce

If OCamlDuce is installed on your system, it is now possible to use it instead of XHTML.M and Eliom_parameters.Xhtml to typecheck your pages. You will get a stronger type checking and more flexibility (easier to use other XML types, to parse incoming XML data, etc.).

To use it, make sure that you have Eliom compiled with OCamlDuce support. Then dynlink ocamlduce.cma and eliomduce.cma from the configuration file (after eliom.cma). Then use Eliom_duce.Xhtml instead of Eliom_predefmod.Xhtml to register your pages.

Here is an example:

open Lwt

let s =
  Eliom_duce.Xhtml.register_new_service
    ~path:[""]
    ~get_params:unit
    (fun sp () () ->
      return
        {{ <html>
             [<head> [<title> ""]
              <body> [<h1> "This page has been type checked by OCamlDuce"]] }}) 

Eliom_predefmod.HtmlText

If you want to register untyped (text) pages, use the functions from Eliom_predefmod.HtmlText, for example Eliom_predefmod.Text.register_new_service :


let coucoutext =
  Eliom_predefmod.HtmlText.register_new_service
    ~path:["coucoutext"]
    ~get_params:Eliom_parameters.unit
    (fun sp () () ->
      return
        ("<html>n'importe quoi "^
         (Eliom_predefmod.HtmlText.a coucou sp "clic" ())^
         "</html>"))

Try it.

More examples

Services registered with register_new_service are available for all users. We call them public services.

Page generation may have side-effects:


let count =
  let next =
    let c = ref 0 in
      (fun () -> c := !c + 1; !c)
  in
  register_new_service
    ~path:["count"]
    ~get_params:unit
    (fun _ () () ->
      return
        (html
         (head (title (pcdata "counter")) [])
         (body [p [pcdata (string_of_int (next ()))]])))

See this example here.

As usual in OCaml, you can forget labels when the application is total:


let hello =
  register_new_service
    ["dir";"hello"]  (* the url dir/hello *)
    unit
    (fun _ () () ->
      return
        (html
         (head (title (pcdata "Hello")) [])
         (body [h1 [pcdata "Hello"]])))

See this example here.

The last example shows how to define the default page for a directory. (Note that ["rep";""] means the default page of the directory rep/)


let default = register_new_service ["rep";""] unit
  (fun _ () () ->
    return
     (html
      (head (title (pcdata "")) [])
      (body [p [pcdata "default page. rep is redirected to rep/"]])))

See default.

Remarks on paths

["foo";"bar"] corresponds to the URL foo/bar.
["dir";""] corresponds to the URL dir/ (that is: the default page of the directory dir).
The empty list [] is equivalent to [""].

Warning: You cannot create a service on path ["foo"] (URL foo, without slash at the end) and another on path ["foo";"bar"] (URL foo/bar) because foo can not be both a directory and a file. Be also careful not to use a string as a directory with Eliom, if it is a file for Staticmod (and vice versa).

Warning: ["foo";"bar"] is not equivalent to ["foo/bar"]. In the latter, the "/" will be encoded in the URL.

Parameters

Typed parameters

The parameter labeled get_params indicates the type of GET parameters for the page (that is, parameters present in the URL). unit means that the page does not take any GET parameter.

Functions implementing services are called service handlers. They take three parameters. The first one has type Eliom_sessions.server_params and corresponds to server parameters (user-agent, ip, current-url, etc. - see later in that section for examples of use), the second one is for GET parameters (that is, parameters in the URL) and the third one for POST parameters (parameters in the body of the HTTP request).

Here is an example of a service with GET parameters:


let writeparams _ (i1, (i2, s1)) () =
  return
   (html
    (head (title (pcdata "")) [])
    (body [p [pcdata "You sent: ";
              strong [pcdata (string_of_int i1)];
              pcdata ", ";
              strong [pcdata (string_of_int i2)];
              pcdata " and ";
              strong [pcdata s1]]]))

let coucou_params = register_new_service
    ~path:["coucou"]
    ~get_params:(int "i" ** (int "ii" ** string "s"))
    writeparams

Note that the URLs of coucou and coucou_params differ only by parameters. Url http://your_server/examples/coucou will run the first one,
http://your_server/examples/coucou?i=42&ii=17&s=krokodile will run the second one.
If i is not an integer, the server will display an error-message (try to change the value in the URL).
Here, int, string and are functions defined in the Eliom_parameters module.
Warning: The infix function ( ) is to be used to construct pairs (not tuples).

The following examples shows how to create a service with "suffix" service (taking the end of the URL as a parameter, as wikis do very often) and how to get server information:


let uasuffix =
  register_new_service
    ~path:["uasuffix"]
    ~get_params:(suffix (int "year" ** int "month"))
    (fun sp (year, month) () ->
      return
       (html
        (head (title (pcdata "")) [])
        (body
           [p [pcdata "The suffix of the url is ";
               strong [pcdata ((string_of_int year)^"/"
                               ^(string_of_int month))];
               pcdata ", your user-agent is ";
               strong [pcdata (Eliom_sessions.get_user_agent sp)];
               pcdata ", your IP is ";
               strong [pcdata (Eliom_sessions.get_remote_ip sp)]]])))

This service will answer to URLs like http://.../uasuffix/2000/11.

See uasuffix

Suffix parameters have names, because we can create forms towards these services. uasuffix/2000/11 is equivalent to uasuffix/?year=2000&month=11.

suffix_prod allows to take both a suffix and other parameters.
all_suffix allows to take the end of the suffix as a string list.


let isuffix =
  register_new_service
    ~path:["isuffix"]
    ~get_params:(suffix_prod (int "suff" ** all_suffix "endsuff") (int "i"))
    (fun sp ((suff, endsuff), i) () ->
      return
       (html
        (head (title (pcdata "")) [])
        (body
           [p [pcdata "The suffix of the url is ";
               strong [pcdata (string_of_int suff)];
               pcdata " followed by ";
               strong [pcdata (Ocsigen_lib.string_of_url_path ~encode:false endsuff)];
               pcdata " and i is equal to ";
               strong [pcdata (string_of_int i)]]])))

See isuffix.

If you want parameters in the path but not always at the end, use the Eliom_parameters.const parameter specification. It will match for example URLs like <tt>/param1/const/param2</tt>. Example:


let constfix =
  register_new_service
    ~path:["constfix"]
    ~get_params:(suffix (string "s1" ** (Eliom_parameters.suffix_const "toto" ** string "s2")))
    (fun _ (s1, ((), s2))  () ->
      return
        (html
          (head (title (pcdata "")) [])
          (body [h1
                   [pcdata "Suffix with constants"];
                 p [pcdata ("Parameters are "^s1^" and "^s2)]])))

Page with constants in suffix.

The following example shows how to use your own types :


type mysum = A | B
let mysum_of_string = function
  | "A" -> A
  | "B" -> B
  | _ -> raise (Failure "mysum_of_string")
let string_of_mysum = function
  | A -> "A"
  | B -> "B"

let mytype =
  Eliom_predefmod.Xhtml.register_new_service
    ~path:["mytype"]
    ~get_params:
      (Eliom_parameters.user_type mysum_of_string string_of_mysum "valeur")
    (fun _ x () ->
      let v = string_of_mysum x in
      return
        (html
         (head (title (pcdata "")) [])
         (body [p [pcdata (v^" is valid. Now try with another value.")]])))

See mytype.

Untyped parameters

If you want a service that answers to requests with any parameters, use the Eliom_parameters.any value. The service will get an association list of strings. Example:


let raw_serv = register_new_service
    ~path:["any"]
    ~get_params:Eliom_parameters.any
  (fun _ l () ->
    let ll =
      List.map
        (fun (a,s) -> << <strong>($str:a$, $str:s$)</strong> >>) l
    in
    return
     << <html>
          <head><title></title></head>
          <body>
          <p>
            You sent:
            $list:ll$
          </p>
          </body>
        </html> >>)

Try raw_serv.

Catching errors

You can catch parameters typing errors and write your own error messages using the optional parameter error_handler. Example:



let catch = register_new_service
    ~path:["catch"]
    ~get_params:(int "i")
    ~error_handler:(fun sp l ->
      return
        (html
         (head (title (pcdata "")) [])
         (body [p [pcdata ("i is not an integer.")]])))
    (fun _ i () ->
      let v = string_of_int i in
      return
        (html
           (head (title (pcdata "")) [])
           (body [p [pcdata ("i is an integer: "^v)]])))

error_handler takes as parameters the usual sp, and a list of pairs (n,ex), where n is the name of the wrong parameter, and ex is the exception that has been raised while parsing its value.

See catch (change the value of the parameter).

To create a link (<a>), use the Eliom_predefmod.Xhtml.a function (or Eliom_duce.Xhtml.a, etc), as in these examples:


let links = register_new_service ["rep";"links"] unit
 (fun sp () () ->
   return
    (html
     (head (title (pcdata "Links")) [])
     (body
       [p
        [Eliom_predefmod.Xhtml.a coucou sp [pcdata "coucou"] (); br ();
         Eliom_predefmod.Xhtml.a hello sp [pcdata "hello"] (); br ();
         Eliom_predefmod.Xhtml.a default sp
           [pcdata "default page of the dir"] (); br ();
         Eliom_predefmod.Xhtml.a uasuffix sp
           [pcdata "uasuffix"] (2007,06); br ();
         Eliom_predefmod.Xhtml.a coucou_params sp
           [pcdata "coucou_params"] (42,(22,"ciao")); br ();
         Eliom_predefmod.Xhtml.a raw_serv sp
           [pcdata "raw_serv"] [("sun","yellow");("sea","blue and pink")]; br ();
         Eliom_predefmod.Xhtml.a
           (new_external_service
              ~prefix:"http://fr.wikipedia.org"
              ~path:["wiki";""]
              ~get_params:(suffix (all_suffix "suff"))
              ~post_params:unit ())
           sp
           [pcdata "OCaml on wikipedia"]
           ["OCaml"]; br ();
         XHTML.M.a
           ~a:[a_href (uri_of_string "http://en.wikipedia.org/wiki/OCaml")]
           [pcdata "OCaml on wikipedia"]
       ]])))

See links.

If you open Eliom_predefmod.Xhtml after XHTML.M, Eliom_predefmod.Xhtml.a will mask XHTML.M.a. Thus you can avoid to write fully qualified values most of the time.

Eliom_predefmod.Xhtml.a takes as first parameter the service you want to link to. Note that to create a (relative) link we need to know the current URL. That's why the function a takes sp as second parameter.

The third parameter is the text of the link. The last parameter is for GET parameters you want to put in the link. The type of this parameter and the name of GET parameters depend on the service you link to.

The links to Wikipedia shows how to define an external service (here it uses a suffix URL). For an external service without parameters, you can use the low level function XHTML.M.a, if you don't want to create an external service explicitely. Note that the path must be a list of strings. Do not write ["foo/bar"], but ["foo";"bar"], otherwise, the "/" will be encoded in the URL.

If you want to create (mutually or not) recursive pages, create the service using Eliom_services.new_service first, then register it in the table using (for example) Eliom_predefmod.Xhtml.register:


let linkrec = Eliom_services.new_service ["linkrec"] unit ()

let _ = Eliom_predefmod.Xhtml.register linkrec
    (fun sp () () ->
      return
       (html
        (head (title (pcdata "")) [])
        (body [p [a linkrec sp [pcdata "click"] ()]])))


See linkrec.

The server will fail on startup if there are any unregistered

services.

Forms

Forms towards services

The function Eliom_predefmod.Xhtml.get_form allows to create a form that uses the GET method (parameters in the URL). It works like Eliom_predefmod.Xhtml.a but takes a function that creates the form from the parameters names as parameter.


let create_form =
  (fun (number_name, (number2_name, string_name)) ->
    [p [pcdata "Write an int: ";
        Eliom_predefmod.Xhtml.int_input ~input_type:`Text ~name:number_name ();
        pcdata "Write another int: ";
        Eliom_predefmod.Xhtml.int_input ~input_type:`Text ~name:number2_name ();
        pcdata "Write a string: ";
        Eliom_predefmod.Xhtml.string_input ~input_type:`Text ~name:string_name ();
        Eliom_predefmod.Xhtml.string_input ~input_type:`Submit ~value:"Click" ()]])

let form = register_new_service ["form"] unit
  (fun sp () () ->
     let f = Eliom_predefmod.Xhtml.get_form coucou_params sp create_form in
     return
       (html
         (head (title (pcdata "")) [])
         (body [f])))

See the function form in action.

Note that if you want to use typed parameters, you cannot use functions like XHTML.M.input to create your forms (if you want to use parameters defined with Eliom_parameters.any, see later). Indeed, parameter names are typed to force them be used properly. In our example, number_name has type int param_name and must be used with int_input (or other widgets), whereas string_name has type string param_name and must be used with string_input (or other widgets). All functions for creating form widgets are detailed here.

For untyped forms, you may use functions from XHTML.M (or OCamlDuce's syntax, or whatever syntax you are using) or functions which name is prefixed by "raw_". Here is a form linking to our (untyped) service raw_serv.


let raw_form = register_new_service
    ~path:["anyform"]
    ~get_params:unit
    (fun sp () () ->
      return
        (html
           (head (title (pcdata "")) [])
           (body
              [h1 [pcdata "Any Form"];
               Eliom_predefmod.Xhtml.get_form raw_serv sp
                 (fun () ->
                   [p [pcdata "Form to raw_serv: ";
                       Eliom_predefmod.Xhtml.raw_input ~input_type:`Text ~name:"plop" ();
                       Eliom_predefmod.Xhtml.raw_input ~input_type:`Text ~name:"plip" ();
                       Eliom_predefmod.Xhtml.raw_input ~input_type:`Text ~name:"plap" ();
                       Eliom_predefmod.Xhtml.string_input ~input_type:`Submit ~value:"Click" ()]])
                ])))

Try this form.

POST parameters

By default Web page parameters are transferred in the URL (GET parameters). A web page may also expect POST parameters (that is, parameters that are not in the URL but in the body of the HTTP request). Use this if you don't want the user to be able to bookmark the URL with parameters, for example if you want to post some data that will change the state of the server (payment, database changes, etc). When designing a Web site, think carefully about the choice between GET or POST method for each service!

When you register a service with POST parameters, you must first register a service (fallback) without these parameters (for example that will answer if the page is reloaded without the hidden parameters, or if it is bookmarked).


let no_post_param_service =
  register_new_service
    ~path:["post"]
    ~get_params:unit
    (fun _ () () ->
      return
        (html
         (head (title (pcdata "")) [])
         (body [h1 [pcdata
                      "Version of the page without POST parameters"]])))

let my_service_with_post_params =
  register_new_post_service
    ~fallback:no_post_param_service
    ~post_params:(string "value")
    (fun _ () value ->
      return
        (html
         (head (title (pcdata "")) [])
         (body [h1 [pcdata value]])))

Services may take both GET and POST parameters:


let get_no_post_param_service =
  register_new_service
    ~path:["post2"]
    ~get_params:(int "i")
    (fun _ i () ->
      return
        (html
         (head (title (pcdata "")) [])
         (body [p [pcdata "No POST parameter, i:";
                   em [pcdata (string_of_int i)]]])))

let my_service_with_get_and_post = register_new_post_service
  ~fallback:get_no_post_param_service
  ~post_params:(string "value")
  (fun _ i value ->
    return
      (html
         (head (title (pcdata "")) [])
         (body [p [pcdata "Value: ";
                   em [pcdata value];
                   pcdata ", i: ";
                   em [pcdata (string_of_int i)]]])))

POST forms

To create a POST form, use the Eliom_predefmod.Xhtml.post_form function. It is similar to Eliom_predefmod.Xhtml.get_form with an additional parameter for the GET parameters you want to put in the URL (if any). Here, form2 is a page containing a form to the service post (using XHTML.M's functions) and form3 (defined using the syntax extension) contains a form to post2, with a GET parameter. form4 is a form to an external page.


let form2 = register_new_service ["form2"] unit
  (fun sp () () ->
     let f =
       (Eliom_predefmod.Xhtml.post_form my_service_with_post_params sp
          (fun chaine ->
            [p [pcdata "Write a string: ";
                string_input ~input_type:`Text ~name:chaine ()]]) ()) in
     return
       (html
         (head (title (pcdata "form")) [])
         (body [f])))

let form3 = register_new_service ["form3"] unit
  (fun sp () () ->
     let f  =
       (Eliom_predefmod.Xhtml.post_form my_service_with_get_and_post sp
          (fun chaine ->
            <:xmllist< <p> Write a string:
                    $string_input ~input_type:`Text ~name:chaine ()$ </p> >>)
          222) in
     return
       << <html>
            <head><title></title></head>
            <body>$f$</body></html> >>)

let form4 = register_new_service ["form4"] unit
  (fun sp () () ->
     let f  =
       (Eliom_predefmod.Xhtml.post_form
          (new_external_service
             ~prefix:"http://www.petizomverts.com"
             ~path:["zebulon"]
             ~get_params:(int "i")
             ~post_params:(string "chaine") ()) sp
          (fun chaine ->
            <:xmllist< <p> Write a string:
                     $string_input ~input_type:`Text ~name:chaine ()$ </p> >>)
          222) in
     return
       (html
        (head (title (pcdata "form")) [])
        (body [f])))

See the urls: post without parameter, post2 without POST parameter, form2, form3, form4.

Threads

Remember that a Web site written with Eliom is an OCaml application. This application must be able to handle several requests at the same time, in order to prevent a single request from slowing down the whole server. To make this possible, Ocsigen is using cooperative threads (implemented in monadic style by Jérôme Vouillon) which make them really easy to use (see Lwt module).

Take time to read the documentation about Lwt right now if you want to understand the following of this tutorial.

As it doesn't cooperate, the following page will stop the server for 5 seconds. No one will be able to query the server during this period:

let looong =
  register_new_service
    ~path:["looong"]
    ~get_params:unit
    (fun sp () () ->
      Unix.sleep 5;
      return
        (html
          (head (title (pcdata "")) [])
          (body [h1 [pcdata "Ok now, you can read the page."]])))

To solve this problem, use a cooperative version of sleep:


let looong =
  register_new_service
    ~path:["looong"]
    ~get_params:unit
    (fun sp () () ->
      Lwt_unix.sleep 5.0 >>= fun () ->
      return
        (html
          (head (title (pcdata "")) [])
          (body [h1 [pcdata
                   "Ok now, you can read the page."]])))

If you want to use, say, a database library that is not written in cooperative way, but is thread safe for preemptive threads, use the Lwt_preemptive module to detach the computation. In the following example, we simulate the request by a call to Unix.sleep:


let looong2 =
  register_new_service
    ~path:["looong2"]
    ~get_params:unit
    (fun sp () () ->
      Lwt_preemptive.detach Unix.sleep 5 >>= fun () ->
      return
        (html
          (head (title (pcdata "")) [])
          (body [h1 [pcdata
                   "Ok now, you can read the page."]])))

See looong2.

The big picture

You now have the minimum knowledge to write basic Web sites with Eliom: page typing, service creation, parameters, forms and database acces using Lwt (and possibly Lwt_preemptive.detach). Here is a summary of all other concepts introduced by Eliom. They will allow you to easily program more complex behaviours and will be developped in the following sections of this tutorial.

Different kinds of services

Before beginning the implementation, think about the URLs you want to create as entry points to your Web site, and the services you want to provide.

Services we used, so far, are called main services. But there are other kinds of services depending on the precise behaviour you want for links and forms. Clicking on a link or a form may trigger:

  • the request of a new document (page) (or not),
  • the sending of data to the server using the POST method (or not),
  • an action on the server (or not),
  • a change of URL (or not).

To take into account all possible behaviours with respect to URLs, Eliom uses three kinds of services:

Main services
are the main entry points of your sites. Created by new_service or new_post_service. They correspond to the public URLs of your Web site, and will last forever.
(Attached) coservices
are services that share their location (URL) with a main service (fallback). They are distinguished from that main service using a special parameter (added automatically by Eliom), containing either the name of the coservice or a number generated automatically. They are often created dynamically for one user (usually in the session table), depending on previous interaction during the session. In general, they disappear after a timeout letting the fallback answer afterwards. Another use of (POST) coservices is to customize one button but not the page it leads to (like the disconnect button in the example of sessions with actions as below).
Non-attached coservices
are coservices that are not attached to a particular URL. A link to a non-attached coservice will go to the current URL with a special parameter containing either the name of the service, or a number generated automatically (and different each time). It is useful when you want the same link or form on several pages (for example a connection box) but you don't want to go to another URL. Non-attached coservices are often used with actions (see below).To summarize, if you want to go to another URL, use (attached) (co)services. If you want to stay on the same URL use non-attached coservices.

GET or POST?

Each of these services both have a version with GET parameters and another with POST parameters.

POST and GET parameters are not equivalent, and you must be very careful if you want to use one or the other.
GET parameters are the parameters you see in the URL (for example http://your_server/examples/coucou?i=42&ii=17&s=krokodile). They are created by browsers if you use forms with the GET method, or written directly in the URL.
POST parameters are sent by browsers in the body of the HTTP request. That's the only solution if you want to send files with your request.

Remember that only pages without POST parameters are bookmarkable. Use GET parameters if you want the user to be able to come back to the URL later or to write the URL manually.
Use POST parameters when you want a different behaviour between the first click and a reload of the page. Usually sending POST parameters triggers an action on server side (like a payment, or adding something in a database), and you don't want it to succeed several times if the page is reloaded or bookmarked.

Data returned by services

Services can send several types of data, using these different modules:

ServicesCoservices
attached
named / anonymous
non-attached
named / anonymous
Eliom_predefmod.XhtmlAllows to register functions that generate xhtml pages statically checked using polymorphic variant types. You may use constructor functions from XHTML.M or a syntax extension close to the standard xhtml syntax.
Eliom_predefmod.XhtmlcompactSame, but without pretty printing (does not add spaces or line breaks).
Eliom_predefmod.BlocksAllows to register functions that generate a portion of page (content of the body tag) using XHTML.M or the syntax extension. (useful for XMLHttpRequest requests for example).
Eliom_duce.XhtmlAllows to register functions that generate xhtml pages statically checked using OCamlduce. Typing is stricter, and you need a modified version of the OCaml compiler (OCamlduce).
Eliom_predefmod.HtmlTextAllows to register functions that generate text html pages, without any typechecking of the content. The content type sent by the server is "text/html".
Eliom_predefmod.CssTextAllows to register functions that generate CSS pages, without any typechecking of the content. The content type sent by the server is "text/css".
Eliom_predefmod.TextAllows to register functions that generate text pages, without any typechecking of the content. The services return a pair of strings. The first one is the content of the page, the second one is the content type.
Eliom_predefmod.ActionAllows to register actions ( functions that do not generate any page). The URL is reloaded after the action.
Eliom_predefmod.Unitis like Eliom_predefmod.Action but the URL is not reloaded after the action.
Eliom_predefmod.Redirection[New in 1.1.0] Allows to register HTTP permanent redirections. You register the URL of the page you want to redirect to. Warning: According to the RFC of the HTTP protocol, the URL must be absolute!
The browser will get a 301 or 307 code in answer and redo the request to the new URL. To specify whether you want temporary (307) or permanent (301) redirections, use the ?options parameter of registration functions. For example: register options:`Permanent ... or register options:`Temporary ....
Eliom_predefmod.FilesAllows to register services that send files
Eliom_predefmod.AnyAllows to register services that can choose what they send, for example an xhtml page or a file, depending on some situation (parameter, user logged or not, page present in a cache ...).It is also possible to create your own modules for other types of pages.

Public and session service tables

Each of these registrations may be done in the public service table, or in a session service table, accessible only to a single user of the Web site. This allows to generate custom services for a specific user.

Eliom will try to find the page, in that order:

  • in the session service table,
  • in the public service table,
  • the fallback in the session table, if the coservice has expired,
  • the fallback in the public table, if the session has expired.

Session data tables

It is also possible to create a session data table, where you can save information about the session. Each service can look in that table to see if a session is opened or not and get the data.

Examples

The most commonly used services are:

  • Main services (GET or POST) in public service table for public pages,
  • GET attached coservices in session service table to make the browser's "back" button turn back in the past, and to allow several tabs on different versions of the same page,
  • Actions registered on POST named non-attached coservices to make an effect on the server, from any page, and without changing the URL (connection/disconnection for example).

Here is a list of frequent issues and the solution Eliom provides to to solve them. Most of them will be developped in the following parts of the tutorial.

Display the result of a search (plane ticket, search engines ...)
Use a coservice (anonymous, with timeout) in the session service table.
Keep information about the session (name of the user ...)
Use a session data table.
A connection or disconnection box on each page of your site
Use actions registered on named non-attached coservices to set or remove data from a session data table.
Add something in a shopping basket
Use an action registered on a non-attached coservice, with the names of the items as parameters. The action saves the shopping basket in a session data table. Thus, the shopping basket will remain even if the user pushes the back button of his browser.
Book a ticket (in several steps)
Each step creates new (GET) coservices (with or without parameters, all attached to the service displaying the main booking page) according to the data entered by the user. These coservices are registered in the session table (with a timeout for the whole session or for each of them). Thus the user can go back to a previous state, or keep several proposals on differents tabs before choosing one.
...
Help us to complete this list by giving your examples or asking questions about other cases! Thank you!

.

Ocsigen

This page has been generated by Ocsimore. If you are a member of the Ocsigen team, you can log in to modify the pages.