webiface.ml 9 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
  <title>CDuce online prototype</title>
</head>
<body>
 <div class=\"title\"> <h1>CDuce online prototype</h1> </div>
 <div id=\"Sidelog\">
   <div class=\"box\">
    <ul>
81
82
83
     <li><a href=\"http://www.cduce.org/\">Main page</a></li>
     <li><a href=\"http://www.cduce.org/manual.html\">User's manual</a></li>
     <li><a href=\"http://www.cduce.org/memento.html\">Quick Reference</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
    p ||| "
<div class=\"box\">
101
102
<br/><center><b style=\"font-size:120&#37;; color: #008000\">Sample programs</b></center>
<p>
103
104
105
106
You can start from one of the predefined examples below or try 
with you own program...</p>
" ||| Examples.present ||| "</div></div><div id=\"Content\">" 
  ||= ()
107

108
109

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

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


let html_footer p =
139
140
141
142
143
  p "
 </div>
</body>
</html>
"
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
183


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

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

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

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

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

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

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

      Cduce.dump_env ppf;

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

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

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