Newbie FAQ

This page is for asking (and answering) real beginners questions about OCaml.

If you just have a question, it's probably best to ask it on the ocaml_beginners mailing list (see also other resources for beginners).

How to intercept stdout?

I have moved this question to the here on the ocaml_beginners mailing list. -- Richard W.M. Jones

I've made small cgi library for my primitive web based Ocaml IDE. ( http://check-these.info/tools/ OcamlEditor )

I want it to intercept any output to the stdout, so that it can send http header before the text output.

In short, I want to do something like stupid PHP is doing so that someone who writes CGI can include; open "easycgi";; and it takes care of form decoding and header sending.

For example, in Python, it's easy to create an object that replaces stdout, and send header when it detects the first output to stdout, as well as providing a method to specify headers to be sent.

How can we do that in Ocaml?

-- Thank you Richard, but I can't really respond there.

I thought it was a simple question. I want to intercept and filter stdout (sort of) when print_string and other functions output.

I know there is Unix.dup and Unix.dup2, and there are pipe related functions.

So, if I create a pipe and "dup2" stdout to it, maybe it works. But I was wondering if there is a way to avoid making pipe, by simply intercepting stdout inside Ocaml implementation (or runtime?) before it goes out.

-- PS. While searching, I came accross your question asking about line buffering (which is a little similar to my problem). http://caml.inria.fr/pub/ml-archives/caml-list/2004/12/95bfd8abe8c497bb06cb3ace7c7f4bdb.en.html

I was hoping to find a way to avoid forking or using specialized functions (such as extlib IO.something).

I'll continue to search and wait if someone comes up with bright idea.

I finaly made it, I think. (It's a ugly bunch of codes, though...)

I would appreciate if someone can suggest better solution. If you want to move this Question and code to somewhere else, please do so.

let header = ref "Content-type: text/html\n\n";; (* Default header data *)
let header_sent = ref false;; (* flag: true if header is sent *)
let (pin, pout) = Unix.pipe ();; (* pipe to redirect stdout till header is sent *)
Unix.set_nonblock pin;; (* non blocking for polling with sigalarm timer *)
let sout = Unix.dup Unix.stdout;; (* Save stdout *)
let prs s = ignore(Unix.write sout s 0 (String.length s));; (* helper func *)
Unix.dup2 sout Unix.stderr;; (* Append stderr to stdout to get errors on browser *)
Unix.dup2 pout Unix.stdout;; (* redirect stdout to the pipe *)
let start_timer () = Unix.setitimer Unix.ITIMER_REAL 
  {Unix.it_interval = 0.0000001; Unix.it_value = 0.0000001} ;;  (* Timer function *)
(* Sigalarm handling func *)
let check_pin signal =
 if not !header_sent then (
   let s = String.create 256 in
   let nc = ref 0 in
   (try 
     nc := Unix.read pin s 0 256; (* Try to read from pipe *)
     ignore(Unix.setitimer Unix.ITIMER_REAL 
      {Unix.it_interval = 0.0; Unix.it_value = 0.0} ); (* reset timer (with success of read) *)
     Unix.dup2 sout Unix.stdout;    (* Restore stdout *)
     prs !header; (* Output header *)
     header_sent := true;
     prs (String.sub s 0 !nc); (* send initial data to stdout *)
     (try while true do
       nc := Unix.read pin s 0 256;
       prs (String.sub s 0 !nc); (* send the rest of data if any to stdout *)
     done; with _ -> ());
   with _ -> ignore (start_timer ()) ) (* restart timer if no data yet *)
 ) ;;
Sys.set_signal Sys.sigalrm (Sys.Signal_handle check_pin);; (* set signal func *)
at_exit (fun () -> check_pin Sys.sigalrm); (* make sure to send header+data before exit *)
start_timer ();; (* Everything ready. Start timer *)
(* Now, before any out put will be kept in pipe
  and http response header will be prefixed without fail. 
  This mimics stupid behavior of PHP and facilitate
  initail adaptation of PHP user.  *)
print_string "<h1>Output to browser</h1>\n";; (* example *)

==Further testing revealed problem if this code is used in the child process as well as the parent. Going back for testing and debugging....