Ocsigen

This is the tutorial for Ocsigen (versions 0.5.0 and 0.6.0). (Please report any error).

Warning: This tutorial assumes you know the Objective Caml language.

Base principles

With Ocsigen, you don't write one file for each URL. You write a caml module (cmo or cma) for your whole website.

The XHTML.M module defines functions to construct html. The Ocsigen module gives access to all the functions you need to communicate with the Ocsigen server, and Ocsigen.Xhtml defines all the functions you need to create new services that use the XHTML.M module. As the second one redefines some functions of the first one, open them in this order:



open XHTML.M
open Ocsigen
open Ocsigen.Xhtml
open Lwt

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

Pages are generated by OCaml functions called services. To associate a service to an URL, use the function register_new_service:



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

return is a function from Lwt. Use it as this for now, and see later for more advanced use.

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

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

(Replace /path_to/ocsigen/ by the directory where ocsigen is installed).

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

<host> <site dir="examples"> <module file="/path_to/tutorial.cmo" /> </site> </host>

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

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

Typing of html is very strict and forces you to respect xhtml 1.1 standard (with some limitations). For example if you write:


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

You have 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 list of tags allowed in a block tag (here <body>), but PCDATA (i.e. raw text) is not allowed here.

In XHTML, some tags cannot be empty. For example <table> must contains at least one row. To enforce this, the table function takes two parameters: the first one is the first row, the second one is a list containig all the other rows. (same thing for <tr> <form> <dl> <ol> <ul> <dd> <select> ...)

Alternate syntax

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



let coucou1 = 
  register_new_service 
    ~url:["coucou1"]
    ~get_params: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).

You can mix the two syntaxes (see later).

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

We recommand to use preferably 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 verify the validity of your pages with the W3C validator.

More info on XHTML.M.

More info on the syntax extension.

Ocsigen and OCamlDuce

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

To use it, make sure that you have Ocsigen compiled with OCamlDuce support. Then dynlink ocamlduce.cma and ocsigenduce.cma from the configuration file (after ocsigenmod.cma).

Here is an example:


open Ocsigen
open Ocsigenduce.Xhtml
open Lwt

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

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 compt = 
  let next =
    let c = ref 0 in
      (fun () -> c := !c + 1; !c)
  in
  register_new_service 
    ~url:["compt"]
    ~get_params:unit
    (fun _ () () ->  return
      (html
       (head (title (pcdata "counter")) [])
       (body [p [pcdata (string_of_int (next ()))]])))

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"]])))

The last example shows how to define the default page for a directory. (Note that ["rep";""] is equivalent to ["rep";"index"].)



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

Parameters

The parameter labelled get_params indicates the type of GET parameters for the page. unit means that the page does not take any GET parameter.

Functions implementing services take three parameters. The first one has type Ocsigen.server_params and corresponds to server informations (user-agent, ip, current-url, etc. - see later), 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 
    ~url:["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 entier is not an integer, the server displays an error-message.
Warning: The infix function ( ) is to be used to construct pairs (not tuples).

The following examples shows how to create a service with "prefix" service (taking the end of the URL as a parameter, as wikis do very often) and how to get values from the http header:



let uaprefix = 
  register_new_service 
    ~url:["uaprefix"]
    ~get_params:(suffix (string "s"))
    ~prefix:true
    (fun sp (suff, s) () ->  return
      (html
        (head (title (pcdata "")) [])
        (body
           [p [pcdata "The suffix of the url is ";
               strong [pcdata suff];
               pcdata ", your user-agent is ";
               strong [pcdata (get_user_agent sp)];
               pcdata ", your IP is ";
               strong [pcdata (get_ip sp)];
               pcdata " and s is ";
               strong [pcdata s]]])))



let iprefix = 
  register_new_service 
    ~url:["iprefix"] 
    ~prefix:true 
    ~get_params:(suffix (int "i"))
    (fun sp (suff, i) () -> return
      (html
        (head (title (pcdata "")) [])
        (body
           [p [pcdata "The suffix of the url is ";
               strong [pcdata suff];
               pcdata " and i is equal to ";
               strong [pcdata (string_of_int i)]]])))

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 = register_new_service 
  ["mytype"]
  (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]])))

Catching errors

You can catch errors using the optional parameter error_handler:



let catch = register_new_service
    ~url:["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.

Links

To create a link (anchor), use the function Ocsigen.a



let links = register_new_service ["rep";"links"] unit
 (fun sp () () -> return
   (html
     (head (title (pcdata "")) [])
     (body 
       [p
        [a coucou sp [pcdata "coucou"] (); br ();
         a hello sp [pcdata "hello"] (); br ();
         a default sp 
           [pcdata "default page of the dir"] (); br ();
         a uaprefix sp 
           [pcdata "uaprefix"] ("suff","toto"); br ();
         a coucou_params sp 
           [pcdata "coucou_params"] (42,(22,"ciao")); br ();
         a
           (new_external_service
              ~url:["http://fr.wikipedia.org";"wiki"]
              ~prefix:true
              ~get_params:suffix_only
              ~post_params:unit ()) 
           sp
           [pcdata "ocaml on wikipedia"]
           "OCaml"]])))


Note that to create a (relative) link we need to know the current URL. That's why the page has a sp parameter.
The link to Wikipedia shows how to define an external service (here it uses a prefix URL).

You give page parameters as additional parameters to the function Ocsigen.a.

If you want to create (mutually or not) recursive pages, first create the service, then register it in the table:



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

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


The server won't accept to start if there are unregistered services.

Continuations

Ocsigen is using the concept of continuation. A continuation represents the future of a program (what to do after). When a user clicks on a link or a form, he chooses the future of the computation. When he uses the "back" button of the browser, he chooses to go back to an old continuation. Continuations for Web programming have been introduced by Christian Queinnec, and are a big step in the understanding of Web interaction.

Some programming languages (Scheme...) allow to manipulate continuations using control operators (like call/cc). The style of programming used by Ocsigen is called Continuation Passing Style (CPS), and has the advantage that it does not need control operators, and fits very well Web programming.

In the following, we will see how to create dynamically new continuations dedicated to a particular user (auxiliary services in session table).

Forms

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



let create_form = 
  (fun (number_name,(number2_name,string_name)) ->
    [p [pcdata "Write an int: ";
        int_input number_name;
        pcdata "Write another int: ";
        int_input number2_name;
        pcdata "Write a string: ";
        string_input string_name;
        submit_input "Click"]])

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

POST parameters

By default parameters of a web page are in the URL (GET parameters). A web page may expect parameters from the http header (POST parameters, that is, parameters which 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 (database, etc). When you register a service with POST parameters, you must register before 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 
    ~url:["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 
    ~url:["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)]]])))

To create a POST form, use the post_form function, possibly applied to GET parameters (if any). Here form2 is a page containig 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 =
       (post_form my_service_with_post_params sp
          (fun chaine -> 
            [p [pcdata "Write a string: ";
                string_input chaine]]) ()) in
     return
       (html
         (head (title (pcdata "form")) [])
         (body [f])))

let form3 = register_new_service ["form3"] unit
  (fun sp () () ->
     let f  = 
       (post_form my_service_with_get_and_post sp
          (fun chaine -> 
            <:xmllist< <p> Write a string: 
                    $string_input 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  = 
       (post_form
          (new_external_service 
             ~url:["http://www.petizomverts.com"]
             ~get_params:(int "i")
             ~post_params:(string "chaine") ()) sp
          (fun chaine -> 
            <:xmllist< <p> Write a string: 
                     $string_input chaine$ </p> >>)
          222) in return
     (html
        (head (title (pcdata "form")) [])
        (body [f])))

Auxiliary services

An auxiliary service is a service that uses the same URL as a public service, but distinguished only by a (hidden) special parameter, called state parameter. Use this if you want to particularize a link, but not the URL it points to. More precisely, auxiliary services are mainly used in two situations:

  • - To create a link that leads to a service after having performed a side-effect. For example a disconnection link that leads to the main page of the side, but with the side effect of disconnecting the user.
  • - In combination with session tables (see later), to create dynamically new services corresponding to precise points of the interaction with the user.

To create an auxiliary service, use new_auxiliary_service and new_post_auxiliary_service. Like register_new_post_service, they take a public service as parameter (labelled fallback) to be used as fallback when the user comes back without the state parameter (for example if he put a bookmark on the page).



let auxiliaryserv = new_service ["auxiliary"] unit ()

let auxiliaryserv2 = 
  new_auxiliary_service ~fallback:auxiliaryserv

let _ = 
  let c = ref 0 in
  let page sp () () = 
    let l3 = post_form auxiliaryserv2 sp 
        (fun _ -> [p [submit_input "incr i (post)"]]) () in
    let l4 = get_form auxiliaryserv2 sp 
        (fun _ -> [p [submit_input "incr i (get)"]]) in 
    return
      (html
       (head (title (pcdata "")) [])
       (body [p [pcdata "i is equal to ";
                 pcdata (string_of_int !c); br ();
                 a auxiliaryserv sp [pcdata "reload"] (); br ();
                 a auxiliaryserv2 sp [pcdata "incr i"] ()];
              l3;
              l4]))
  in
    register_service auxiliaryserv page;
    register_service auxiliaryserv2
      (fun sp () () -> c := !c + 1; page sp () ())

The auxiliary service has the same GET parameters as its fallback, but may have different POST parameters.

Note that with links or GET forms, it is not possible to send the state parameter in the body of the HTTP request. That's why we send the state parameter in the URL (using GET method). This may cause some problems because the state parameter will be saved in bookmarks. Note that the state parameter is ignored if the auxiliary service does not exist in the table of services.

Sessions

Ocsigen allows to replace a public service by a service valid only for one user. To create a "session service", register the service in a "session table" (valid only for one client) instead of the global table. To do that, use register_service_for_session.

Use this for example if you want two versions of each page, one public, one for connected users.
To close a session, use close_session.

Note that register_for_session and close_session take sp as parameter (because sp contains the session table).

The following is an example of web site that behaves differently when users are connected. We first define the main page, with a login form:



let public_session_without_post_params = 
  new_service ~url:["session"] ~get_params:unit ()

let public_session_with_post_params = 
  new_post_service 
    ~fallback:public_session_without_post_params
    ~post_params:(string "login")

let accueil sp () () = 
  let f = post_form public_session_with_post_params sp
    (fun login -> 
         [p [pcdata "login: ";
             string_input login]]) () in return
  (html
     (head (title (pcdata "")) [])
     (body [f]))


let _ = register_service
  ~service:public_session_without_post_params
  accueil

When the page is called with login parameters, it runs the function launch_session that replaces some services already defined by new ones:



let launch_session sp () login =
  let close = register_new_auxiliary_service_for_session
    sp
    ~fallback:public_session_without_post_params 
    (fun sp () () -> close_session sp; accueil sp () ())
  in
  let new_main_page sp () () = return
    (html
       (head (title (pcdata "")) [])
       (body [p [pcdata "Welcome ";
                 pcdata login; 
                 pcdata "!"; br ();
                 a coucou sp [pcdata "coucou"] (); br ();
                 a hello sp [pcdata "hello"] (); br ();
                 a links sp [pcdata "links"] (); br ();
                 a close sp [pcdata "close session"] ()]]))
  in
  register_service_for_session 
    sp
    ~service:public_session_without_post_params 
    (* service is any public service already registered *)
    new_main_page;
  register_service_for_session 
    sp
    ~service:coucou
    (fun _ () () -> return
      (html
         (head (title (pcdata "")) [])
         (body [p [pcdata "Coucou ";
                   pcdata login;
                   pcdata "!"]])));
  register_service_for_session 
    sp
    hello
    (fun _ () () -> return
      (html
         (head (title (pcdata "")) [])
         (body [p [pcdata "Ciao ";
                   pcdata login;
                   pcdata "!"]])));
  new_main_page sp () ()
    
let _ =
  register_service
    ~service:public_session_with_post_params
    launch_session


Auxiliary services in session tables

You can register auxiliary services in session tables to create dynamically new services dedicated to an user. Here is an example of pages that add two integer. Once the first number is sent by the user, an auxiliary service is created and registered in the session table. This service takes the second number as parameter and displays the result of the sum with the first one. Try to duplicate the pages and/or to use the back button of your navigator to verify that it has the expected behaviour.





let calc = 
  new_service
    ~url:["calc"]
    ~get_params:unit
    ()

let calc_post = 
  new_post_service 
    ~fallback:calc
    ~post_params:(int "i")

let _ = 
  let create_form is = 
    (fun entier ->
       [p [pcdata (is^" + ");
           int_input entier;
           br ();
           submit_input "Sum"]])
  in
  register_service
    ~service:calc_post
    (fun sp () i ->
      let is = string_of_int i in
      let calc_result = 
        register_new_post_auxiliary_service_for_session
          sp
          ~fallback:calc
          ~post_params:(int "j")
          (fun sp () j -> 
            let js = string_of_int j in
            let ijs = string_of_int (i+j) in  return
            (html
               (head (title (pcdata "")) [])
               (body
                  [p [pcdata (is^" + "^js^" = "^ijs)]])))
      in
      let f = 
        post_form calc_result sp (create_form is) () in 
      return
        (html
           (head (title (pcdata "")) [])
           (body [f])))


    
let _ =   
  let create_form = 
    (fun entier ->
      [p [pcdata "Write a number: ";
          int_input entier;
          br ();
          submit_input "Send"]])
  in
  register_service
    calc
    (fun sp () () ->
      let f = post_form calc_post sp create_form () in
      return
        (html 
           (head (title (pcdata "")) [])
           (body [f])))

Services registered in session tables are called session services. Services registered in the global table are called public (or global).

+

Remember that the server will try to find the page, in this order:

  • in the session table,
  • in the global (public) table,
  • in the session table, without state,
  • in the global table, without state.

All the services defined (but auxiliary services) must be registered in the global table. If not, the server will not start (with an error message in the logs). This is to ensure that all the URLs the user can bookmark will always give an answer, even if the session has expired.

Do not register public services after the initialisation phase.

Do not register twice the same service, and do not replace an URL by a directory (or vice versa). If this happens during the initialisation phase, the server won't start. If this happens after, it will be ignored (with a warning in the logs).

Actions

Actions are like services but they do not generate any page. Use them to perform an effect on the server (connection/disconnection of a user, etc.). By default, the current page is redisplayed after the action. For ex, when you have the same form (or link) on several pages (for ex a connection form), instead of making a version with post params of all these pages, you can use only one action. Create and register an action with new_action, register_action, register_new_action, register_action_for_session.
Make a form or a link to an action with action_form or action_a. By default, they reload the current page again after having done the action. But you can give the optional boolean parameter reload to action_form or action_a to prevent reloading the page.

Here we rewrite the example session using actions.



let action_session = 
  new_service ~url:["action"] ~get_params:unit ()

let connect_action = 
  new_action ~post_params:(string "login")

let accueil_action sp () () = 
  let f = action_form connect_action sp
      (fun login -> 
        [p [pcdata "login: "; 
            string_input login]]) in return
  (html
    (head (title (pcdata "")) [])
    (body [f]))

let _ = register_service
  ~service:action_session
  accueil_action



let rec launch_session sp login =
  let deconnect_action = 
   register_new_action_for_session sp unit 
      (fun sp () -> return (close_session sp)) in
  let deconnect_box sp s = 
    action_a deconnect_action sp s in
  let new_main_page sp () () = return
    (html
      (head (title (pcdata "")) [])
      (body [p [pcdata "Welcome ";
                pcdata login; br ();
                a coucou sp [pcdata "coucou"] (); br ();
                a hello sp [pcdata "hello"] (); br ();
                a links sp [pcdata "links"] (); br ()];
             deconnect_box sp [pcdata "Close session"]]))
  in
  register_service_for_session 
    sp ~service:action_session new_main_page;
  register_service_for_session sp coucou
   (fun _ () () -> return
     (html
       (head (title (pcdata "")) [])
       (body [p [pcdata "Coucou ";
                 pcdata login;
                 pcdata "!"]])));
  register_service_for_session sp hello 
   (fun _ () () -> return
     (html
       (head (title (pcdata "")) [])
       (body [p [pcdata "Ciao ";
                 pcdata login;
                 pcdata "!"]])))
    
let _ = register_action
    ~action:connect_action
    (fun sp login -> return (launch_session sp login))

Example: write a forum

As an example, we will now write a small forum. Our forum has a main page, summarising all the messages and a page for each message. Suppose you have written a function news_headers_list_box that writes the beginning of messages, and message_box that write a full message.







(* All the services: *)

let main_page = new_service ~url:[""]
    ~get_params:unit ()

let news_page = new_service ["msg"] (StringMessage.index "num") ()

(* Construction of pages *)

let accueil sp () () =
  page sp
    [h1 [pcdata "Mon site"];
     news_headers_list_box 
       sp anonymoususer news_page]

let print_news_page sp i () = 
  page sp
    [h1 [pcdata "Info"];
     message_box i anonymoususer]

(* Services registration *)

let _ = register_service
  ~service:main_page
  accueil

let _ = register_service
  ~service:news_page
  print_news_page

Now the same with a login box on each page. We now have two versions of each page: connected and not connected. We need two actions (for connection and disconnection). Suppose we have the functions login_box, connected_box, and connect.






(* All the urls: *)

let main_page = new_service ~url:[""] ~get_params:unit ()

let news_page = new_service ["msg"] (StringMessage.index "num") ()

let connect_action =
  new_action
    ~post_params:(string "login" ** string "password"
)

(* Construction of pages *)

let accueil sp () () =
  page sp
    [h1 [pcdata "Mon site"];
     p [pcdata "(user : toto and password : titi)"];
     login_box sp connect_action;
     news_headers_list_box sp anonymoususer news_page]

let print_news_page sp i () = 
  page sp
    [h1 [pcdata "Info";
     login_box sp connect_action;
     message_box i anonymoususer]

let user_main_page user sp () () =
  page sp
    [h1 [pcdata "Mon site"];
     text_box "Bonjour !";
     connected_box sp user disconnect_action;
     news_headers_list_box sp user news_page]

let user_news_page user sp i () = 
  page sp
    [h1 [pcdata "Info"];
     connected_box sp user disconnect_action;
     message_box i user]

(* Services registration *)

let _ = register_service
  ~service:main_page
  accueil

let _ = register_service
  ~service:news_page
  print_news_page

let launch_session user =
  register_service_for_session ~service:main_page (user_main_page user);
  register_service_for_session ~service:news_page (user_news_page user)

let _ = register_action
  ~action:connect_action
    (fun h (login, password) ->
      launch_session (connect login password))

Threads

Remember that a Web site written with Ocsigen is an OCaml application. This application must be able to handle several requests at the same time, if one of the requests takes time. To make this possible, Ocsigen is using cooperative threads, implemented in monadic style by Jérôme Vouillon (Lwt module), which make them really easy to use.

With respect to preemptive threads, cooperative threads are not using a scheduler to distribute processor time between threads. Instead of this, each thread must tell the others that he wants to let them work. If a thread does not cooperate, the others will be blocked.

Advantages
  • - It is much lighter
  • - No need of mutex and no risk of deadlock!
  • - - The use of many (small) threads make implementation very easy (for example, for user interfaces, no need to implement another event loop, make a thread for each widget!)
Drawbacks
  • - Threads must cooperate ... Otherwise the whole program will hang.As it does not cooperate, the following page will stop the server for 5 seconds. No one will be able to do a request during this delay:


let looong =
  register_new_service
    ~url:["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 
    ~url:["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."]]))))

The >>= operator (from Lwt) is used to specify a sequence of computations that depend one from another. It is a kind of let binding. e1 >>= (fun r -> return e2) will try to evaluate e1, and once e1 is evaluated, it will give the result to the function given as second parameter. If the left handside (e1) takes time (for example because it is waiting for a read on a socket), the whole computation will be saved in a table and the program will continue to the next instruction that does not depend on e1. The computation will resume at a future cooperation point, if it is ready to continue. Instead of e1 >>= (fun r -> return e2), you can write bind e1 (fun r -> return e2).

Lwt.bind, (or >>=) has type
'a Lwt.t -> ('a -> 'b Lwt.t) -> 'b Lwt.t

Lwt.return has type
'a -> 'a Lwt.t

'a Lwt.t is the type of threads returning a result of type 'a.

Cooperation points are inserted when you call cooperative functions such as Lwt_unix.read or Lwt_unix.write. You can add other cooperation points by calling Lwt_unix.yield (). The thread will suspend itself, let other threads run, and resume as soon as possible.

Catching exceptions

You must be careful when catching exception with Lwt. If you use the try ... with construct for an expression of type 'a Lwt.t, it may not work (as the computation may happen later).

Remember the following: if e has type 'a Lwt.t (where 'a is any type), do not write:


try
  e
with
  ...

but write:


catch
  (fun () -> e)
  (function ... | exn -> fail exn)

What if my function is not implemented in cooperative way?

If my function is thread-safe (for preemptive threads)

Ocsigen implements a way to make a non cooperative computation be executed automatically by a another preemptive thread (for example a database request using a non-cooperative database library, such as postgresql-ocaml or pgocaml). To do this, use the detach function. For example:



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

A pool of preemptive threads is waiting for such "detached functions". You can specify the number of threads in the pool in the configuration file.

Warning: Detached functions must be thread-safe! Be careful to concurrent access to data. Be sure to use mutexes for your own functions, and use only thread-safe libraries.<!– For example <code></code> (version ) is NOT thread-safe, <code></code> (version ) is thread-safe. –> The libraries from Ocsigen are NOT thread-safe for now. Let us know if you need them to be thread-safe.

If my function is not thread-safe (for preemptive threads)

If you want to use a function that takes time to execute but it not written in thread-safe way, consider rewriting it in cooperative manner, or delegate the work to another process.

Examples

A thread that prints "hello" every 10 seconds

Just add the following lines to your program:


let rec f () = 
  print_endline "hello";
  Lwt_unix.sleep 10. >>= f
in f ();
      
Create a thread waiting for an event


let w = wait () in
(w >>= (fun v -> return (print_endline v));
...
wakeup w "HELLO");
      

Static parts

Fully static pages

To each ocsigen module is associated a static directory where you can put all the static (non generated) parts of your web-site (for examples images). See the default config file ocsigen.conf to learn how to do that. There is a predefined service called static_dir to make links to static files. It takes as string parameter the name of the file.
For example


Ocsigen.a 
  (static_dir sp)
  sp
  [pcdata "download image"] 
  "ocsigen8-100x30.png"

creates this link: download image

Static parts of a page

To be available soon

Predefined constructs

Images, CSS, Javascript

To include an image, use simply the function XHTML.M.img:


img ~alt:"Ocsigen" 
    ~src:(make_uri (static_dir sp) sp "ocsigen1024.jpg")
    ()

The function make_uri creates the relative URL string from current URL (in sp) (see above) to the URL of the image in the static directory configured in the configuration file.

To simplify the creation of <link> tags for CSS or <script> tags for Javascript, use the following functions:


css_link (make_uri (static_dir sp) sp "style.css")


js_script (make_uri (static_dir sp) sp "funs.js")

Menus

To make a menu an your web page, you can use the function Ocsigenboxes.menu. First, define your menu like this:


let mymenu current sp =
  Ocsigenboxes.menu ~classe:["menuprincipal"]
    [
     (home, <:xmllist< Home >>);
     (infos, <:xmllist< More infos >>);
     (tutorial, <:xmllist< Documentation >>)
   ] current sp

Here, home, infos, and tutorial are your three pages (generated for example by Ocsigen.new_url).

Then mymenu home sp will generate the following code:

<ul class="menu menuprincipal"> <li class="current first">Home </li> <li><a href="infos">More infos</a> </li> <li class="last"><a href="tutorial">Documentation</a> </li> </ul>

Personalise it in your CSS style-sheet.

Tip: How to make a menu with different kinds of urls

(external, internal...)? You need to coerce each of them. For example



 [(home :> (('a,'b,Ocsigen.service_kind,'c,'d,'e) service))]
       

Others

To be available soon

.

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.