OCaml for the Skeptical
U of C Library DLDC Informal OCaml Class

Complete Applications

A Command-Line-Based Application

A complete OCaml application (a one-file application) typically consists of a sequence of let definitions terminated by the invocation of a "main" function. Let's construct a complete application — a version of the Unix cat command — around the cat function from some pages back.

let cat filename =
  let chan = open_in filename in
  let size = 4 * 1024 in
  let buffer = String.create size in
  let eof = ref false in
    while not !eof do
      let len = input chan buffer 0 size in
	if len > 0
	then print_string (String.sub buffer 0 len)
	else eof := true

let main () =
  let len = (Array.length Sys.argv) in
  let argv = (Array.sub Sys.argv 1 (len-1)) in (* skip argv0 *)
    Array.iter cat argv

let _ = main ()

The main function interfaces cat to the outside world, and emulates the Unix cat command which takes several filenames on the command line and concatenates their contents on standard output.

Unix processes access the command line through a vector (provided by the OS) known as argv (the argument vector). OCaml makes this available as an array in the Sys module called argv. Unix stores the name of the running process in the zeroth element of argv, so Sys.argv.(0) will be something like "cat". We need to ignore that (or we'd end up catting our own executable code!) so we use Array.sub to return the subarray of Sys.argv from element 1 (the second element) to the end. Finally, we implement the Unix cat command by using Array.iter to apply the cat function to each filename on the command line.

The very last line of the program actually invokes the main function by applying it to () (since the domain of main is unit). It's conventional in Ocaml to bind the return value of main (which we don't care about) to _ by way of ignoring it. This is purely conventional (as is the use of a function called main) and is really a way of avoiding a ;;. I recommend you always structure your application this way if you don't want to learn this little detail of the syntax.

Web-Based Applications

Roll-Your-Own CGI

Let's look at a simple CGI application that displays the IP address and hostname of the machine that makes the request. This program handles the CGI protocol itself (it doesn't even take any parameters, so this is easy):

(* report IP address and hostname of machine requesting this web page *)

(* format addr and host into HTML template*)
let html (addr, host) =
  Printf.printf "\
    Content-Type: text/html\n\
    <title>IP address and hostname</title>\n\
    <h1>IP address and hostname</h1>\n\
    <p>Your IP address is %s</p>\n\
    <p>Your hostname is %s</p>\n\
  " addr host

(* return pair of (ipaddr, hostname) strings; use CGI variable
 * REMOTE_ADDR for IP address and convert that to hostname if
 * possible
let addrhost () =
  let addr = Sys.getenv "REMOTE_ADDR" in
    addr, (Unix.gethostbyaddr (Unix.inet_addr_of_string addr)).Unix.h_name

let main () =
  html (addrhost ())

let _ = main ()

The only thing worth mentioning about this program is that it uses functions from the Unix module to do the DNS lookup, so that it doesn't have to exec an external program.

I compiled this program using ocamake with this command: ocamake -o address address.ml unix.cma. You can try it out here.

Third-Party CGI Solutions

More complex web applications might use the Cgi module, which is sufficient for simple applications (providing GET and POST handling and URL decoding) or ocamlnet, which provides a sophisticated O-O interface; here's a trivial example of the latter:

let actobj = new Netcgi.std_activation() in
  actobj # set_header();
  actobj # output # output_string "<HTML><BODY>Hello world!</BODY></HTML>\n";
  actobj # output # commit_work()