let non_blocking_queues = ref 3
let maybe_blocking_queues = ref 1
let files_path = ref ""
let port = ref 8080
let usage = "usage: http [options] /path/to/files"
let () =
let pnum = ref 0 in
let arg s =
incr pnum;
if !pnum > 1 then (
Printf.eprintf "Error: too many arguments\n";
exit 1 )
else files_path := s
in
Arg.parse
[
( "--non_blocking_queues",
Arg.Int (fun i -> non_blocking_queues := i),
Printf.sprintf "Number of non-blocking queues. (default: %d)"
!non_blocking_queues );
( "--maybe_blocking_queues",
Arg.Int (fun i -> maybe_blocking_queues := i),
Printf.sprintf "Number of maybe-blocking queues. (default: %d)"
!maybe_blocking_queues );
( "--port",
Arg.Int (fun i -> port := i),
Printf.sprintf "Port used to bind the server. (default: %d)" !port );
]
arg usage;
if !files_path = "" then (
Printf.printf "%s\n" usage;
exit 1 )
else ()
type priority = Maybe_blocking | Non_blocking
let scheduler = Duppy.create ()
type http_method = Post | Get
type http_protocol = Http_11 | Http_10
let string_of_protocol = function
| Http_11 -> "HTTP/1.1"
| Http_10 -> "HTTP/1.0"
let protocol_of_string = function
| "HTTP/1.1" -> Http_11
| "HTTP/1.0" -> Http_10
| _ -> assert false
let string_of_method = function Post -> "POST" | Get -> "GET"
let method_of_string = function
| "POST" -> Post
| "GET" -> Get
| _ -> assert false
type data = None | String of string | File of Unix.file_descr
type request = {
request_protocol : http_protocol;
request_method : http_method;
request_uri : string;
request_headers : (string * string) list;
request_data : data;
}
type reply = {
reply_protocol : http_protocol;
reply_status : int * string;
reply_headers : (string * string) list;
reply_data : data;
}
exception Assoc of string
let assoc_uppercase x y =
try
List.iter
(fun (l, v) ->
if String.uppercase_ascii l = x then raise (Assoc v) else ())
y;
raise Not_found
with Assoc s -> s
let server = "dhttpd"
let html_template =
Printf.sprintf
"\r\n\
\r\n\
%s"
let server_error status protocol =
let _, explanation = status in
let data =
String
(html_template
(Printf.sprintf "
%s\r\n%s !"
explanation explanation))
in
{
reply_protocol = protocol;
reply_status = status;
reply_headers =
[("Content-Type", "text/html; charset=UTF-8"); ("Server", server)];
reply_data = data;
}
let error_404 = server_error (404, "File Not Found")
let error_500 = server_error (500, "Bad Request") Http_10
let error_403 = server_error (403, "Forbidden")
let http_302 protocol uri =
{
reply_protocol = protocol;
reply_status = (302, "Found");
reply_headers = [("Location", uri)];
reply_data = String "";
}
type socket_status = Keep | Close
let send_reply h reply =
let write s =
Duppy.Monad.Io.write ?timeout:None ~priority:Non_blocking h
(Bytes.unsafe_of_string s)
in
let code, status = reply.reply_status in
let http_header =
Printf.sprintf "%s %d %s\r\n%s\r\n\r\n"
(string_of_protocol reply.reply_protocol)
code status
(String.concat "\r\n"
(List.map
(fun (x, y) -> Printf.sprintf "%s: %s" x y)
reply.reply_headers))
in
Duppy.Monad.bind (write http_header) (fun () ->
match reply.reply_data with
| String s -> write s
| File fd ->
let stats = Unix.fstat fd in
let ba =
Unix.map_file fd Bigarray.char Bigarray.c_layout false
[| stats.Unix.st_size |]
in
let ba = Bigarray.array1_of_genarray ba in
let close () = try Unix.close fd with _ -> () in
let on_error e =
close ();
h.Duppy.Monad.Io.on_error e
in
let h = { h with Duppy.Monad.Io.on_error } in
Duppy.Monad.bind
(Duppy.Monad.Io.write_bigarray ?timeout:None
~priority:Non_blocking h ba) (fun () ->
Duppy.Monad.return (close ()))
| None -> Duppy.Monad.return ())
let parse_headers headers =
let split_header l h =
try
let rex = Pcre.regexp "([^:\\r\\n]+):\\s*([^\\r\\n]+)" in
let sub = Pcre.exec ~rex h in
Duppy.Monad.return
((Pcre.get_substring sub 1, Pcre.get_substring sub 2) :: l)
with Not_found -> Duppy.Monad.raise error_500
in
Duppy.Monad.fold_left split_header [] headers
let index_uri path index protocol uri =
let uri =
try
let ret = Pcre.extract ~rex:(Pcre.regexp "([^\\?]*)\\?") uri in
ret.(1)
with Not_found -> uri
in
try
if Sys.is_directory (Printf.sprintf "%s%s" path uri) then
if uri.[String.length uri - 1] <> '/' then
Duppy.Monad.raise (http_302 protocol (Printf.sprintf "%s/" uri))
else (
let index = Printf.sprintf "%s/%s" uri index in
if Sys.file_exists (Printf.sprintf "%s/%s" path index) then
Duppy.Monad.return index
else Duppy.Monad.return uri )
else Duppy.Monad.return uri
with _ -> Duppy.Monad.return uri
let file_request path _ request =
let uri =
try
let ret =
Pcre.extract ~rex:(Pcre.regexp "([^\\?]*)\\?.*") request.request_uri
in
ret.(1)
with Not_found -> request.request_uri
in
let __pa_duppy_0 = index_uri path "index.html" request.request_protocol uri in
Duppy.Monad.bind __pa_duppy_0 (fun uri ->
let fname = Printf.sprintf "%s%s" path uri in
if Sys.file_exists fname then (
try
let fd = Unix.openfile fname [Unix.O_RDONLY] 0o640 in
let stats = Unix.fstat fd in
let headers =
[
("Server", server);
("Content-Length", string_of_int stats.Unix.st_size);
]
in
let headers =
if Pcre.pmatch ~rex:(Pcre.regexp "\\.html$") fname then
("Content-Type", "text/html") :: headers
else if Pcre.pmatch ~rex:(Pcre.regexp "\\.css$") fname then
("Content-Type", "text/css") :: headers
else headers
in
Duppy.Monad.raise
{
reply_protocol = request.request_protocol;
reply_status = (200, "OK");
reply_headers = headers;
reply_data = File fd;
}
with _ -> Duppy.Monad.raise (error_403 request.request_protocol) )
else Duppy.Monad.raise (error_404 request.request_protocol))
let file_handler = ((fun _ -> Duppy.Monad.return true), file_request !files_path)
let cgi_handler process path h request =
let uri, args, suffix =
try
let ret =
Pcre.extract ~rex:(Pcre.regexp "([^\\?]*)\\?(.*)") request.request_uri
in
try
let ans =
Pcre.extract ~rex:(Pcre.regexp "^([^/]*)/([^&=]*)$") ret.(2)
in
(ret.(1), ans.(1), ans.(2))
with Not_found -> (ret.(1), ret.(2), "")
with Not_found -> (request.request_uri, "", "")
in
let __pa_duppy_0 = index_uri path "index.php" request.request_protocol uri in
Duppy.Monad.bind __pa_duppy_0 (fun script ->
let script = Printf.sprintf "%s%s" path script in
let env =
Printf.sprintf
"export SERVER_SOFTWARE=Duppy-httpd/1.0; export \
SERVER_NAME=localhost; export GATEWAY_INTERFACE=CGI/1.1; export \
SERVER_PROTOCOL=%s; export SERVER_PORT=%d; export \
REQUEST_METHOD=%s; export REQUEST_URI=%s; export \
REDIRECT_STATUS=200; export SCRIPT_FILENAME=%s"
(string_of_protocol request.request_protocol)
!port
(string_of_method request.request_method)
(Filename.quote uri) (Filename.quote script)
in
let env =
Printf.sprintf "%s; export QUERY_STRING=%s" env (Filename.quote args)
in
let env =
let tr_suffix = Printf.sprintf "%s%s" path suffix in
(* Trick ! *)
let tr_suffix =
Printf.sprintf "%s/%s"
(Filename.dirname tr_suffix)
(Filename.basename tr_suffix)
in
Printf.sprintf "%s; export PATH_TRANSLATED=%s; export PATH_INFO=%s" env
(Filename.quote tr_suffix) (Filename.quote suffix)
in
let sanitize s =
Pcre.replace ~pat:"-" ~templ:"_" (String.uppercase_ascii s)
in
let headers =
List.map (fun (x, y) -> (sanitize x, y)) request.request_headers
in
let append env key =
if List.mem_assoc key headers then
Printf.sprintf "%s; export %s=%s" env key
(Filename.quote (List.assoc key headers))
else env
in
let env = append env "CONTENT_TYPE" in
let env = append env "CONTENT_LENGTH" in
let __pa_duppy_0 =
if List.mem_assoc "AUTHORIZATION" headers then (
let ret =
Pcre.extract
~rex:(Pcre.regexp "(^[^\\s]*\\s.*)$")
(List.assoc "AUTHORIZATION" headers)
in
if Array.length ret > 0 then
Duppy.Monad.return
(Printf.sprintf "%s; extract AUTH_TYPE=%s" env ret.(1))
else Duppy.Monad.raise error_500 )
else Duppy.Monad.return env
in
Duppy.Monad.bind __pa_duppy_0 (fun env ->
let f env (x, y) =
Printf.sprintf "%s; export HTTP_%s=%s" env x (Filename.quote y)
in
let env = List.fold_left f env headers in
let data =
match request.request_data with
| None -> ""
| String s -> s
| _ -> assert false
in
(* not implemented *)
let process = Printf.sprintf "%s; %s 2>/dev/null" env process in
let in_c, out_c = Unix.open_process process in
let out_s = Unix.descr_of_out_channel out_c in
let h = { h with Duppy.Monad.Io.socket = out_s; data = "" } in
let __pa_duppy_0 =
Duppy.Monad.Io.write ?timeout:None ~priority:Non_blocking h
(Bytes.unsafe_of_string data)
in
Duppy.Monad.bind __pa_duppy_0 (fun () ->
let in_s = Unix.descr_of_in_channel in_c in
let h = { h with Duppy.Monad.Io.socket = in_s; data = "" } in
let __pa_duppy_0 =
Duppy.Monad.Io.read ?timeout:None ~priority:Non_blocking
~marker:(Duppy.Io.Split "[\r]?\n[\r]?\n") h
in
Duppy.Monad.bind __pa_duppy_0 (fun headers ->
let __pa_duppy_0 =
Duppy.Monad.catch
(Duppy.Monad.Io.read_all ?timeout:None
~priority:Non_blocking h.Duppy.Monad.Io.scheduler in_s)
(fun (s, _) -> Duppy.Monad.return s)
in
Duppy.Monad.bind __pa_duppy_0 (fun data ->
let data =
Printf.sprintf "%s%s" h.Duppy.Monad.Io.data data
in
ignore (Unix.close_process (in_c, out_c));
let __pa_duppy_0 =
let headers = Pcre.split ~pat:"\r\n" headers in
parse_headers headers
in
Duppy.Monad.bind __pa_duppy_0 (fun headers ->
let __pa_duppy_0 =
if List.mem_assoc "Status" headers then (
try
let ans =
Pcre.extract
~rex:(Pcre.regexp "([\\d]+)\\s(.*)")
(List.assoc "Status" headers)
in
Duppy.Monad.return
( (int_of_string ans.(1), ans.(2)),
List.filter
(fun (x, _) -> x <> "Status")
headers )
with _ -> Duppy.Monad.raise error_500 )
else Duppy.Monad.return ((200, "OK"), headers)
in
Duppy.Monad.bind __pa_duppy_0
(fun (status, headers) ->
let headers =
( "Content-length",
string_of_int (String.length data) )
:: headers
in
Duppy.Monad.raise
{
reply_protocol = request.request_protocol;
reply_status = status;
reply_headers = headers;
reply_data = String data;
})))))))
let php_handler =
( (fun request ->
let __pa_duppy_0 =
index_uri !files_path "index.php" request.request_protocol
request.request_uri
in
Duppy.Monad.bind __pa_duppy_0 (fun uri ->
Duppy.Monad.return (Pcre.pmatch ~rex:(Pcre.regexp "\\.php$") uri))),
cgi_handler "php-cgi" !files_path )
let handlers = [php_handler; file_handler]
let handle_request h request =
let f (check, handler) =
let __pa_duppy_0 = check request in
Duppy.Monad.bind __pa_duppy_0 (fun check ->
if check then handler h request else Duppy.Monad.return ())
in
Duppy.Monad.catch
(Duppy.Monad.bind (Duppy.Monad.iter f handlers) (fun () ->
Duppy.Monad.return (error_404 request.request_protocol)))
(fun reply -> Duppy.Monad.return reply)
let parse_request h r =
try
let headers = Pcre.split ~pat:"\r\n" r in
let __pa_duppy_0 =
match headers with
| e :: l ->
let __pa_duppy_0 = parse_headers l in
Duppy.Monad.bind __pa_duppy_0 (fun headers ->
Duppy.Monad.return (e, headers))
| _ -> Duppy.Monad.raise error_500
in
Duppy.Monad.bind __pa_duppy_0 (fun (request, headers) ->
let rex = Pcre.regexp "([\\w]+)\\s([^\\s]+)\\s(HTTP/1.[01])" in
let __pa_duppy_0 =
try
let sub = Pcre.exec ~rex request in
let http_method, uri, protocol =
( Pcre.get_substring sub 1,
Pcre.get_substring sub 2,
Pcre.get_substring sub 3 )
in
Duppy.Monad.return
(method_of_string http_method, uri, protocol_of_string protocol)
with _ -> Duppy.Monad.raise error_500
in
Duppy.Monad.bind __pa_duppy_0 (fun (http_method, uri, protocol) ->
let __pa_duppy_0 =
match http_method with
| Get -> Duppy.Monad.return None
| Post ->
let __pa_duppy_0 =
try
let length = assoc_uppercase "CONTENT-LENGTH" headers in
Duppy.Monad.return (int_of_string length)
with
| Not_found -> Duppy.Monad.return 0
| _ -> Duppy.Monad.raise error_500
in
Duppy.Monad.bind __pa_duppy_0 (fun len ->
match len with
| 0 -> Duppy.Monad.return None
| d ->
let __pa_duppy_0 =
Duppy.Monad.Io.read ?timeout:None
~priority:Non_blocking
~marker:(Duppy.Io.Length d) h
in
Duppy.Monad.bind __pa_duppy_0 (fun data ->
Duppy.Monad.return (String data)))
in
Duppy.Monad.bind __pa_duppy_0 (fun data ->
Duppy.Monad.return
{
request_method = http_method;
request_protocol = protocol;
request_uri = uri;
request_headers = headers;
request_data = data;
})))
with _ -> Duppy.Monad.raise error_500
let handle_client socket =
(* Read and process lines *)
let on_error _ = error_500 in
let h = { Duppy.Monad.Io.scheduler; socket; data = ""; on_error } in
let rec exec () =
let __pa_duppy_0 =
Duppy.Monad.catch
(let __pa_duppy_0 =
Duppy.Monad.Io.read ?timeout:None ~priority:Non_blocking
~marker:(Duppy.Io.Split "\r\n\r\n") h
in
Duppy.Monad.bind __pa_duppy_0 (fun data ->
let __pa_duppy_0 = parse_request h data in
Duppy.Monad.bind __pa_duppy_0 (fun request ->
let __pa_duppy_0 = handle_request h request in
Duppy.Monad.bind __pa_duppy_0 (fun reply ->
let close_header headers =
try assoc_uppercase "CONNECTION" headers = "close"
with Not_found -> false
in
let keep =
if
request.request_protocol = Http_10
|| close_header request.request_headers
|| close_header reply.reply_headers
then Close
else Keep
in
Duppy.Monad.return (keep, reply)))))
(fun reply -> Duppy.Monad.return (Close, reply))
in
Duppy.Monad.bind __pa_duppy_0 (fun (keep, reply) ->
Duppy.Monad.bind (send_reply h reply) (fun () ->
if keep = Keep then exec () else Duppy.Monad.return ()))
in
let finish _ = try Unix.close socket with _ -> () in
Duppy.Monad.run ~return:finish ~raise:finish (exec ())
let new_queue ~priority ~name () =
let priorities p = p = priority in
let queue () = Duppy.queue scheduler ~log:(fun _ -> ()) ~priorities name in
Thread.create queue ()
let bind_addr_inet = Unix.inet_addr_of_string "0.0.0.0"
let bind_addr = Unix.ADDR_INET (bind_addr_inet, !port)
let max_conn = 100
let sock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0
let () =
(* See http://caml.inria.fr/mantis/print_bug_page.php?bug_id=4640
* for this: we want Unix EPIPE error and not SIGPIPE, which
* crashes the program.. *)
Sys.set_signal Sys.sigpipe Sys.Signal_ignore;
ignore (Unix.sigprocmask Unix.SIG_BLOCK [Sys.sigpipe]);
Unix.setsockopt sock Unix.SO_REUSEADDR true;
let rec incoming _ =
( try
let s, _ = Unix.accept sock in
handle_client s
with e ->
Printf.printf "Failed to accept new client: %S\n" (Printexc.to_string e)
);
[
{
Duppy.Task.priority = Non_blocking;
events = [`Read sock];
handler = incoming;
};
]
in
( try Unix.bind sock bind_addr
with Unix.Unix_error (Unix.EADDRINUSE, "bind", "") ->
failwith (Printf.sprintf "port %d already taken" !port) );
Unix.listen sock max_conn;
Duppy.Task.add scheduler
{
Duppy.Task.priority = Non_blocking;
events = [`Read sock];
handler = incoming;
};
for i = 1 to !non_blocking_queues do
ignore
(new_queue ~priority:Non_blocking
~name:(Printf.sprintf "Non blocking queue #%d" i)
())
done;
for i = 1 to !maybe_blocking_queues do
ignore
(new_queue ~priority:Maybe_blocking
~name:(Printf.sprintf "Maybe blocking queue #%d" i)
())
done;
Duppy.queue scheduler ~log:(fun _ -> ()) "root"