Warning: Reason support is experimental. We are looking for beta-tester and contributors.

Communication between the client and the server

Outside of the values passed by the mean of the %variable syntax, there are multiple ways for the client and server to echange values. Depending on what kind of transfer to do, there are different methods addapted.

Client requesting data

The client process can call special services to get some Ocaml values. Those services cannot be visited by the browser as web pages. They are registered using Eliom_registration.​Ocaml and can be called using Eliom_client.​call_caml_service.

First of all we give us easy construction of content (i.e. {{Html5.D}} here):

open Eliom_content

For instance:

let pi_service =
  Eliom_registration.Ocaml.register_service
    ~path:["pi"]
    ~get_params:Eliom_parameter.unit
    (fun () () -> Lwt.return 3.1415926535)

let _ =
  My_appl.register_service
    ~path:["pi service"]
    ~get_params:Eliom_parameter.unit
    (fun () () ->
      Eliom_service.onload
	{{
          Lwt.ignore.result(
            lwt pi =
	      Eliom_client.call_caml_service ~service:%pi_service () ()
            in
	    Lwt.return (
	      Dom_html.window##alert(Js.string
				       ("pi = "^(string_of_float pi)))))
	}};
      Lwt.return
        Html5.D.(html
                  (head (title (pcdata "pi")) [])
                  (body [])))

Since client and server side value representation are not the same, it is not possible to send any Ocaml value, the restrinction on what can be sent are the same as for the %variable mechanism (see the wrapping chapter).

Client sending data

The client can send values to the server using the service mechanism, by defining the parameter types. To send arbitrary Ocaml values, there is a special type declaration: Eliom_parameter.​caml.

Since the server can't trust the client to send correcly formed data, those values can't be sent using the Ocaml marshall mechanism: the server needs to be able to check that the value is of the expected type. To do this we use Deriving, a syntax extension for the type declarations.

{shared{
  type some_type = (int * string list) deriving (Json)
  type another_type =
    | A of some_type
    | B of another_type
    deriving (Json)
}}

this type can now be used as a parameter for a service

open Eliom_content

let s =
  My_appl.register_service
    ~path:["s1"]
    ~get_params:(caml "param" Json.t<another_type>)
    (fun () v ->
      Lwt.return
        Html5.D.(html
                  (head (title (pcdata "title")))
                  (body [
                    match v with
                      | A _ -> pcdata "A"
                      | B _ -> pcdata "B"
                  ])))

let _ =
  My_appl.register_service
    ~path:["s2"]
    ~get_params:unit
    (fun () () ->
      Lwt.return
        Html5.D.(html
                  (head (title (pcdata "title")))
                  (body [
                    [p ~a:[a_onclick
                      {{ ignore (Eliom_client.change_page ~service:%s (A (1,["s"])) ()) }}]
                      [pcdata "Click to send Ocaml data"]
                  ]])))

For more information see deriving documentation.

deriving

Server sending data

There are ways for the server to send data to the client, without the client to explicitely request it. We call that mechanism comet, it also sometimes called HTTP push.

The simple low level version on wich all other following mechanism is implemented is provided in the Eliom_comet.​Channels module.

Comet defines channels which can convey data. A channel is created using an Lwt stream. It is a kind of cooperative lazy list.

The two main methods to create a stream is using Lwt_stream.​from or Lwt_stream.​create functions.

val from : (unit -> 'a option Lwt.t) -> 'a t
val create : unit -> 'a t * ('a option -> unit)

With Lwt_stream.​from you can create a stream where a new value is added each time a function returns. Lwt_stream.​create returns a stream and function to push new values to it.

On client side the Eliom_comet.​Channels.​t type is just an Lwt stream Lwt_stream.​t.

There are 3 kind of channels depending on how you want to send data.

  • Channels created with Eliom_comet.​Channels.​create have a buffer with a limited size. Message are read from the stream as soon as they are available, i.e. for stream created with Lwt_stream.​from, that means that the function is called another time as soon as the previous one terminate. For stream created with Lwt_stream.​create, this is as soon as they are pushed. If the client has missed too much messages ( more than the size of the buffer ) it will receive an exception Eliom_comet.​Channel_full when reading data from the stream.

Channels can be closed on client side by canceling a thread waiting for data on it.

Depending on the scope used to create a channel, the channel does not have the same constraints.

  • The other channels must be created inside a service handler. They are assigned to a particular client process. Different channels created with the same stream does not share memory. They are closed when requested or when the client process is closed. It is possible to know when a client stop requesting data on those channels using Eliom_comet.​Channels.​wait_timeout

Comet configuration

The server can push data to a client only when the client has an open HTTP connection waiting for answer. As of now, a comet request can only last at most 10 seconds. After that, the client can either do a new request or stale for some time: this is the activity behaviour. This can be configured on client side, using the functions from Eliom_comet.​Configuration

For instance for if you receive data that doesn't need frequent update, you could set the time between different request quite high and stop requesting data as soon as the browser loose the focus.

open Eliom_comet.Configuration
let slow_c = new_configuration () in
set_active_until_timeout slow_c false;
set_time_between_request slow_c 60.

then if you want to have some reactivity for a few seconds.

open Eliom_comet.Configuration
let fast_c = new_configuration () in
set_set_always_active fast_c true;
set_set_time_between_request fast_c 0.;
ignore (Lwt_js.sleep 10. >|= (fun () -> drop_configuration fast_c))

The original setting will be reset after the drop.

Reactive values

A common usage of comet is for the server to update a value available on client side. To do this, there are functions available to share a React event or signal: Eliom_react.​Down.​of_react and Eliom_react.​S.​Down.​of_react

On client side the value returned by those function is directly a React event or signal.

The contrary is also available using Eliom_react.​Up.​create.

Since this is implemented using comet, tunnig comet configuration will also affect the behaviour of shared react variables.

Client-Server shared bus

Sometimes it is usefull to have a bidirectionnal channel shared between multiple clients. This is the intent of buses. Those are created using Eliom_bus.​create. Since the server will also receive data on the bus, the description of the type ( using deriving ) is needed to create a bus.

Like comet channels, the behaviour of buses can be tuned using the module Eliom_comet.​Configuration. There are additionnal configurations available on buses to tune the client side buffering.

+ events (à mettre dans js_of_ocaml)

Another Server sending data (Comet on another server)

It is possible to access a named stateless channel created on another server. It has to be declared using Eliom_comet.​Channels.​external_channel. The declaration of the channel must match exactly the creation. The server generating the page and the server that created the channel must run exactly the same version of Eliom. By default a browser can't do requests to a different server, to allow that the server serving the channel must allow Cross-Origin Resource Sharing using the CORS Ocsigenserver extension.