Ocsigen

This is the tutorial for Ocsigen version 0.3.25

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.

Module XHTML.M defines functions to construct html. Module Ocsigen gives access to all the functions you need to communicate with the Ocsigen server. As the second one redefines some functions of the first one, open them in this order:


open XHTML.M
open Ocsigen

Pages are generated by OCaml functions. To associate such a function to an URL, use the function register_new_url:


let plop = 
  register_new_url
    ~path:["plop"]
    ~params:_noparam
    (html
       (head (title (pcdata "")) [])
       (body [h1 [pcdata "Hallo"]]))

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):

<site> <url>examples</url> <module>/path_to/tutorial.cmo</module> </site>

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

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.

Typing of html is very strict and forces you to respect xhtml 1.1 standard. 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.

Alternate syntax

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


let plop1 = 
  register_new_url 
    ~path:["plop1"]
    ~params:_noparam 
    << <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.

More examples

The parameter labelled params tells you the type of the page. _noparam means that the page does not take any parameter. _unit means that the page takes a unit parameter. It allows to add side-effects to your pages:


let compt = 
  let next =
    let c = ref 0 in
      (fun () -> c := !c + 1; !c)
  in
  register_new_url 
    ~path:["compt"]
    ~params:_unit
    (fun () -> 
      (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 plip = 
  register_new_url 
    ["dir";"plip"]  (* the url dir/plip *)
    _noparam
    << <html> 
         <head><title>plip</title></head>
         <body><h1>plip</h1></body>
       </html> >>

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_url ["rep";""] _noparam
  << <html>
       <head><title></title></head>
       <body>
         <p>default page. rep is redirected to rep/</p>
       </body>
     </html> >>

Parameters

Pages may depend on URL parameters:


let writeparams entier chaine chaine2 = 
<< <html>
    <head><title></title></head>
    <body>
    <p>
      You sent to me: 
      <strong>$str:string_of_int entier$</strong> et 
      <strong>$str:chaine$</strong> et 
      <strong>$str:chaine2$</strong>
    </p>
    </body>
  </html> >>

let plop_params = register_new_url 
  ~path:["plop"]
  ~params:((_int "entier") ** (_string "chaine")
            ** (_string "chaine2"))
  writeparams

Note that the urls plop and plop_params differ only by parameters. Url http://your_server/examples/plop
http://your_server/examples/plop?entier=42&chaine=hello&chaine2=krokodile will run the second one.
If entier is not an integer, the server displays an error-message.

The following examples show how to create a "prefix" url (taking the end of the url as a parameter) and how to get values from the http header:


let uaprefix = 
  register_new_url 
    ~path:["uaprefix"]
    ~params:(_useragent (_ip (_url_suffix (_string "s"))))
    ~prefix:true
    (fun ua ip suff s -> 
       << <html>
            <head><title></title></head>
            <body>
              <p>
              The suffix of the url is 
                           <strong>$str:suff$</strong>
              and your user-agent is 
		             <strong>$str:ua$</strong>
              and your IP is <strong>$str:ip$</strong>
              and s is <strong>$str:s$</strong>
              </p>
            </body>
          </html> >>)


let iprefix = 
  register_new_url 
    ~path:["iprefix"] 
    ~prefix:true 
    ~params:(_url_suffix (_int "i"))
    (fun suff i -> 
<< <html>
     <head><title></title></head>
     <body><p>
       The suffix of the url is <strong>$str:suff$</strong> 
       and i is <strong>$str:string_of_int i$</strong>
     </p></body>
   </html> >>)

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_url 
  ["mytype"]
  (_user_type mysum_of_string string_of_mysum "valeur")
  (fun x -> let v = string_of_mysum x in
    (html
       (head (title (pcdata "")) [])
       (body [p [pcdata v]])))

Links

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


let links = register_new_url ["rep";"links"] 
  (_current_url _noparam)
  (fun current_url ->
    (html
     (head (title (pcdata "")) [])
     (body 
        [p
          [a plop current_url [pcdata "plop"]; br ();
           a plip current_url [pcdata "plip"]; br ();
           a default current_url 
             [pcdata "default page of the directory"]; 
           br ();
           a uaprefix current_url 
             [pcdata "uaprefix"] "suf" "toto"; 
           br ();
           a plop_params current_url 
             [pcdata "plop_params"] 42 "ciao" "hallo"; 
	   br ();
           a (new_external_url
               ~path:["http://fr.wikipedia.org";"wiki"]
               ~prefix:true
               ~params:(_url_suffix _noparam) ()) 
             current_url
             [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 _current_url parameter.
The link to Wikipedia shows how to define an external url (here it is 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 url, then register it in the table:


let linkrec = 
   new_url ["linkrec"] (_current_url _noparam) ()

let _ = register_url linkrec 
    (fun url -> 
      << <html>
          <head><title></title></head>
          <body><p>$a linkrec url <:xmllist< click >>$
                </p></body>
         </html> >>)

The server won't accept unregistered url.

Forms

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


let create_form = 
  (fun entier chaine chaine2 ->
    <:xmllist< <p>Write an int: $int_input entier$ <br/>
    Write a string: $string_input chaine$ <br/>
    Write a string: $string_input chaine2$ <br/>
    $submit_input "Click"$
    </p>
    >>)

let form = register_new_url ["form"] (_current_url _noparam)
  (fun current_url -> 
     let f = get_form plop_params current_url create_form in
     << <html>
          <head><title></title></head>
          <body> $f$ </body>
        </html> >>)

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). 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 an URL with POST parameters, you must register before an url (fallback) without these parameters (for example that will answer if the page is reloaded without the hidden parameters, or bookmarked).


let no_post_param_url = 
  register_new_url 
    ~path:["post"]
    ~params:_noparam
    (html
       (head (title (pcdata "")) [])
       (body [h1 [pcdata 
"Version of the page without POST parameters"]]))
    
let my_url_with_post_params = register_new_post_url
    ~fallback:no_post_param_url
    ~post_params:(_string "value")
    (fun value -> 
    (html
       (head (title (pcdata "")) [])
       (body [h1 [pcdata value]])))

If you want POST parameters and SERVER parameters, put first POST parameters, then SERVER parameters.

If you want GET, SERVER and POST parameters, remember to put first POST parameters, then SERVER parameters, then GET parameters:


let get_no_post_param_url = 
  register_new_url 
    ~path:["post2"]
    ~params:(_int "i")
    (fun i -> 
      (html
         (head (title (pcdata "")) [])
         (body [p [pcdata "No POST parameter, i:";
                   em [pcdata (string_of_int i)]]])))


let my_url_with_get_and_post = register_new_post_url 
  ~fallback:get_no_post_param_url
  ~post_params:(_string "value")
  (fun value i -> 
      (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 url post (using XHTML.M's functions) and form3 (defined using the syntax extension) contains a form to post2, with a GET parameter. form3 is a form to an external page.


let form2 = register_new_url ["form2"]
  (_current_url _noparam)
  (fun current_url -> 
     let f = 
       (post_form my_url_with_post_params current_url
          (fun chaine -> 
            [p [pcdata "Write a string: ";
                string_input chaine]])) in
     (html
        (head (title (pcdata "")) [])
        (body [f])))

let form3 = register_new_url ["form3"]
  (_current_url _noparam)
  (fun current_url ->
     let f = 
       (post_form my_url_with_get_and_post current_url
          (fun chaine -> 
            <:xmllist< <p> Write a string: 
                    $string_input chaine$ </p> >>)
          222) in
       << <html><head><title></title></head>
                <body>$f$</body></html> >>)

let form4 = register_new_url ["form4"]
  (_current_url _noparam)
  (fun current_url ->
     let f = 
       (post_form
          (new_external_post_url 
             ~path:["http://www.your_external_url.com"]
             ~params:(_int "i")
             ~post_params:(_string "chaine") ()) 
          current_url
          (fun chaine -> 
            <:xmllist< <p> Write a string: 
		$string_input chaine$ </p> >>)
          222) in
       << <html><body>$f$</body></html> >>)

URL with states

You can define new urls that differs from public url only by a (hidden) parameter (internal state). To do that, use new_state_url and new_post_state_url. Actually the text of the URL need to be exactly the same as a public url, so that it doesn't fail if you bookmark the page. That's why, like register_new_post_url, new_state_url and new_post_state_url take a public URL as parameter (called fallback).


let ustate = new_url ["state"] (_current_url _noparam) ()

let ustate2 = new_state_url ~fallback:ustate

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

The session URL will have the same GET parameters but may have different POST parameters.

Note that with links or GET forms, it is not possible to send a parameter in the header. That's why we send the state parameter in the URL. It is ignored if the state URL does not exist in the URL table.

Sessions

You can replace some public url by an url valid only for one session. To create a "session url", register the url in a "session table" (valid only for one client) instead of the global table. To do that, use register_url_for_session or register_post_url_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 ().

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_url 
    ~path:["session"]
    ~params:(_current_url _noparam)
    ()

let public_session_with_post_params = 
  new_post_url 
    ~fallback:public_session_without_post_params
    ~post_params:(_string "login")

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


let _ = register_url
  ~url:public_session_without_post_params
  accueil

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


let rec launch_session login =
  let close =
    register_new_state_url_for_session
      ~fallback:public_session_without_post_params 
      (fun url -> close_session (); accueil url)
  in
  let new_main_page url =
    (html
       (head (title (pcdata "")) [])
       (body [p [pcdata "Welcome ";
                 pcdata login; 
                 pcdata "!"; br ();
                 a plop url [pcdata "plop"]; br ();
                 a plip url [pcdata "plip"]; br ();
                 a links url [pcdata "links"]; br ();
                 a close url [pcdata "close session"]]]))
  in
  register_url_for_session 
    ~url:public_session_without_post_params 
    (* url is any public url already registered *)
    new_main_page;
  register_url_for_session 
    ~url:plop
    (html
       (head (title (pcdata "")) [])
       (body [p [pcdata "Plop ";
                 pcdata login;
                 pcdata "!"]]));
  register_url_for_session 
    plip
    (html
       (head (title (pcdata "")) [])
       (body [p [pcdata "Plop2 ";
                 pcdata login;
                 pcdata "!"]]));
  new_main_page
    
let _ =
  register_post_url
    ~url:public_session_with_post_params
    launch_session

State URL and session tables

You can register url with states in session tables. Here is an example of pages that add two integer. 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_url
    ~path:["calc"]
    ~params:(_current_url _noparam)
    ()

let calc_post = 
  new_post_url 
    ~fallback:calc
    ~post_params:(_int "i")

let _ = 
  let create_form is = 
    (fun entier ->
      let ib = int_input entier in
      let b = submit_input "Sum" in
      <:xmllist< <p> $str:is$ + $ib$ <br/>
      $b$ </p>
      >>)
  in
  register_post_url
    ~url:calc_post
    (fun i current_url ->
      let is = string_of_int i in
      let calc_result = 
        register_new_post_state_url_for_session
          ~fallback:calc_post
          ~post_params:(_int "j")
          (fun j current_url -> 
            let js = string_of_int j in
            let ijs = string_of_int (i+j) in
            << <html> 
                 <body><p> 
                   $str:is$ + $str:js$ = $str:ijs$ </p>
                 </body></html> >>)
      in
      let f = post_form
        calc_result current_url (create_form is) in
      << <html><body>$f$</body></html> >>)


let _ =   
  let create_form = 
    (fun entier ->
      <:xmllist<
	<p> Write a number: $int_input entier$ <br/>
        $submit_input "Send"$ </p>
      >>)
  in
  register_url
    calc
    (fun current_url ->
      let f = post_form
        calc_post current_url create_form in
      << <html><body>$f$</body></html> >>)

Pages/URL registered in session tables are called session pages/URL. Pages/URL registered in the global table are called global or public.

+

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 url defined (without state) 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 url the user can bookmark will always give an answer, even if the session has expired.

Do not register public pages after the initialisation phase.

Do not register twice the same URL, 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 url but they do not generate any page. For ex, when you have the same form (or link) on several pages (for ex a connect form), instead of making a version with post params of all these pages, you can use actions. Create and register an action with new_actionurl, register_actionurl, register_new_actionurl, register_actionurl_for_session, register_state_actionurl_for_session.
Make a form or a link to an action with action_form or action_a. By default, they print the 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_url
    ~path:["action"]
    ~params:(_http_params _noparam)
    ()

let connect_action =
    new_actionurl ~params:(_string "login")

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

let _ = register_url ~url:action_session accueil_action


let rec launch_session login =
  let deconnect_action = 
    register_new_actionurl _unit close_session in
  let deconnect_box h s = 
    action_a deconnect_action h s in
  let new_main_page h =
    html
      (head (title (pcdata "")) [])
      (body [p [pcdata "Welcome ";
                pcdata login; br ();
                a plop h.current_url [pcdata "plop"]; br ();
                a plip h.current_url [pcdata "plip"]; br ();
                a links h.current_url [pcdata "links"]; br ()];
             deconnect_box h [pcdata "Close session"]])
  in
  register_url_for_session ~url:action_session new_main_page;
  register_url_for_session plop
    (html
       (head (title (pcdata "")) [])
       (body [p [pcdata "Plop ";
                 pcdata login;
                 pcdata "!"]]));
  register_url_for_session plip 
    (html
       (head (title (pcdata "")) [])
       (body [p [pcdata "Plop2 ";
                 pcdata login;
                 pcdata "!"]]))
    
let _ = register_actionurl
    ~actionurl:connect_action
    ~action:(fun login -> launch_session login)

Example: write a forum

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 urls: *)

let main_page = new_url 
    ~path:[""]
    ~params:(_http_params _noparam)
    ()

let news_page = new_url 
    ["msg"]
    (_http_params (_int "num"))
    ()

(* Construction of pages *)

let accueil h =
  page h
    [h1 [pcdata "Mon site"];
     news_headers_list_box 
       h anonymoususer news_page]

let print_news_page h i = 
  page h
    [h1 [pcdata "Info"];
     message_box i anonymoususer]

(* Page registering *)

let _ = register_url ~url:main_page accueil

let _ = register_url ~url:news_page print_news_page

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


(* All the urls: *)

let main_page = new_url
    ~path:[""]
    ~params:(_http_params _noparam)
    ()

let news_page = new_url
    ["msg"]
    (_http_params (_int "num"))
    ()

let deconnect_action =
    register_new_actionurl _unit close_session

let connect_action = new_actionurl
    ~params:(_string "login" ** _string "password")

(* Construction of pages *)

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

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

let user_main_page user h =
  page h
    [h1 [pcdata "Mon site"];
     text_box "Bonjour !";
     connected_box h user deconnect_action;
     news_headers_list_box h user news_page]

let user_news_page user h i = 
  page h
    [h1 [pcdata "Info"];
     connected_box h user deconnect_action;
     message_box i user]

(* Page registering *)

let _ = register_url
  ~url:main_page
  accueil

let _ = register_url
  ~url:news_page
  print_news_page

let launch_session user =
  register_url_for_session
    ~url:main_page (user_main_page user);
  register_url_for_session
    ~url:news_page (user_news_page user)

let _ = register_actionurl
  ~actionurl:connect_action
  ~action:(fun login password ->
             launch_session (connect login password))

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 URL 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 
  current_url
  [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 current_url "ocsigen1024.jpg")
    ()

The function make_uri creates the relative URL string from current_url (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 current_url "style.css")


js_script (make_uri static_dir current_url "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 current_url =
  Ocsigenboxes.menu ~classe:["menuprincipal"]
    [
     (home, <:xmllist< Home >>);
     (infos, <:xmllist< More infos >>);
     (tutorial, <:xmllist< Documentation >>)
   ] current current_url

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

Then mymenu home current_url 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, 'c, 'd, 'e, 'f, 'g, 'h, Ocsigen.url_kind) url))]
       

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.