webiface.ml 7.96 KB
Newer Older
1
2
3
4
5
6
(* TODO:
   - correct error messages, not failwith "..."
   - HTML design, logo
   - dump
*)

7
open Netcgi
8
exception Timeout
9

10
11
12
13
14
15
let operating_type = Netcgi.buffered_transactional_optype
let cgi = new Netcgi.std_activation ~operating_type ()

let fatal_error title s =
  cgi # output # rollback_work();
  cgi # set_header 
16
    ~content_type:"text/html; charset=\"iso-8859-1\""
17
18
19
20
    ~cache:`No_cache 
    ();
  cgi # output # output_string ("<h1>" ^ title ^ "</h1>");
  cgi # output # output_string s;
21
  cgi # output # output_string "\n";
22
  cgi # output # commit_work();
23
24
  cgi # finalize ();
  exit 0
25
26


27
28
(* Configuration *)

29
let session_dirs = [ "/home/beppe/sessions"; "/home/frisch/sessions"; "/users/formel/cduce/sessions"]
30
31
let session_dir = 
  try List.find Sys.file_exists session_dirs
32
33
  with Not_found -> fatal_error "Internal error" 
    "Cannot find sessions directory"
34
35
36
37
38
let timeout = 60 * 5  (* seconds *)
let max_sess = 10

(*****************)

39

40
41
42
43
44
45
(* Loading examples *)

let example code = 
  try List.assoc code Examples.examples
  with Not_found -> ""

46
47
48
49
50
51
52
let begin_table =
"<table width='100%' border=0 cellspacing=0 cellpadding=2 bgcolor=black>
<tr><td>
<table width='100%' border=0 cellspacing=0 cellpadding=3 bgcolor=white>
<tr><td>"

let end_table = "</td></tr></table></td></tr></table><br>"
53

54
55
56
57
let persistant = ref false
let session_id = ref ""

let html_header p =
58
59
60
61
62
63
  p "<html><head><title>CDuce online prototype</title></head>";
  p "<body bgcolor='#BBDDFF'>";
  p begin_table;
  p "<h1>CDuce online prototype</h1>";
  p end_table;

64
  if !persistant then 
65
66
    (p begin_table;
     p "You're running the CDuce prototype in session mode: values and
67
68
types accepted by CDuce when you click 'Submit' will be available
for subsequent requests.";
69
70
71
72
73
(*     p "<small> (session #"; p !session_id; p ")</small>"; *)
     p end_table)
  else
    (p begin_table;
     p "This page is a front-end to a prototype implementation of CDuce.";
74
     p "You can start from one of the predefined examples below or try ";
75
76
77
78
79
     p "with you own program...";
     p "The session mode remembers CDuce definitions across requests.";
     p Examples.present;
     p end_table)

80
81

let html_form p content =
82
83
  p begin_table;
  p "<h2>Input</h2>";
84
  p "<form method=post>";
85
  p "<input type=submit name=exec value='Submit to CDuce'>";
86
  if !persistant then(
87
88
89
    p "<input type=submit name=dump value='Show current environment'>";
    p "<input type=submit name=close value='Close session'>";
    p "<input type=hidden name=session value='"; p !session_id; p "'>";
90
91
92
93
  ) else (
    p "<input type=submit name=open value=\"Initiate session\">";
  );
  p "<br><textarea name=prog cols=80 rows=25>"; p content; p "</textarea>";
94
95
  p "</form>";
  p end_table
96
97
98


let html_footer p =
99
  p "</body></html>\n"
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139


let () =
  Random.self_init (); 
  State.close ()

let session_file sid =
  Filename.concat session_dir sid

let gen_session_id () =  string_of_int (Random.bits ())

let check_session_id sid =
  try ignore (int_of_string sid)
  with _ -> failwith "Invalid session id"

let close_session sid =
  check_session_id sid;
  try Unix.unlink (session_file sid)
  with Unix.Unix_error (_,_,_) -> ()

let flush_sessions () =
  let time = Unix.time () -. (float timeout) in
  let n = ref 0 in
  let dir = Unix.opendir session_dir in 
  try while true do
    let f = session_file (Unix.readdir dir) in
    let st = Unix.stat f in
    if (st.Unix.st_kind = Unix.S_REG) then
      if  (st.Unix.st_mtime < time) 
      then Unix.unlink f
      else incr n
  done; assert false with End_of_file ->
    Unix.closedir dir;
    !n


let cmds = [ "open", `Open;
	     "close", `Close;
	     "dump", `Dump;
	     "exec", `Exec;
140
	     "example", `Example;
141
142
	     "new", `New;
	   ]
143

144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
let cut p w s =
  let rec aux i x =
    if i < String.length s then
      match s.[i] with
	| '\n' -> p '\n'; aux (i + 1) 0
	| '\r' -> aux (i + 1) 0
	| c -> 
	    let x = if x = w then (p '\\'; p '\n'; p ':'; 2) else (x + 1) in
	    p c; 
	    if c = '&' then
	      let rec ent i =
		p s.[i];
		if (s.[i] = ';') then aux (i + 1) x else ent (i + 1) in
	      ent (i + 1)
	    else
	      aux (i + 1) x
  in
  aux 0 0

163
let main (cgi : Netcgi.std_activation) =
164
165
  let p = cgi # output # output_string in
  let clicked s = cgi # argument_value s <> "" in
166
  try
167
    let nb_sessions = flush_sessions () in
168
169
170
    cgi # set_header
      ~content_type:"text/html; charset=\"iso-8859-1\""
      ();
171

172
173
174
175
176
177
178
179
    let cmd = 
      try snd (List.find (fun (x,y) -> clicked x) cmds)
      with Not_found -> `New in

    let sid = match cmd with
      | `Open ->
	  if (nb_sessions >= max_sess) then
	    failwith "Too many open sessions ...";
180
181
182
183
184
	  let sid = gen_session_id () in
	  (* touch the session file ... *)
	  let chan = open_out_bin (session_file sid) in
	  close_out chan;
	  sid
185
186
187
188
189
190
191
192
193
      | `Close -> close_session (cgi # argument_value "session"); ""
      | `New ->  ""
      | _ -> cgi # argument_value "session"
    in
    session_id := sid;
    persistant := !session_id <> "";
    if !persistant then check_session_id !session_id;
      
    let dialog content = html_form p content in
194
195
196

    let load_state () =
      if !persistant then
197
198
	try
	  let chan = open_in_bin (session_file !session_id) in
199
200
201
	  if in_channel_length chan > 0 then
	    (let s = Marshal.from_channel chan in
	     State.set s);
202
	  close_in chan;
203
204
	with Sys_error _ ->
	  failwith "This session has expired ..."
205
    in
206

207
208
209
210
211
212
213
214
215
216
217
218
    let store_state () =
      if !persistant then
	let s = State.get () in
	let chan = open_out_bin (session_file !session_id) in
	Marshal.to_channel chan s [ Marshal.Closures ];
	close_out chan
    in
    
    let exec src =
      let ppf = Format.str_formatter
      and input = Stream.of_string src in
      Location.set_source (`String src);
219
      Location.set_protected true;
220
      
221
      Location.warning_ppf := ppf;
222
      let ok = Cduce.run ppf ppf input in
223
224
      if ok then Format.fprintf ppf "@\nOk.@\n";
      let res = Format.flush_str_formatter () in
225
226
227
228
      p begin_table;
      p "<h2>Results</h2>";
      p "<pre>"; cut (cgi # output # output_char) 80 res;  p "</pre>";
      p end_table;
229
230
      if ok then (dialog ""; store_state ()) else dialog src;
    in
231

232
233
234
235
236
237
    let dump src =
      let ppf = Format.str_formatter in

      Cduce.dump_env ppf;

      let res = Format.flush_str_formatter () in
238
239
240
241
      p begin_table;
      p "<h2>Current session environment</h2>";
      p ("<pre>" ^ res ^ "</pre>");
      p end_table;
242
      dialog src
243
244
    in

245
    Location.set_viewport `Html;
246
247
    load_state ();
    store_state ();  (* Just touch the file ... *)
248
249
250
251
252
253
    html_header p;
    let prog = cgi # argument_value "prog" in
    (match cmd with
       | `Exec -> exec prog
       | `Open -> dialog prog
       | `New -> dialog ""
254
       | `Dump -> dump prog
255
       | `Close -> dialog ""
256
       | `Example -> dialog (example (cgi # argument_value "example"))
257
    );
258
259
260
261
262
263
264
265
266
267
268
    p begin_table;
    p "<h2>About the prototype</h2>";
    p "CDuce is under active development; some features may not work properly.";
    p "We are planning a beta release for the beginning of 2003. ";
    p "The prototype is written in ";
    p "<a href='http://www.caml.inria.fr'>Objective Caml</a>, ";
    p "and uses several OCaml packages: ";
    p "<a href='http://caml.inria.fr/camlp4'>Camlp4</a>, ";
    p "<a href='http://ocamlnet.sourceforge.net/'>OCamlnet</a>, ";
    p "<a href='http://www.ocaml-programming.de/programming/pxp.html'>PXP</a>, ";
    p "<a href='http://www.eleves.ens.fr/home/frisch/soft#wlex'>wlex</a>.";
269
    p "<p>";
270
271
272
    p "<a href='mailto:Alain.Frisch@ens.fr'>Webmaster</a>";
    p end_table;

273
    html_footer p;
274
275
276
    cgi # output # commit_work()
  with
      exn ->
277
278
279
	let msg = 
	  match exn with
	    | Unix.Unix_error (e,f,arg) ->
280
281
282
		"System error: " ^ (Unix.error_message e) ^ 
		"; function " ^ f ^ 
		"; argument " ^ arg
283
284
	    | Timeout ->
		"Timeout reached ! This prototype limits computation time ..."
285
286
287
288
	    | exn ->
	      Printexc.to_string exn
	in
	fatal_error "Internal software error!" msg
289
290

let () =
291
  ignore (Unix.alarm 20);
292
  Sys.set_signal Sys.sigalrm (Sys.Signal_handle (fun _ -> raise Timeout));
293
294
295
  main cgi;
  cgi # finalize ()