webiface.ml 8.5 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 = ["/usr/local/tmp/"; "/home/beppe/sessions"; "/home/frisch/sessions";
30
"/users/formel/cduce/sessions"; "/home/zack/cduce/sessions" ]
31
32
let session_dir = 
  try List.find Sys.file_exists session_dirs
33
34
  with Not_found -> fatal_error "Internal error" 
    "Cannot find sessions directory"
35
36
37
38
39
let timeout = 60 * 5  (* seconds *)
let max_sess = 10

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

40

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

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

47
48
let begin_table = "<div class=\"box\">"
let end_table = "</div>"
49

50
51
52
let persistant = ref false
let session_id = ref ""

53
54
55
let (|||) p x = p x; p
let (||=) p () = ()

56
let html_header p =
57
58
59
60
61
62
63
64
  p "
<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>
<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"
  \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">
<html>
<head>
  <meta content=\"text/html; charset=iso-8859-1\" 
        http-equiv=\"Content-Type\"/>
65
  <link type=\"text/css\" href=\"/cduce.css\" rel=\"stylesheet\"/>
66
67
68
69
70
71
72
73
  <title>CDuce online prototype</title>
</head>
<body>
 <div class=\"title\"> <h1>CDuce online prototype</h1> </div>
 <div id=\"Sidelog\">
   <div class=\"box\">
    <ul>
     <li><a href=\"/\">Main page</a></li>
74
     <li><a href=\"/memento.php\">CDuce Memento</a></li>
75
76
77
    </ul>
   </div>
";
78

79
  if !persistant then 
80
81
82
83
84
    p "
  </div>
<div id=\"Content\">
<div class=\"box\">
 <p>You're running the CDuce prototype in session mode: values and
85
types accepted by CDuce when you click 'Submit' will be available
86
87
88
for subsequent requests.</p>
</div>
"
89
  else
90
91
92
93
94
95
96
    p ||| "
<div class=\"box\">
 <p>
You can start from one of the predefined examples below or try 
with you own program...</p>
" ||| Examples.present ||| "</div></div><div id=\"Content\">" 
  ||= ()
97

98
99

let html_form p content =
100
101
102
  p "
<div class=\"box\">
 <h2>Input</h2>
103
   <form name=\"main\" method=\"post\" action=\"/cgi-bin/cduce\">
104
   <p><input type=\"submit\" name=\"exec\" value=\"Submit to CDuce\"/>" ;
105
106
107
108
109
110
111
112
  if !persistant then
    p ||| "
  <input type=\"submit\" name=\"dump\" value=\"Show current environment\"/>
  <input type=\"submit\" name=\"close\" value=\"Close session\"/>
  <input type=\"hidden\" name=\"session\" value=\"" ||| !session_id
    ||| "\"/>" ||= ()
  else
    p "<input type=\"submit\" name=\"open\" value=\"Initiate session\"/>
113
114
115
116
117
118
119
   <small>
     (The session mode remembers CDuce definitions across requests)
   </small>";
  p
   "<br />
   <input type=\"button\" value=\"Clear\" onClick=\"main.prog.value=''\"/>
   <input type=\"reset\" value=\"Revert changes\"/>
120
121
122
123
124
125
";

  p ||| "</p><p><textarea name=\"prog\" cols=\"80\" rows=\"25\">"
    ||| content
    ||| "</textarea></p></form></div>"
    ||= ()
126
127
128


let html_footer p =
129
130
131
132
133
  p "
 </div>
</body>
</html>
"
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173


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;
174
	     "example", `Example;
175
176
	     "new", `New;
	   ]
177

178
179
180
181
182
183
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
184
185
186
187
188
	| '<' ->
	    let rec tag i =
	      p s.[i];
	      if (s.[i] = '>') then aux (i + 1) x else tag (i + 1) in
	    tag i
189
190
191
192
193
194
195
196
197
198
199
200
201
	| 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

202
let main (cgi : Netcgi.std_activation) =
203
204
  let p = cgi # output # output_string in
  let clicked s = cgi # argument_value s <> "" in
205
  try
206
    let nb_sessions = flush_sessions () in
207
    cgi # set_header
208
(*      ~content_type:"text/html; charset=\"iso-8859-1\"" *)
209
      ();
210

211
212
213
214
215
216
217
218
    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 ...";
219
220
221
222
223
	  let sid = gen_session_id () in
	  (* touch the session file ... *)
	  let chan = open_out_bin (session_file sid) in
	  close_out chan;
	  sid
224
225
226
227
228
229
230
231
232
      | `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
233
234
235

    let load_state () =
      if !persistant then
236
237
	try
	  let chan = open_in_bin (session_file !session_id) in
238
239
240
	  if in_channel_length chan > 0 then
	    (let s = Marshal.from_channel chan in
	     State.set s);
241
	  close_in chan;
242
243
	with Sys_error _ ->
	  failwith "This session has expired ..."
244
    in
245

246
247
248
249
250
251
252
253
254
255
256
    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
257
      Location.push_source (`String src);
258
      Location.set_protected true;
259
      
260
      Location.warning_ppf := ppf;
261
      let ok = Cduce.run ppf ppf input in
262
263
      if ok then Format.fprintf ppf "@\nOk.@\n";
      let res = Format.flush_str_formatter () in
264
265
      p "<div class=\"box\"><h2>Results</h2><pre>"; 
      cut (cgi # output # output_char) 80 res;  p "</pre></div>";
266
      dialog (if !persistant then "" else src);
267
      if ok then store_state ()
268
    in
269

270
271
272
273
274
275
    let dump src =
      let ppf = Format.str_formatter in

      Cduce.dump_env ppf;

      let res = Format.flush_str_formatter () in
276
277
      p "<div class=\"box\"><h2>Current session environment</h2>";
      p ("<pre>" ^ res ^ "</pre></div>");
278
      dialog src
279
280
    in

281
    Location.set_viewport `Html;
282
283
    load_state ();
    store_state ();  (* Just touch the file ... *)
284
285
286
287
288
289
    html_header p;
    let prog = cgi # argument_value "prog" in
    (match cmd with
       | `Exec -> exec prog
       | `Open -> dialog prog
       | `New -> dialog ""
290
       | `Dump -> dump prog
291
       | `Close -> dialog ""
292
       | `Example -> dialog (example (cgi # argument_value "example"))
293
    );
294
295
296
297
298
299
300
301
302
303
304
305
    p "
<div class=\"box\"><h2>About the prototype</h2>
<p>
CDuce is under active development; some features may not work properly.
The prototype is written in 
<a href='http://www.caml.inria.fr'>Objective Caml</a>, 
and uses several OCaml packages: 
<a href='http://caml.inria.fr/camlp4'>Camlp4</a>, 
<a href='http://ocamlnet.sourceforge.net/'>OCamlnet</a>, 
<a href='http://www.ocaml-programming.de/programming/pxp.html'>PXP</a>, 
<a href='http://www.eleves.ens.fr/home/frisch/soft#wlex'>wlex</a>.</p>
<p><a href='mailto:Alain.Frisch@ens.fr'>Webmaster</a></p></div>";
306
    html_footer p;
307
308
309
    cgi # output # commit_work()
  with
      exn ->
310
311
312
	let msg = 
	  match exn with
	    | Unix.Unix_error (e,f,arg) ->
313
314
315
		"System error: " ^ (Unix.error_message e) ^ 
		"; function " ^ f ^ 
		"; argument " ^ arg
316
317
	    | Timeout ->
		"Timeout reached ! This prototype limits computation time ..."
318
319
320
321
	    | exn ->
	      Printexc.to_string exn
	in
	fatal_error "Internal software error!" msg
322
323

let () =
324
  ignore (Unix.alarm 20);
325
  Sys.set_signal Sys.sigalrm (Sys.Signal_handle (fun _ -> raise Timeout));
326
327
328
  main cgi;
  cgi # finalize ()