webiface.ml 8.85 KB
Newer Older
1
2
3
4
(* TODO:
   - HTML design, logo
*)

5
open Netcgi
6
exception Timeout
7

8
9
10
11
12
13
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 
14
    ~content_type:"text/html; charset=\"iso-8859-1\""
15
16
17
18
    ~cache:`No_cache 
    ();
  cgi # output # output_string ("<h1>" ^ title ^ "</h1>");
  cgi # output # output_string s;
19
  cgi # output # output_string "\n";
20
  cgi # output # commit_work();
21
22
  cgi # finalize ();
  exit 0
23
24


25
26
(* Configuration *)

27
28
29
30
31
32
33
34
let session_dir = <:symbol<session_dir>>

let () =
  if not (Sys.file_exists session_dir) then
    try Unix.mkdir session_dir 0o755
    with Unix.Unix_error(_,_,_)-> fatal_error "Fatal error" "Cannot create session directory"

(*
35
let session_dirs = ["/usr/local/tmp/"; "/home/beppe/sessions"; "/home/frisch/sessions";
36
"/users/formel/cduce/sessions"; "/home/zack/cduce/sessions"; "/tmp" ]
37
38
let session_dir = 
  try List.find Sys.file_exists session_dirs
39
40
  with Not_found -> fatal_error "Internal error" 
    "Cannot find sessions directory"
41
42
*)

43
44
45
46
47
let timeout = 60 * 5  (* seconds *)
let max_sess = 10

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

48

49
50
51
52
53
54
(* Loading examples *)

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

55
56
let begin_table = "<div class=\"box\">"
let end_table = "</div>"
57

58
59
60
let persistant = ref false
let session_id = ref ""

61
62
63
let (|||) p x = p x; p
let (||=) p () = ()

64
let html_header p =
65
66
67
68
69
70
71
72
  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\"/>
73
  <link type=\"text/css\" href=\"/cduce.css\" rel=\"stylesheet\"/>
74
75
76
77
78
79
80
81
  <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>
82
83
     <li><a href=\"/manual.html\">User's manual</a></li>
     <li><a href=\"/memento.html\">Memento</a></li>
84
85
86
    </ul>
   </div>
";
87

88
  if !persistant then 
89
90
91
92
93
    p "
  </div>
<div id=\"Content\">
<div class=\"box\">
 <p>You're running the CDuce prototype in session mode: values and
94
types accepted by CDuce when you click 'Submit' will be available
95
96
97
for subsequent requests.</p>
</div>
"
98
  else
99
100
101
102
103
104
105
    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\">" 
  ||= ()
106

107
108

let html_form p content =
109
110
111
  p "
<div class=\"box\">
 <h2>Input</h2>
112
   <form name=\"main\" method=\"post\" action=\"/cgi-bin/cduce\">
113
   <p><input type=\"submit\" name=\"exec\" value=\"Submit to CDuce\"/>" ;
114
115
116
117
118
119
120
121
  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\"/>
122
123
124
125
126
127
128
   <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\"/>
129
130
131
132
133
134
";

  p ||| "</p><p><textarea name=\"prog\" cols=\"80\" rows=\"25\">"
    ||| content
    ||| "</textarea></p></form></div>"
    ||= ()
135
136
137


let html_footer p =
138
139
140
141
142
  p "
 </div>
</body>
</html>
"
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
174
175
176
177
178
179
180
181
182


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;
183
	     "example", `Example;
184
185
	     "new", `New;
	   ]
186

187
188
189
190
191
192
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
193
194
195
196
197
	| '<' ->
	    let rec tag i =
	      p s.[i];
	      if (s.[i] = '>') then aux (i + 1) x else tag (i + 1) in
	    tag i
198
199
200
201
202
203
204
205
206
207
208
209
210
	| 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

211
let main (cgi : Netcgi.std_activation) =
212
213
  let p = cgi # output # output_string in
  let clicked s = cgi # argument_value s <> "" in
214
  try
215
    let nb_sessions = flush_sessions () in
216
    cgi # set_header
217
(*      ~content_type:"text/html; charset=\"iso-8859-1\"" *)
218
      ();
219

220
221
222
223
224
225
226
227
    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 ...";
228
229
230
231
232
	  let sid = gen_session_id () in
	  (* touch the session file ... *)
	  let chan = open_out_bin (session_file sid) in
	  close_out chan;
	  sid
233
234
235
236
237
238
239
240
241
      | `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
242
243
244

    let load_state () =
      if !persistant then
245
246
	try
	  let chan = open_in_bin (session_file !session_id) in
247
248
249
	  if in_channel_length chan > 0 then
	    (let s = Marshal.from_channel chan in
	     State.set s);
250
	  close_in chan;
251
	with Sys_error _ ->
252
	  fatal_error "Fatal error" "This session has expired ..."
253
    in
254

255
256
257
258
259
260
261
262
263
264
265
    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
266
      Location.push_source (`String src);
267
      Location.set_protected true;
268
      
269
      Location.warning_ppf := ppf;
270
      let ok = Cduce.script ppf ppf input in
271
272
      if ok then Format.fprintf ppf "@\nOk.@\n";
      let res = Format.flush_str_formatter () in
273
274
      p "<div class=\"box\"><h2>Results</h2><pre>"; 
      cut (cgi # output # output_char) 80 res;  p "</pre></div>";
275
      dialog (if !persistant then "" else src);
276
      if ok then store_state ()
277
    in
278

279
280
281
282
283
284
    let dump src =
      let ppf = Format.str_formatter in

      Cduce.dump_env ppf;

      let res = Format.flush_str_formatter () in
285
286
      p "<div class=\"box\"><h2>Current session environment</h2>";
      p ("<pre>" ^ res ^ "</pre></div>");
287
      dialog src
288
289
    in

290
    Location.set_viewport `Html;
291
292
    load_state ();
    store_state ();  (* Just touch the file ... *)
293
294
295
296
297
298
    html_header p;
    let prog = cgi # argument_value "prog" in
    (match cmd with
       | `Exec -> exec prog
       | `Open -> dialog prog
       | `New -> dialog ""
299
       | `Dump -> dump prog
300
       | `Close -> dialog ""
301
       | `Example -> dialog (example (cgi # argument_value "example"))
302
    );
303
    p ("
304
305
306
307
308
309
310
311
312
313
<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>
314
<p><a href='mailto:Alain.Frisch@ens.fr'>Webmaster</a></p>
315
316
<p>Prototype version "^ <:symbol<cduce_version>> ^",
 built on "^ <:symbol<build_date>> ^".</p></div>");
317
    html_footer p;
318
319
320
    cgi # output # commit_work()
  with
      exn ->
321
322
323
	let msg = 
	  match exn with
	    | Unix.Unix_error (e,f,arg) ->
324
325
326
		"System error: " ^ (Unix.error_message e) ^ 
		"; function " ^ f ^ 
		"; argument " ^ arg
327
328
	    | Timeout ->
		"Timeout reached ! This prototype limits computation time ..."
329
330
331
332
	    | exn ->
	      Printexc.to_string exn
	in
	fatal_error "Internal software error!" msg
333
334

let () =
335
  ignore (Unix.alarm 20);
336
  Sys.set_signal Sys.sigalrm (Sys.Signal_handle (fun _ -> raise Timeout));
337
338
339
  main cgi;
  cgi # finalize ()