
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.
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.
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 _ () () -> 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.
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"]] }})
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/"]])))
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]])))
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.
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.
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).
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])))
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])))
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 (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.
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
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:
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 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))
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))
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.
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.
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)
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 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.
Just add the following lines to your program:
let rec f () = print_endline "hello"; Lwt_unix.sleep 10. >>= f in f ();
let w = wait () in (w >>= (fun v -> return (print_endline v)); ... wakeup w "HELLO");
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
To be available soon
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")
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.
(external, internal...)? You need to coerce each of them. For example
[(home :> (('a,'b,Ocsigen.service_kind,'c,'d,'e) service))]
To be available soon
.