(* $Id: wserver.ml,v 4.21 2004/12/14 13:47:46 ddr Exp $ *)
(* Copyright (c) 1998-2005 INRIA *)
value sock_in = ref "wserver.sin";
value sock_out = ref "wserver.sou";
value noproc = ref False;
value wserver_oc =
do { set_binary_mode_out stdout True; ref stdout }
;
value wprint fmt = Printf.fprintf wserver_oc.val fmt;
value wflush () = flush wserver_oc.val;
value hexa_digit x =
if x >= 10 then Char.chr (Char.code 'A' + x - 10)
else Char.chr (Char.code '0' + x)
;
value hexa_val conf =
match conf with
[ '0'..'9' -> Char.code conf - Char.code '0'
| 'a'..'f' -> Char.code conf - Char.code 'a' + 10
| 'A'..'F' -> Char.code conf - Char.code 'A' + 10
| _ -> 0 ]
;
value decode s =
let rec need_decode i =
if i < String.length s then
match s.[i] with
[ '%' | '+' -> True
| _ -> need_decode (succ i) ]
else False
in
let rec compute_len i i1 =
if i < String.length s then
let i =
match s.[i] with
[ '%' when i + 2 < String.length s -> i + 3
| _ -> succ i ]
in
compute_len i (succ i1)
else i1
in
let rec copy_decode_in s1 i i1 =
if i < String.length s then
let i =
match s.[i] with
[ '%' when i + 2 < String.length s ->
let v = hexa_val s.[i + 1] * 16 + hexa_val s.[i + 2] in
do { s1.[i1] := Char.chr v; i + 3 }
| '+' -> do { s1.[i1] := ' '; succ i }
| x -> do { s1.[i1] := x; succ i } ]
in
copy_decode_in s1 i (succ i1)
else s1
in
let rec strip_heading_and_trailing_spaces s =
if String.length s > 0 then
if s.[0] == ' ' then
strip_heading_and_trailing_spaces
(String.sub s 1 (String.length s - 1))
else if s.[String.length s - 1] == ' ' then
strip_heading_and_trailing_spaces
(String.sub s 0 (String.length s - 1))
else s
else s
in
if need_decode 0 then
let len = compute_len 0 0 in
let s1 = String.create len in
strip_heading_and_trailing_spaces (copy_decode_in s1 0 0)
else s
;
value special =
fun
[ '\000'..'\031' | '\127'..'\255' | '<' | '>' | '"' | '#' | '%' |
'{' | '}' | '|' | '\\' | '^' | '~' | '[' | ']' | '`' | ';' | '/' | '?' |
':' | '@' | '=' | '&' ->
True
| _ -> False ]
;
value encode s =
let rec need_code i =
if i < String.length s then
match s.[i] with
[ ' ' -> True
| x -> if special x then True else need_code (succ i) ]
else False
in
let rec compute_len i i1 =
if i < String.length s then
let i1 = if special s.[i] then i1 + 3 else succ i1 in
compute_len (succ i) i1
else i1
in
let rec copy_code_in s1 i i1 =
if i < String.length s then
let i1 =
match s.[i] with
[ ' ' -> do { s1.[i1] := '+'; succ i1 }
| c ->
if special c then do {
s1.[i1] := '%';
s1.[i1 + 1] := hexa_digit (Char.code c / 16);
s1.[i1 + 2] := hexa_digit (Char.code c mod 16);
i1 + 3
}
else do { s1.[i1] := c; succ i1 } ]
in
copy_code_in s1 (succ i) i1
else s1
in
if need_code 0 then
let len = compute_len 0 0 in copy_code_in (String.create len) 0 0
else s
;
value nl () = wprint "\013\010";
value http answer =
let answer = if answer = "" then "200 OK" else answer in
do {
wprint "HTTP/1.0 %s" answer; nl ();
}
;
value print_exc exc =
match exc with
[ Unix.Unix_error err fun_name arg ->
do {
prerr_string "\"";
prerr_string fun_name;
prerr_string "\" failed";
if String.length arg > 0 then do {
prerr_string " on \""; prerr_string arg; prerr_string "\"";
}
else ();
prerr_string ": ";
prerr_endline (Unix.error_message err);
}
| Out_of_memory -> prerr_string "Out of memory\n"
| Match_failure (file, first_char, last_char) ->
do {
prerr_string "Pattern matching failed, file ";
prerr_string file;
prerr_string ", chars ";
prerr_int first_char;
prerr_char '-';
prerr_int last_char;
prerr_char '\n';
}
| Assert_failure (file, first_char, last_char) ->
do {
prerr_string "Assertion failed, file ";
prerr_string file;
prerr_string ", chars ";
prerr_int first_char;
prerr_char '-';
prerr_int last_char;
prerr_char '\n';
}
| x ->
do {
prerr_string "Uncaught exception: ";
prerr_string (Obj.magic (Obj.field (Obj.field (Obj.repr x) 0) 0));
if Obj.size (Obj.repr x) > 1 then do {
prerr_char '(';
for i = 1 to Obj.size (Obj.repr x) - 1 do {
if i > 1 then prerr_string ", " else ();
let arg = Obj.field (Obj.repr x) i in
if not (Obj.is_block arg) then
prerr_int (Obj.magic arg : int)
else if Obj.tag arg = 252 then do {
prerr_char '"'; prerr_string (Obj.magic arg : string);
prerr_char '"'
}
else prerr_char '_';
};
prerr_char ')';
}
else ();
prerr_char '\n';
} ]
;
value print_err_exc exc =
do { print_exc exc; flush stderr; }
;
value case_unsensitive_eq s1 s2 =
String.lowercase s1 = String.lowercase s2
;
value rec extract_param name stop_char =
fun
[ [x :: l] ->
if String.length x >= String.length name
&& case_unsensitive_eq (String.sub x 0 (String.length name)) name then
let i =
loop (String.length name) where rec loop i =
if i = String.length x then i
else if x.[i] = stop_char then i
else loop (i + 1)
in
String.sub x (String.length name) (i - String.length name)
else extract_param name stop_char l
| [] -> "" ]
;
value buff = ref (String.create 80);
value store len x =
do {
if len >= String.length buff.val then
buff.val := buff.val ^ String.create (String.length buff.val)
else ();
buff.val.[len] := x;
succ len
}
;
value get_buff len = String.sub buff.val 0 len;
value get_request strm =
let rec loop len =
parser
[ [: `'\010'; s :] ->
if len == 0 then []
else let str = get_buff len in [str :: loop 0 s]
| [: `'\013'; s :] -> loop len s
| [: `c; s :] -> loop (store len c) s
| [: :] -> if len == 0 then [] else [get_buff len] ]
in
loop 0 strm
;
ifdef UNIX then
value timeout tmout spid _ =
do {
Unix.kill spid Sys.sigkill;
http "";
wprint "Content-type: text/html; charset=iso-8859-1"; nl (); nl ();
wprint "
Time out\n";
wprint "Time out
\n";
wprint "Computation time > %d second(s)\n" tmout;
wprint "\n";
wflush ();
exit 2
}
;
value get_request_and_content strm =
let request = get_request strm in
let content =
match extract_param "content-length: " ' ' request with
[ "" -> ""
| x ->
let str = String.create (int_of_string x) in
do {
for i = 0 to String.length str - 1 do {
str.[i] :=
match strm with parser
[ [: `x :] -> x
| [: :] -> ' ' ];
};
str
} ]
in
(request, content)
;
value string_of_sockaddr =
fun
[ Unix.ADDR_UNIX s -> s
| Unix.ADDR_INET a _ -> Unix.string_of_inet_addr a ]
;
value sockaddr_of_string s = Unix.ADDR_UNIX s;
value treat_connection tmout callback addr fd =
do {
ifdef NOFORK then ()
else ifdef UNIX then
if tmout > 0 then
let spid = Unix.fork () in
if spid > 0 then do {
let _ (* : Sys.signal_behavior *) =
Sys.signal Sys.sigalrm
(Sys.Signal_handle (timeout tmout spid))
in ();
let _ = Unix.alarm tmout in ();
let _ = Unix.waitpid [] spid in ();
let _ (* : Sys.signal_behavior *) =
Sys.signal Sys.sigalrm Sys.Signal_default
in ();
exit 0;
}
else ()
else ()
else ();
let (request, script_name, contents) =
let (request, contents) =
let strm =
let c = " " in
Stream.from
(fun _ -> if Unix.read fd c 0 1 = 1 then Some c.[0] else None)
in
get_request_and_content strm
in
let (script_name, contents) =
match extract_param "GET /" ' ' request with
[ "" -> (extract_param "POST /" ' ' request, contents)
| str ->
try
let i = String.index str '?' in
(String.sub str 0 i,
String.sub str (i + 1) (String.length str - i - 1))
with
[ Not_found -> (str, "") ] ]
in
(request, script_name, contents)
in
if script_name = "robots.txt" then do {
http "";
wprint "Content-type: text/plain"; nl (); nl ();
wprint "User-Agent: *"; nl ();
wprint "Disallow: /"; nl ();
wflush ();
Printf.eprintf "Robot request\n";
flush stderr;
}
else do {
try callback (addr, request) script_name contents with
[ Unix.Unix_error Unix.EPIPE "write" _ -> ()
| exc -> print_err_exc exc ];
try wflush () with _ -> ();
try flush stderr with _ -> ();
};
}
;
value buff = String.create 1024;
ifdef WIN95 then
value copy_what_necessary t oc =
let strm =
let len = ref 0 in
let i = ref 0 in
Stream.from
(fun _ ->
do {
if i.val >= len.val then do {
len.val := Unix.read t buff 0 (String.length buff);
i.val := 0;
if len.val > 0 then output oc buff 0 len.val else ();
}
else ();
if len.val == 0 then None
else do { incr i; Some buff.[i.val - 1] }
})
in
let _ = get_request_and_content strm in
()
;
value rec list_remove x =
fun
[ [] -> failwith "list_remove"
| [y :: l] -> if x = y then l else [y :: list_remove x l] ]
;
ifdef NOFORK then declare end else
value pids = ref [];
ifdef NOFORK then declare end else
value cleanup_verbose = ref True;
ifdef NOFORK then declare end else
value cleanup_sons () =
List.iter
(fun p ->
let pid =
try fst (Unix.waitpid [Unix.WNOHANG] p) with
[ Unix.Unix_error _ _ _ as exc ->
do {
if cleanup_verbose.val then do {
Printf.eprintf "*** Why error on waitpid %d?\n" p;
flush stderr;
print_exc exc;
Printf.eprintf "[";
List.iter (fun p -> Printf.eprintf " %d" p) pids.val;
Printf.eprintf "]\n";
flush stderr;
cleanup_verbose.val := False;
}
else ();
p
} ]
in
if pid = 0 then ()
else pids.val := list_remove pid pids.val)
pids.val
;
ifdef NOFORK then declare end else
value wait_available max_clients s =
match max_clients with
[ Some m ->
do {
if List.length pids.val >= m then
(*
let tm = Unix.localtime (Unix.time ()) in
let _ = do { Printf.eprintf "*** %02d/%02d/%4d %02d:%02d:%02d " tm.Unix.tm_mday (succ tm.Unix.tm_mon) (1900 + tm.Unix.tm_year) tm.Unix.tm_hour tm.Unix.tm_min tm.Unix.tm_sec; Printf.eprintf "%d clients running; waiting...\n" m; flush stderr; } in
*)
let (pid, _) = Unix.wait () in
(*
let tm = Unix.localtime (Unix.time ()) in
let _ = do { Printf.eprintf "*** %02d/%02d/%4d %02d:%02d:%02d " tm.Unix.tm_mday (succ tm.Unix.tm_mon) (1900 + tm.Unix.tm_year) tm.Unix.tm_hour tm.Unix.tm_min tm.Unix.tm_sec; Printf.eprintf "ok: place for another client\n"; flush stderr; } in
*)
pids.val := list_remove pid pids.val
else ();
if pids.val <> [] then cleanup_sons () else ();
let stop_verbose = ref False in
while pids.val <> [] && Unix.select [s] [] [] 15.0 = ([], [], []) do {
cleanup_sons ();
if pids.val <> [] && not stop_verbose.val then do {
stop_verbose.val := True;
let tm = Unix.localtime (Unix.time ()) in
Printf.eprintf "*** %02d/%02d/%4d %02d:%02d:%02d %d process(es) remaining after cleanup (%d)\n" tm.Unix.tm_mday (succ tm.Unix.tm_mon) (1900 + tm.Unix.tm_year) tm.Unix.tm_hour tm.Unix.tm_min tm.Unix.tm_sec (List.length pids.val) (List.hd pids.val); flush stderr; ()
}
else ();
};
}
| None -> () ]
;
value wait_and_compact s =
if Unix.select [s] [] [] 15.0 = ([], [], []) then do {
Printf.eprintf "Compacting... "; flush stderr;
Gc.compact ();
Printf.eprintf "Ok\n"; flush stderr;
}
else ()
;
value skip_possible_remaining_chars fd =
do {
let b = "..." in
try
loop () where rec loop () =
match Unix.select [fd] [] [] 5.0 with
[ ([_], [], []) ->
let len = Unix.read fd b 0 (String.length b) in
if len = String.length b then loop () else ()
| _ -> () ]
with
[ Unix.Unix_error Unix.ECONNRESET _ _ -> () ]
}
;
value accept_connection tmout max_clients callback s =
do {
ifdef NOFORK then wait_and_compact s
else if noproc.val then wait_and_compact s
else wait_available max_clients s;
let (t, addr) = Unix.accept s in
Unix.setsockopt t Unix.SO_KEEPALIVE True;
ifdef NOFORK then
let cleanup () =
do {
try Unix.shutdown t Unix.SHUTDOWN_SEND with _ -> ();
try Unix.shutdown t Unix.SHUTDOWN_RECEIVE with _ -> ();
try Unix.close t with _ -> ();
}
in
do {
wserver_oc.val := Unix.out_channel_of_descr t;
treat_connection tmout callback addr t;
cleanup ();
}
else ifdef UNIX then
match try Some (Unix.fork ()) with _ -> None with
[ Some 0 ->
do {
try do {
if max_clients = None && Unix.fork () <> 0 then exit 0 else ();
Unix.close s;
Unix.dup2 t Unix.stdout;
Unix.dup2 t Unix.stdin;
(*
j'ai l'impression que cette fermeture fait parfois bloquer le serveur...
try Unix.close t with _ -> ();
*)
treat_connection tmout callback addr t;
}
with
[ Unix.Unix_error Unix.ECONNRESET "read" _ -> ()
| exc ->
try do { print_err_exc exc; flush stderr; }
with _ -> () ];
try Unix.shutdown t Unix.SHUTDOWN_SEND with _ -> ();
try Unix.shutdown Unix.stdout Unix.SHUTDOWN_SEND with _ -> ();
skip_possible_remaining_chars t;
try Unix.shutdown t Unix.SHUTDOWN_RECEIVE with _ -> ();
try Unix.shutdown Unix.stdin Unix.SHUTDOWN_RECEIVE with _ -> ();
exit 0
}
| Some id ->
do {
Unix.close t;
if max_clients = None then let _ = Unix.waitpid [] id in ()
else pids.val := [id :: pids.val];
}
| None ->
do { Unix.close t; Printf.eprintf "Fork failed\n"; flush stderr } ]
else do {
let oc = open_out_bin sock_in.val in
let cleanup () = try close_out oc with _ -> () in
try copy_what_necessary t oc with
[ Unix.Unix_error _ _ _ -> ()
| exc -> do { cleanup (); raise exc } ];
cleanup ();
ifdef SYS_COMMAND then
let comm =
let stringify_if_spaces s =
try let _ = String.index s ' ' in "\"" ^ s ^ "\"" with
[ Not_found -> s ]
in
List.fold_left (fun s a -> s ^ stringify_if_spaces a ^ " ") ""
(Array.to_list Sys.argv) ^
"-wserver " ^ string_of_sockaddr addr
in
let _ = Sys.command comm in ()
else if noproc.val then do {
let fd = Unix.openfile sock_in.val [Unix.O_RDONLY] 0 in
let oc = open_out_bin sock_out.val in
wserver_oc.val := oc;
treat_connection tmout callback addr fd;
flush oc;
close_out oc;
Unix.close fd;
}
else
let pid =
let env =
Array.append (Unix.environment ())
[| "WSERVER=" ^ string_of_sockaddr addr |]
in
(*
let args = Array.map (fun x -> "\"" ^ x ^ "\"") Sys.argv in
*)
let args = Sys.argv in
(**)
Unix.create_process_env Sys.argv.(0) args env Unix.stdin
Unix.stdout Unix.stderr
in
let _ = Unix.waitpid [] pid in
let ic = open_in_bin sock_in.val in
let request = get_request (Stream.of_channel ic) in
close_in ic
;
let cleanup () =
do {
try Unix.shutdown t Unix.SHUTDOWN_SEND with _ -> ();
skip_possible_remaining_chars t;
try Unix.shutdown t Unix.SHUTDOWN_RECEIVE with _ -> ();
try Unix.close t with _ -> ();
}
in
try
let ic = open_in_bin sock_out.val in
let cleanup () = try close_in ic with _ -> () in
do {
try
loop () where rec loop () =
let len = input ic buff 0 (String.length buff) in
if len == 0 then ()
else do {
loop_write 0 where rec loop_write i =
let olen = Unix.write t buff i (len - i) in
if i + olen < len then loop_write (i + olen) else ();
loop ()
}
with
[ Unix.Unix_error _ _ _ -> ()
| exc -> do { cleanup (); raise exc } ];
cleanup ();
}
with
[ Unix.Unix_error _ _ _ -> ()
| exc -> do { cleanup (); raise exc } ];
cleanup ();
}
}
;
value f addr_opt port tmout max_clients g =
match
ifdef NOFORK then None
else ifdef WIN95 then
ifdef SYS_COMMAND then
let len = Array.length Sys.argv in
if len > 2 && Sys.argv.(len - 2) = "-wserver" then
Some Sys.argv.(len - 1)
else None
else
try Some (Sys.getenv "WSERVER") with [ Not_found -> None ]
else None
with
[ Some s ->
ifdef NOFORK then ()
else ifdef WIN95 then do {
let addr = sockaddr_of_string s in
let fd = Unix.openfile sock_in.val [Unix.O_RDONLY] 0 in
let oc = open_out_bin sock_out.val in
wserver_oc.val := oc;
ignore (treat_connection tmout g addr fd);
exit 0
}
else ()
| None ->
let addr =
match addr_opt with
[ Some addr ->
try Unix.inet_addr_of_string addr with
[ Failure _ -> (Unix.gethostbyname addr).Unix.h_addr_list.(0) ]
| None -> Unix.inet_addr_any ]
in
let s = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
do {
Unix.setsockopt s Unix.SO_REUSEADDR True;
Unix.bind s (Unix.ADDR_INET addr port);
Unix.listen s 4;
ifdef NOFORK then Sys.set_signal Sys.sigpipe Sys.Signal_ignore
else ifdef UNIX then let _ = Unix.nice 1 in ()
else ();
let tm = Unix.localtime (Unix.time ()) in
Printf.eprintf "Ready %4d-%02d-%02d %02d:%02d port"
(1900 + tm.Unix.tm_year) (succ tm.Unix.tm_mon) tm.Unix.tm_mday
tm.Unix.tm_hour tm.Unix.tm_min;
Printf.eprintf " %d" port;
Printf.eprintf "...\n";
flush stderr;
while True do {
try accept_connection tmout max_clients g s with
[ Unix.Unix_error Unix.ECONNRESET "accept" _ -> ()
| Unix.Unix_error (Unix.EBADF | Unix.ENOTSOCK) "accept" _ as x ->
(* oops! *) raise x
| exc -> print_err_exc exc ];
try wflush () with [ Sys_error _ -> () ];
try flush stdout with [ Sys_error _ -> () ];
flush stderr;
};
} ]
;