Today's example focuses on co-services. A co-service is basically a service that shares its URL with a "parent" service, but which may have different parameters. Co-services are often used together with sessions to personalise a given URL for one specific user.
Since co-services share the same URL, how does Eliom know which one is being invoked? That distinction is made via a special hidden parameter. However, it is nothing that you should be worried about, as it happens transparently to the developer.
The code below is a minimalistic illustration of the possible use for a co-service. I suggest that you use the "view source" functionality of your browser to observe the hidden parameter contained in the page.
(************************************************************************)
(* Coservice demonstration. *)
(************************************************************************)
open XHTML.M
(************************************************************************)
(* Declaration of the "foobar" service. Note that we are not
registering it yet, and therefore we do not provide the handler
at this point.
*)
let foobar_service =
Eliom_services.new_service
~path: [""]
~get_params: Eliom_parameters.unit
()
(************************************************************************)
(* Declaration of the "foobar2" service, which is actually a
coservice for "foobar". Coservices share the same path as
the main service, but may have different parameters.
*)
let foobar2_service =
Eliom_services.new_coservice
~fallback: foobar_service
~get_params: (Eliom_parameters.int "amount")
()
(************************************************************************)
(* Handler for "foobar" service. It displays the current value of
the counter and provides links to reload itself or to invoke the
coservice with different parameters.
*)
let counter = ref 0
let foobar_handler sp () () =
Lwt.return
(html
(head (title (pcdata "")) [])
(body [p [
pcdata "counter is equal to ";
pcdata (string_of_int !counter);
br ();
Eliom_predefmod.Xhtml.a foobar_service sp [pcdata "reload"] ();
br ();
Eliom_predefmod.Xhtml.a foobar2_service sp [pcdata "increment by 1"] 1;
br ();
Eliom_predefmod.Xhtml.a foobar2_service sp [pcdata "increment by 2"] 2;
]]))
(************************************************************************)
(* Handler for the "foobar2" service (the coservice of "foobar").
Note that this handler just increments the counter by the amount
specified in the GET parameter and then invokes the handler of
"foobar" to display the actual page.
*)
let foobar2_handler sp amount () =
counter := !counter + amount;
foobar_handler sp () ()
(************************************************************************)
(* Registration of the services.
*)
let () =
Eliom_predefmod.Xhtml.register foobar_service foobar_handler;
Eliom_predefmod.Xhtml.register foobar2_service foobar2_handler
In the previous instalment of this series, the example illustrated the creation of a form for a GET service. This time we'll see a similar example, but one whose service uses POST instead of GET.
GET and POST are just two of the verbs part of the HTTP specification (other verbs are HEAD, PUT, DELETE, etc). The main visible difference is that a GET request encodes the parameters into the URL, whereas POST does so in the body of the request. It is therefore common that web developers pick one or the other based solely on this distinction. This criterion is however fundamentally wrong. GET should be used only for services that do not cause side-effects, and which are typically idempotent. Using GET disregarding this rule may subject your users to possible cross-site request forgery attacks.
As illustrated by the code below, the first thing to notice about POST services is that they require the declaration of the GET fallback service. The reason is because POST services encode parameters in the body of the request, and therefore their parameters are not recorded if the user bookmarks the URL. This requirement may seem annoying at first, but it fits very well with the emphasis on correctness that permeates Ocsigen/Eliom (and the Ocaml ecosystem in general). Another thing to notice is that function Eliom_predefmod.Xhtml.post_form (the POST counterpart of Eliom_predefmod.Xhtml.get_form) also requires the GET parameters for the service.
When trying this example, I suggest you reload the "coucou" URL after its first invocation via the form. You will see that upon the second invocation the fallback page is displayed, because no POST parameters were specified. (Depending on the browser, simply hitting "reload" will resend the POST parameters; therefore, select the URL and press ENTER for a clean invocation).
(************************************************************************)
(* Services with POST parameters. *)
(************************************************************************)
open XHTML.M
open Eliom_parameters
(************************************************************************)
(* This service is just the fallback for the "coucou" service, and
is never meant to be directly invoked. Eliom forces one to declare
these fallbacks because browsers bookmark only the GET parameters
and not the POST ones. There is therefore a chance the service is
invoked without any POST parameters.
*)
let fallback_handler _ () () =
Lwt.return
(html
(head (title (pcdata "Fallback")) [])
(body [p [pcdata "You've invoked the fallback service for coucou"]]))
let fallback_service =
Eliom_predefmod.Xhtml.register_new_service
~path: ["coucou"]
~get_params: Eliom_parameters.unit
fallback_handler
(************************************************************************)
(* Service "coucou" takes no GET parameters and one POST parameter.
Note that during the service registration the service path is not
indicated, since it will be the same as the fallback's. The same
goes for the GET parameters.
*)
let coucou_handler _ () foo =
Lwt.return
(html
(head (title (pcdata "Coucou")) [])
(body [p [pcdata ("The POST parameter is " ^ foo)]]))
let coucou_service =
Eliom_predefmod.Xhtml.register_new_post_service
~fallback: fallback_service
~post_params: (Eliom_parameters.string "foo")
coucou_handler
(************************************************************************)
(* This function creates a form for the "coucou" service. Note that
proper XHTML forms should be divided into fieldsets, and that any
input widget should have a matching label, as indicated via its ID.
*)
let coucou_form enter_foo =
let enter_foo_label = "enter_foo"
in [
fieldset
[
label ~a:[a_for enter_foo_label] [pcdata "String foo:"];
Eliom_predefmod.Xhtml.string_input ~a:[a_id enter_foo_label] ~input_type:`Text ~name:enter_foo ();
Eliom_predefmod.Xhtml.string_input ~input_type:`Submit ~value:"Click" ()
]
]
(************************************************************************)
(* The "main" service creates a form for the "coucou" POST service.
*)
let main_handler sp () () =
Lwt.return
(html
(head (title (pcdata "Main")) [])
(body [
p [pcdata "Please fill in this form:"];
Eliom_predefmod.Xhtml.post_form coucou_service sp coucou_form ()
]))
let main_service =
Eliom_predefmod.Xhtml.register_new_service
~path: [""]
~get_params: Eliom_parameters.unit
main_handler
We've previously seen that Eliom ensures that hyperlinks to services are type-safe, ie, you cannot for example provide an alphabetic string (or no parameters at all) to a service that expects an integer. This type-safety is also extended to the creation and validation of XHTML forms. You can therefore provide together with a service a function that creates forms for that service. Eliom — or rather the compiler — makes sure the types are in harmony. Moreover, bear in mind that before invoking a service with any parameters, Eliom first checks that the parameters have values as expected. It will therefore raise an error if the user provided us with an alphabetic string in a form field that required an integer or a float, for example (it is of course possible to provide a custom handler for this sort of error situations instead of relying on the default one — check the Eliom tutorial for details).
Another cool feature is the possibility of Eliom services accepting custom types, and not just the predefined integers, floats, strings, etc. Again, check the tutorial for details.
The code below demonstrates the use of GET forms. It defines a "coucou" service taking three GET parameters of different types, and defines also a function "coucou_form" to create the contents of a form for "coucou". In service "main", this function is used as a parameter to Eliom_predefmod.Xhtml.get_form to actually create the form.
(************************************************************************)
(* Demonstration of forms for services with GET parameters. *)
(************************************************************************)
open XHTML.M
open Eliom_parameters
(************************************************************************)
(* Service "coucou" takes three GET parameters and no POST parameters.
*)
let coucou_handler _ (i, (j, s)) () =
Lwt.return
(html
(head (title (pcdata "Coucou")) [])
(body [p
[
pcdata "You sent: ";
strong [pcdata (string_of_int i)];
pcdata ", ";
strong [pcdata (string_of_float j)];
pcdata " and ";
strong [pcdata s]
]]))
let coucou_service =
Eliom_predefmod.Xhtml.register_new_service
~path: ["coucou"]
~get_params: (Eliom_parameters.int "i" ** (Eliom_parameters.float "j" ** Eliom_parameters.string "s"))
coucou_handler
(************************************************************************)
(* This function is used for building forms for service "coucou".
Note that its parameter is a nested pair defined in the same
fashion as the GET parameters for "coucou". Also note that
proper XHTML forms should make use of fieldsets for logically
dividing the form, and that field labels should be declared
with the
In the examples we've seen so far, GET parameters were passed using the standard scheme (RFC 3986), ie, an ampersand delimited list of key-value assignments. As an alternative, Eliom offers also the possibility of encoding parameters as slash-delimited suffixes to the main URL. For many applications this produces friendlier and cleaner looking URLs.
As an example, consider a service "foobar" taking three integers as parameter: "year", "month", and "day". In the standard scheme, the service could be invoked with today's date by calling "foobar?year=2009&month=3&day=17". Using the suffix scheme, the same invocation becomes "foobar/2009/3/17". You trade the flexibility of specifying parameters by any order for the improved legibility.
The code sample below shows the definition of the service "foobar" using suffix parameters. Note that only the declaration of the service needs to change; any links towards this service created with Eliom_predefmod.Xhtml.a will automatically reflect the new convention (yet another reason to always use Eliom_predefmod.Xhtml.a!).
(************************************************************************)
(* Example for suffix parameters. *)
(************************************************************************)
open XHTML.M
open Eliom_parameters
(************************************************************************)
(* The "foobar" service takes three GET parameters, all integers.
It illustrates the use of Eliom_parameters.suffix to construct
friendlier looking URLs.
*)
let foobar_handler _ (year, (month, day)) () =
let date = (string_of_int year) ^ "/" ^ (string_of_int month) ^ "/" ^ (string_of_int day)
in Lwt.return
(html
(head (title (pcdata "Foobar")) [])
(body [p [pcdata ("The date specified by the GET parameters is " ^ date)]]))
let foobar_service =
Eliom_predefmod.Xhtml.register_new_service
~path: ["foobar"]
~get_params: (Eliom_parameters.suffix
(Eliom_parameters.int "year" **
(Eliom_parameters.int "month" **
Eliom_parameters.int "day")))
foobar_handler
(************************************************************************)
(* The "main" service just provides a link to the "foobar" service.
*)
let main_handler sp () () =
Lwt.return
(html
(head (title (pcdata "Main")) [])
(body [p [Eliom_predefmod.Xhtml.a foobar_service sp [pcdata "Click for foobar"] (2009, (3, 17))]]))
let main_service =
Eliom_predefmod.Xhtml.register_new_service
~path: [""]
~get_params: Eliom_parameters.unit
main_handler
In the example from the previous article, the various services are registered with Eliom upon declaration. Note that we used the function Eliom_predefmod.Xhtml.register_new_service for this purpose. It is however also possible to separate these two actions, by first just declaring a service, and registering it later. You may use function Eliom_services.new_service for the former action, and function Eliom_predefmod.Xhtml.register for the latter.
Why is the separation between the declaration and registration actions useful? The most obvious application lies in services that call each other. Without this feature, you would most likely would need to resort to a "let rec ... and ..." construct, which isn't always the most practical.
The code below depicts two services, "coucou" and "foobar", which call each other. Note that we begin by just declaring each service and that we only register them in the end.
(************************************************************************)
(* Example for delayed registration. *)
(************************************************************************)
open XHTML.M
open Eliom_parameters
(************************************************************************)
(* We begin by declaring services "coucou" and "foobar". The first
takes no parameters, while the second takes a float as a GET
parameter.
*)
let coucou_service =
Eliom_services.new_service
~path: ["coucou"]
~get_params: Eliom_parameters.unit
()
let foobar_service =
Eliom_services.new_service
~path: ["foobar"]
~get_params: (Eliom_parameters.float "x")
()
(************************************************************************)
(* We now build the handler functions for each service. Note how
each handler creates a link to the other service.
*)
let coucou_handler sp () () =
Lwt.return
(html
(head (title (pcdata "Coucou")) [])
(body [p
[
pcdata "Here's a ";
Eliom_predefmod.Xhtml.a foobar_service sp [pcdata "link"] 3.1416;
pcdata " to service foobar";
]]))
let foobar_handler sp x () =
Lwt.return
(html
(head (title (pcdata "Foobar")) [])
(body [p
[
pcdata ("The parameter X is " ^ (string_of_float x));
br ();
pcdata "Here's a ";
Eliom_predefmod.Xhtml.a coucou_service sp [pcdata "link"] ();
pcdata " to service coucou";
]]))
(************************************************************************)
(* Finally we register the declared services and assign to each
one its handler function.
*)
let () =
Eliom_predefmod.Xhtml.register coucou_service coucou_handler;
Eliom_predefmod.Xhtml.register foobar_service foobar_handler
Postscriptum: If you're on the bleeding edge, you may have noted that the ocsigen.conf file I presented in the first chapter of this series needs a tweak in order to work with latest development version of Ocsigen (soon to be version 1.2). The change is minimal: basically, the "hostname" attribute for the host configuration is now called "defaulthostname". Here's the revised file:
<ocsigen>
<server>
<port>8080</port>
<charset>utf-8</charset>
<mimefile>/etc/mime.types</mimefile>
<debugmode/>
<findlib path="/home/dario/.local/lib/ocsigen/METAS"/>
<findlib path="."/>
<extension findlib-package="ocsigen_ext.ocsipersist-sqlite"/>
<extension findlib-package="ocsigen_ext.eliom"/>
<extension findlib-package="ocsigen_ext.staticmod"/>
<host defaulthostname="localhost">
<site dir="">
<eliom findlib-package="module"/>
<static dir="./"/>
</site>
</host>
</server>
</ocsigen>
The second instalment of this series presents the first actual Eliom code sample. You can use the Makefile shown in the first part to compile it, and the META.module and ocsigen.conf files for starting the Ocsigen web server.
This example contains three different services: first, the hello-worldish service "coucou" (so named as a homage of sorts to the convention used in the Eliom tutorial); second, the "foobar" service, which illustrates the use of GET parameters; and third, the "main" service, which makes use of Eliom_predefmod.Xhtml.a to create type safe hyperlinks to the first two services. I suggest you fiddle with the types of the GET parameters — you'll see that Eliom makes good use of the type safety of the Ocaml language by making it impossible to create hyperlinks that pass the wrong type to a service.
(************************************************************************)
(* Simple "Hello World!" Eliom examples. *)
(************************************************************************)
open XHTML.M
open Eliom_parameters
(************************************************************************)
(* The "coucou" service takes no arguments, and just displays a
welcome message with the client's IP address. The IP can be
obtained via the "sp" (server parameters) parameter that is
passed to every handler.
*)
let coucou_handler sp () () =
let greeting = "Hello " ^ (Eliom_sessions.get_remote_ip sp) ^ "!"
in Lwt.return
(html
(head (title (pcdata "Coucou")) [])
(body [p [pcdata greeting]]))
let coucou_service =
Eliom_predefmod.Xhtml.register_new_service
~path: ["coucou"]
~get_params: Eliom_parameters.unit
coucou_handler
(************************************************************************)
(* The "foobar" service takes two GET parameters, an integer "i"
and a string "s". Note that the handler is declared as an
anonymous function instead of as a separate function like we
did with "coucou". Though sometimes useful, this practice can
lead to confusing code; for this reason, this is the only time
we'll use it in this tutorial series.
*)
let foobar_service =
Eliom_predefmod.Xhtml.register_new_service
~path: ["foobar"]
~get_params: (Eliom_parameters.int "i" ** Eliom_parameters.string "s")
(fun _ (i, s) () ->
Lwt.return
(html
(head (title (pcdata "Foobar")) [])
(body [p [pcdata ("The GET parameters are: " ^ (string_of_int i) ^ " and " ^ s)]])))
(************************************************************************)
(* The "main" service just provides links to both the "coucou" and
"foobar" services. Try changing the types of the parameters to
see that the compiler catches any typing inconstencies.
*)
let main_handler sp () () =
Lwt.return
(html
(head (title (pcdata "Main")) [])
(body [
p [
pcdata "Here's a ";
Eliom_predefmod.Xhtml.a coucou_service sp [pcdata "link"] ();
pcdata " to the coucou service"
];
p [
pcdata "And here's a ";
Eliom_predefmod.Xhtml.a foobar_service sp [pcdata "link"] (10, "hello");
pcdata " to the foobar service"
]
]))
let main_service =
Eliom_predefmod.Xhtml.register_new_service
~path: [""]
~get_params: Eliom_parameters.unit
main_handler
This article is the first of a series I'll be doing on Ocsigen programming. Ocsigen is both a web server and the umbrella name for an Ocaml-based framework that brings the power and safety of functional programming to the world of web development. Strictly speaking, this tutorial is about Eliom, the actual programming framework part of Ocsigen. However, because the two are closely related, and because the name "Ocsigen" has much better recognition, I've used it as a title.
Note that the Ocsigen site already offers a very comprehensive tutorial on Eliom. This series of articles is not intended to compete with it. In fact, I won't be doing any tutoring at all and I'll assume that readers are familiar with the existing tutorial. The goal of this series is simply to present self-contained and documented code samples illustrating various aspects of Eliom programming. These are taken from my own self-study experiments with Eliom, and I'm only sharing them with the hope they might be useful to others.
Let's start with the development setup. All the examples in this series can be compiled with the same Makefile and invoked with the same ocsigen.conf configuration file for the server and META file with findlib information. For the sake of simplicity, you can put all of these files in the same directory and just run the Ocsigen web server from that directory in debug mode by invoking "ocsigen -v -c ocsigen.conf" for the byte-code version or "ocsigen.opt -v -c ocsigen.conf" for the native-code version (if you're running Ocaml >= 3.11). The Makefile is as follows:
NAME=module
all: $(NAME).cma $(NAME).cmxs
%.cma: %.cmo
ocamlc -a -o $@ $+
%.cmxa: %.cmx
ocamlopt -a -o $@ $+
%.cmxs: %.cmxa
ocamlopt -shared -linkall -I `pwd` -o $@ $<
%.cmo: %.ml
ocamlfind ocamlc -thread -package lwt,ocsigen -c $<
%.cmx: %.ml
ocamlfind ocamlopt -thread -package lwt,ocsigen -c $<
clean:
rm -f $(NAME).cm[ioax] $(NAME).cmx[as] $(NAME).[oa]
As for the ocsigen.conf file, it can be defined like the code below (just change the findlib path according to your installation). Note the we're using port 8080 so the server can be started by any user:
<ocsigen>
<server>
<port>8080</port>
<charset>utf-8</charset>
<mimefile>/etc/mime.types</mimefile>
<debugmode/>
<findlib path="/home/dario/.local/lib/ocsigen/METAS"/>
<findlib path="."/>
<extension findlib-package="ocsigen_ext.ocsipersist-sqlite"/>
<extension findlib-package="ocsigen_ext.eliom"/>
<extension findlib-package="ocsigen_ext.staticmod"/>
<host hostname="dual">
<site dir="">
<eliom findlib-package="module"/>
<static dir="./"/>
</site>
</host>
</server>
</ocsigen>
Finally, note that because we rely on findlib for the installation of the module containing the actual code for the web site, we must provide a META file indicating the dependencies and the names of the compiled code units. As per the ocsigen.conf declaration, the name of this file must be META.module:
directory = "."
requires = ""
archive(plugin,byte) = "module.cma"
archive(plugin,native) = "module.cmxs"
And that's it as far as the development setup is concerned. The next article will present the first real example. Stay tuned!
I mentioned Eben Moglen before on this blog. If you are still not familiar with any of his writings, I strongly suggest you listen to this episode of the Software Freedom Law Show podcast. It features a recording of one of Eben's recent talks on the origins of copyright and patents. It is well worth it, I promise.
I recommend a moment of Zen before breakfast. Here's one for the holidays:
Consider the set S of all possible files of length n. A file compressor (such as Gzip, 7-Zip, and many others) is a programme that given any randomly chosen file from S, returns a new file whose size is on average greater than or equal to n.
Meditate on the statement above until you realise its truth. Then, grasshopper, you shall be enlightened.
Note: The "equal to" part is necessary because there is an entire family of compressors (the identity compressor being the most obvious one) that always return a file the same size as the one provided as input.
Last time, I told you about Blahcaml, a library that wraps Blahtex, thus providing facilities for converting equations in TeX format into MathML. The news is that in the meantime I've completed the missing pieces in Blahcaml (namely proper error reporting), and released version 1.2. Functionality-wise, I consider the library "finished". Please let me know if you find any glaring omissions or bugs.
Another feature I want for Lambdoc is the ability to display syntax-highlighted source code. And so I spent some time investigating the available options for highlighting source code within Ocaml. Note that I wanted a generic highlighter, one that could handle most common programming and markup languages. Because there is no Ocaml library up to the task, I looked at the C/C++ options and even at the possibility of co-opting Vim for the task.
Using Vim is a seductive idea given the high-quality of Vim's syntax-highlighting, but unfortunately flounders on Vim's manifest unsuitability to be used inside scripts. Yes, you can invoke Vim from the command line with a sequence of commands, but I saw no way of camouflaging the fact that Vim's interface still pops up, even if for a split-second.
On the C/C++ side the options are GNU Source-highlight and Highlight. While both are command line applications, their core routines can easily be extracted and made into a library. Moreover, their licenses are friendly to this operation. The downside is that the quality of the highlight of either application is not as good as Vim's, at least as far as highlighting Ocaml is concerned.
I've chosen Highlight for the time being. The wrapper library is called Camlhighlight and is now at version 0.5. While there are still some features missing, it's already quite usable. The library has functions for generating an Ocsigen-friendly XHTML.M output, both in fancy and more plain style (it's possible to customise whether or not one wants line numbers and/or zebra stripes).