Ocsigen uses the expressiveness of the OCaml language to provide high level semantic concepts dedicated to Web programming. It relieves the programmer of many technical details.
Service based programming
An Ocsigen Web site is written as a single OCaml module. Requests are handled by services, which can be seen as functions called by clicking on a hyperlink or by sending a form.
Services may return HTML pages, but also files, redirections, or many other types of output. There is also a kind of service called action, that just performs a side effect on server side.
The following piece of code defines two services. The first one returns an HTML5 page, the second one returns a redirection to the first one.
let s1 = Html5.register_service ~path:["foo"] ~get_params:unit (fun g p -> ... (* an HTML5 page *)) let s2 = Redirection.register_service ~path:["bar"] ~get_params:unit (fun g p -> return s1 (* redirection to service s1 *))
The register_service function from module Html5 or Redirection takes three parameters:
- The first one, called path, is the URL (here /foo and /bar)
- The second one, called get_params describes the URL parameters expected by the service (unit means no parameter)
- The third one is a function, that generates the result from consuming the GET and POST parameters (here called g and p).
Powerful service identification
Ocsigen provides a very precise way to program traditional Web interactions (URLs, links, forms, bookmarks, sessions, etc.) The service to be executed is chosen automatically with respect to several criteria, among which are:
- The URL
- A service identifier, sent as parameter
- The names and types of parameters
- The session the client belongs to
According to the behaviour you want to implement, just choose the kind of identification you want and Ocsigen will do everything for you!
Example: If you want to put a login form on each page of the site, use a non-attached POST coservice, that is, a service that is not linked to an URL, but only to a special (internal) parameter sent in the body of the HTTP request. Thus, calling the service will not change the URL, and you will stay on the same page after connection. This service will perform the action of opening a session.
let login_service = Action.register_post_coservice' ~post_params:(string "login" ** string "password") (fun () (login, password) -> ... (* Check password and open a session *))
The register_post_coservice' allows you to define a non-attached POST coservice. As you can see it does not take a path parameter: it is available at any URL. The post_params parameter describes the parameters expected by the service (used here as two strings, called "login" and "password"). The function that implements the service takes no GET argument, and two POST arguments (here called login and password).
Ocsigen provides you with the ability to dynamically register new services. This allows one to create customized services that will depend on previous interaction with the user.
This greatly helps in implementing a common Web interaction scheme: presenting a sequences of pages, each one depending on the previous ones. For example if you want to buy a plane ticket, you fill a first form, then the Web site displays a list of tickets. If you choose one of them, the site may for example display another form asking for your personal information, then another for the payment, and so on. On each of these steps, you can duplicate the browser window, or press the back button of the browser, and make another choice. Accounting for this behaviour correctly is very tricky with typical Web frameworks and many implementations are buggy and error-prone.
To be implemented correctly, such a behaviour requires saving all the information about each step of the interaction with each user. With usual Web programming frameworks, you will probably use tables on server side. But this is cumbersome and tedious work, and many Web sites are bugged for this reason.
With dynamic services, you create a new service for each point of the interaction tree. It is no longer possible to take the wrong path through the tree, and–better yet–this is very easy to write.
This feature is usually called continuation based Web programming.
The following example shows a service with one integer parameter (in the URL, called i). It displays a form, asking for a second integer, that leads to a service that has been registered dynamically. It takes one integer parameter (called j) and displays the result of adding this parameter with i.
let add_service = Html5.register_service ~path:["calc"] ~get_params:(int "i") (fun i () -> let result_service = Html5.register_coservice' ~get_params:(int "j") (fun j () -> ... (* return a page displaying the result of i+j *)) in ... (* return a page with a form asking for a second value, leading to result_service *) )
The value of i is recorded automatically in the memory of the server. In a more complicated example, you could have a longer sequence of services, recording arbitrary data. Each of these services corresponds to one point in a long-running interaction with the user.
In an effort to remain efficient with respect to server-side resources, Eliom employs a variety of mechanisms to keep memory consumption as low as possible–even if you use lots of dynamic services.