This is the end of the tutorial about writing a collaborative Web drawing in OCaml. Have a look at the full tutorial if you haven’t read the first part or if you want a version with full colors and links.
We will now see how to draw on the canvas, program mouse events with Lwt, and do server to client communication on a bus.
We now want to draw something on the page using an HTML5 canvas. The
drawing primitive is defined in the client-side function called
draw that just draws a line between two given points in a canvas.
To start our collaborative drawing application, we define another
init_client, which just draws a single
line for now.
Here is the (full) new version of the program:
Here we use the function
from Js_of_ocaml’s library to convert an OCaml string
into a JS string.
What sounds a bit weird at first, is a very convenient practice for processing request in a client-server application: If a client value is created while processing a request, it will be evaluated on the client once it receives the response and the document is created; the corresponding side effects are then executed. For example, the line
creates a client value for the sole purpose of performing side
effects on the client. The client value can also be named (as
opposed to ignored via
_), thus enabling server-side
manipulation of client-side values (see below).
(Lwt, Mouse events with Lwt)
We now want to catch mouse events to draw lines with the mouse like
with the brush tools of any classical drawing application. One
example by using function
that is the Js_of_ocaml’s equivalent of
addEventListener. However, this solution is at least as verbose
library provides a much easier way to do that with the help of Lwt.
init_client of the previous example by the
following piece of code, then compile and draw!
We use two references
y to record the last mouse
position. The function
set_coord updates those references from
mouse event data. The function
compute_line computes the
coordinates of a line from the initial (old) coordinates to the new
coordinates–the event data sent as a parameter.
The last four lines of code implement the event-handling loop. They
can be read as follows: for each
mousedown event on the canvas,
line (this will draw a dot), then
behave as the
first of the two following lines that terminates:
Functions in Eliom and Js_of_ocaml which do not implement just a
computation or direct side effect, but rather wait for user activity,
or file system access, or need a unforseeable amount of time to return
are defined with Lwt; instead of returning a value of type
they return an Lwt thread of type
The only way to use the result of such functions (ones that return
values in the Lwt monad), is to use
It is convenient to define an infix operator like this:
Then the code
is conceptually similar to
but only for functions returning a value in the Lwt monad.
For more clarity, there is a syntax extension for Lwt, defining
let%lwt to be used instead of
let for Lwt functions:
Lwt.return creates a terminated thread from a value:
Lwt.return : 'a -> 'a Lwt.t Use it when you must
return something in the Lwt monad (for example in a service handler,
or often after a
An Eliom application is a cooperative program, as the server must be able to handle several requests at the same time. Ocsigen is using cooperative threading instead of the more widely used preemptive threading paradigm. It means that no scheduler will interrupt your functions whenever it wants. Switching from one thread to another is done only when there is a cooperation point.
We will use the term cooperative functions to identify functions
implemented in cooperative way, that is: if something takes
(potentially a long) time to complete (for example reading a value
from a database), they insert a cooperation point to let other threads
run. Cooperative functions return a value in the Lwt monad
(that is, a value of type
'a Lwt.t for some type
Lwt.return do not introduce cooperation points.
In our example, the function
Lwt_js_events.mouseup may introduce
a cooperation point, because it is unforseeable when this event
happens. That’s why it returns a value in the Lwt monad.
Using cooperative threads has a huge advantage: given that you know precisely where the cooperation points are, you need very few mutexes and you have very low risk of deadlocks!
Using Lwt is very easy and does not cause trouble, provided you never use blocking functions (non-cooperative functions). Blocking functions can cause the entre server to hang! Remember:
Lwt_unixinstead of module
Lwt.binddoes not introduce any cooperation point.
allows easily defining event listeners using Lwt. For example,
Lwt_js_events.click takes a
DOM element and returns an Lwt thread that will wait until a click
occures on this element.
Functions with an ending “s” (
Lwt_js_events.mousedowns, …) start again waiting after the
Lwt.pick behaves as the first thread
in the list to terminate, and cancels the others.
(Client server communication)
In order to see what other users are drawing, we now want to do the following:
We first declare a type, shared by the server and the client, describing the color (as RGB values) and coordinates of drawn lines.
We annotate the type declaration with
[@@deriving json] to allow
type-safe deserialization of this type. Eliom forces you to use this
in order to avoid server crashes if a client sends corrupted data.
This is defined using a JSON plugin for
ppx_deriving, which you
need to install. You need to do that for each type of data sent by the
client to the server. This annotation can only be added on types
containing exclusively basic types, or other types annotated with
Then we create an Eliom bus to broadcast drawing events to all client
with the function
This function take as parameter the type of
values carried by the bus.
To write draw commands into the bus, we just replace the function
Finally, to interpret the draw orders read on the bus, we add the
following line at the end of function
Now you can try the program using two browser windows to see that the lines are drawn on both windows.
Eliom provides multiple ways for the server to send unsolicited data to the client:
Eliom_bus.tare broadcasting channels where client and server can participate (see also «a_api project=”eliom” subproject=”client” | type Eliom_bus.t » in the client API).
Eliom_reactallows sending React events from the server to the client, and conversely.
Eliom_comet.Channel.t are one-way communication channels
allowing finer-grained control. It allows sending
to the client.
Eliom_bus are implemented over
It is possible to control the idle behaviour with module
(Widgets with Ocsigen-widgets)
In this section, we add a color picker and slider to choose the size
of the brush. For the colorpicker we used a widget available in
To install Ocsigen widgets, do:
opam pin add ocsigen-widgets https://github.com/ocsigen/ocsigen-widgets.git opam install ocsigen-widgets
Makefile.options, created by Eliom’s distillery, add
ocsigen-widgets.client to the
CLIENT_PACKAGES := ... ocsigen-widgets.client
To create the widget, we add the following code in the
init_client immediately after canvas configuration:
We subsequently add a simple HTML5 slider to change the size of the
brush. Near the
canvas_elt definition, simply add the following
Form.int is a typing information telling that this input takes
an integer value. This kind of input can only be associated to
services taking an integer as parameter.
We then add the slider to the page body, as follows:
To change the size and the color of the brush, we replace the last
line of the function
Finally, we need to add a stylesheet in the headers of our page. To
easily create the
head HTML element, we use the function
You need to install the corresponding stylesheets and images into your
project. The stylesheet files should go to the directory
graffiti.css is a custom-made CSS file.
You can then test your application (
Ocsigen-widgets is a Js_of_ocaml library providing useful widgets for your Eliom applications. You can use it for building complex user interfaces.
(Services sending other data types)
To finish the first part of the tutorial, we want to save the current drawing on server side and send the current image when a new user arrives. To do that, we will use the Cairo binding for OCaml.
For using Cairo, first, make sure that it is installed (it is
cairo2 via OPAM). Second, add it to the
SERVER_PACKAGES in your
SERVER_PACKAGES := ... cairo2
draw_server function below is the equivalent of the
draw function on the server side and the
function outputs the PNG image in a string.
We also define a service that sends the picture:
Eliom_registration defines several modules with
registration functions for a variety of data types. We have already
Eliom_registration.String sends arbitrary byte output
(represented by an OCaml string). The handler function must return
a pair consisting of the content and the content-type.
There are also several other output modules, for example:
Eliom_registration.Fileto send static files
Eliom_registration.Redirectionto create a redirection towards another page
Eliom_registration.Anyto create services that decide late what they want to send
Eliom_registration.Ocamlto send any OCaml data to be used in a client side program
Eliom_registration.Actionto create service with no output (the handler function just performs a side effect on the server) and reload the current page (or not). We will see an example of actions in the next chapter.
We now want to load the initial image once the canvas is created. Add the following lines just between the creation of the canvas context and the creation of the slider:
You are then ready to try your graffiti-application by
Note, that the
Makefile from the distillery automatically adds
the packages defined in
SERVER_PACKAGES as an extension in your
<extension findlib-package="cairo2" />