
This is the tutorial for Ocsigen version 0.4.0. (Please report any error).
Warning: This tutorial assumes you know the Objective Caml language.
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. 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 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 _ () () -> (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 (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> ...)
If you prefer using a syntax closer to html, you can write:
let coucou1 = register_new_service ~url:["coucou1"] ~get_params:unit (fun _ () () -> << <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.
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 _ () () -> (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 _ () () -> (html (head (title (pcdata "Ciao")) []) (body [p [pcdata ("Ciao")]])))
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 _ () () -> (html (head (title (pcdata "")) []) (body [p [pcdata ("default page. rep is redirected to rep/")]])))
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 _ (entier, (chaine, chaine2)) () = (html (head (title (pcdata "")) []) (body [p [pcdata "You sent: "; strong [pcdata (string_of_int entier)]; pcdata ", "; strong [pcdata chaine]; pcdata " and "; strong [pcdata chaine2]]])) let coucou_params = register_new_service ~url:["coucou"] ~get_params:(int "entier" ** (string "chaine" ** string "chaine2")) writeparams
Note that the URLs of coucou
and coucou_params
differ only by parameters. Url
http://your_server/examples/plop
will run the first one,
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.
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) () -> << <html> <head><title></title></head> <body> <p> The suffix of the URL is <strong>$str:suff$</strong> and your user-agent is <strong>$str:sp.user_agent$</strong> and your IP is <strong>$str:Unix.string_of_inet_addr sp.ip$ </strong> and s is <strong>$str:s$</strong> </p> </body> </html> >>)
let iprefix = register_new_service ~url:["iprefix"] ~prefix:true ~get_params:(suffix (int "i")) (fun sp (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_service ["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]])))
To create a link (anchor), use the function Ocsigen.a
let links = register_new_service ["rep";"links"] unit (fun sp () () -> (html (head (title (pcdata "")) []) (body [p [a coucou sp.current_url [pcdata "coucou"] (); br (); a hello sp.current_url [pcdata "hello"] (); br (); a default sp.current_url [pcdata "default page of the directory"] (); br (); a uaprefix sp.current_url [pcdata "uaprefix"] ("suff","toto"); br (); a coucou_params sp.current_url [pcdata "coucou_params"] (42,("ciao","hallo")); br (); a (new_external_service ~url:["http://fr.wikipedia.org";"wiki"] ~prefix:true ~get_params:suffix_only ~post_params:unit ()) sp.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 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 () () -> << <html> <head><title></title></head> <body><p>$a linkrec sp.current_url <:xmllist< click >> ()$</p></body> </html> >>)
The server won't accept to start if there are unregistered services.
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 (entier,(chaine,chaine2)) -> [p [pcdata "Write an int: "; int_input entier; pcdata "Write a string: "; string_input entier; pcdata "Write another string: "; string_input entier; submit_input "Click"]]) let form = register_new_service ["form"] unit (fun sp () () -> let f = get_form coucou_params sp.current_url create_form in << <html> <head><title></title></head> <body> $f$ </body> </html> >>)
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 _ () () -> (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 -> (html (head (title (pcdata "")) []) (body [h1 [pcdata value]])))
Services may take both GET and POST parameters:
let getno_post_param_service = register_new_service ~url:["post2"] ~get_params:(int "i") (fun _ i () -> (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:getno_post_param_service ~post_params:(string "value") (fun _ i value -> (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. form3 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.current_url (fun chaine -> [p [pcdata "Write a string: "; string_input chaine]]) ()) in (html (head (title (pcdata "--")) []) (body [f]))) let form3 = register_new_service ["form3"] unit (fun sp () () -> let f = (post_form my_service_with_get_and_post sp.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_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.current_url (fun chaine -> <:xmllist< <p> Write a string: $string_input chaine$ </p> >>) 222) in << <html><body>$f$</body></html> >>)
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 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.current_url (fun _ -> [p [submit_input "incr i (post)"]]) () in let l4 = get_form auxiliaryserv2 sp.current_url (fun _ -> [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 auxiliaryserv sp.current_url [pcdata "reload"] (); br (); a auxiliaryserv2 sp.current_url [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.
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 ().
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.current_url (fun login -> [p [pcdata "login: "; string_input login]]) () in (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 rec launch_session sp () login = let close = register_new_auxiliary_service_for_session ~fallback:public_session_without_post_params (fun sp () () -> close_session (); accueil sp () ()) in let new_main_page sp () () = (html (head (title (pcdata "")) []) (body [p [pcdata "Welcome "; pcdata login; pcdata "!"; br (); a coucou sp.current_url [pcdata "coucou"] (); br (); a hello sp.current_url [pcdata "hello"] (); br (); a links sp.current_url [pcdata "links"] (); br (); a close sp.current_url [pcdata "close session"] ()]])) in register_service_for_session ~service:public_session_without_post_params (* service is any public service already registered *) new_main_page; register_service_for_session ~service:coucou (fun _ () () -> (html (head (title (pcdata "")) []) (body [p [pcdata "Coucou "; pcdata login; pcdata "!"]]))); register_service_for_session hello (fun _ () () -> (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
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 ~fallback:calc ~post_params:(int "j") (fun sp () j -> let js = string_of_int j in let ijs = string_of_int (i+j) in (html (head (title (pcdata "")) []) (body [p [pcdata (is^" + "^js^" = "^ijs)]]))) in let f = post_form calc_result sp.current_url (create_form is) () in (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.current_url create_form () in (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:
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 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 html (head (title (pcdata "")) []) (body [f]) let _ = register_service ~service:action_session accueil_action
let rec launch_session login = let disconnect_action = register_new_action unit (fun _ () -> close_session ()) in let disconnect_box sp s = action_a disconnect_action sp s in let new_main_page sp () () = html (head (title (pcdata "")) []) (body [p [pcdata "Welcome "; pcdata login; br (); a coucou sp.current_url [pcdata "coucou"] (); br (); a hello sp.current_url [pcdata "hello"] (); br (); a links sp.current_url [pcdata "links"] (); br ()]; disconnect_box sp [pcdata "Close session"]]) in register_service_for_session ~service:action_session new_main_page; register_service_for_session coucou (fun _ () () -> (html (head (title (pcdata "")) []) (body [p [pcdata "Coucou "; pcdata login; pcdata "!"]]))); register_service_for_session hello (fun _ () () -> (html (head (title (pcdata "")) []) (body [p [pcdata "Ciao "; pcdata login; pcdata "!"]]))) let _ = register_action ~action:connect_action (fun _ login -> launch_session login)
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))
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 current_url [pcdata "download image"] "ocsigen8-100x30.png"
creates this link: download image
To be available soon
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")
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.
You need to coerce each of them. For example
[(home :> (('a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, Ocsigen.url_kind) url))]
To be available soon
.