An OCaml webserver, but the allocating version (vs httpz which doesnt)
at main 122 lines 4.1 kB view raw
1(* httpz_server.ml - Minimal CLI for httpzo static file server *) 2 3open Cmdliner 4 5(* Configuration file structure *) 6type config_file = { 7 port : int option; 8 root : string option; 9 max_content_length : int64 option; 10 max_header_size : int option; 11 max_header_count : int option; 12 max_chunk_size : int option; 13} 14 15let config_codec = 16 Tomlt.(Table.( 17 obj (fun port root max_content_length max_header_size max_header_count max_chunk_size -> 18 { port; root; max_content_length; max_header_size; max_header_count; max_chunk_size }) 19 |> opt_mem "port" int ~enc:(fun c -> c.port) 20 |> opt_mem "root" string ~enc:(fun c -> c.root) 21 |> opt_mem "max_content_length" int64 ~enc:(fun c -> c.max_content_length) 22 |> opt_mem "max_header_size" int ~enc:(fun c -> c.max_header_size) 23 |> opt_mem "max_header_count" int ~enc:(fun c -> c.max_header_count) 24 |> opt_mem "max_chunk_size" int ~enc:(fun c -> c.max_chunk_size) 25 |> finish 26 )) 27 28(* Load config from XDG config directory *) 29let load_config xdg = 30 match Xdge.find_config_file xdg "config.toml" with 31 | None -> None 32 | Some path -> 33 let (_fs, path_str) = path in 34 (try Some (Tomlt_unix.decode_file_exn config_codec path_str) 35 with _ -> None) 36 37(* Command-line arguments *) 38let port_arg = 39 let doc = "TCP port to listen on." in 40 Arg.(value & opt (some int) None & info ["p"; "port"] ~docv:"PORT" ~doc) 41 42let root_arg = 43 let doc = "Document root directory to serve." in 44 Arg.(value & opt (some string) None & info ["d"; "root"] ~docv:"DIR" ~doc) 45 46(* Build limits from config file and defaults *) 47let build_limits ?config () = 48 let defaults = Httpzo.default_limits in 49 match config with 50 | None -> defaults 51 | Some c -> 52 { Httpzo.Buf_read.max_content_length = 53 Option.value c.max_content_length ~default:defaults.max_content_length 54 ; max_header_size = 55 Option.value c.max_header_size ~default:defaults.max_header_size 56 ; max_header_count = 57 Option.value c.max_header_count ~default:defaults.max_header_count 58 ; max_chunk_size = 59 Option.value c.max_chunk_size ~default:defaults.max_chunk_size 60 } 61 62(* Main server function *) 63let run_server env xdg port_opt root_opt = 64 let file_config = load_config xdg in 65 (* Priority: CLI > config file > defaults *) 66 let port = 67 match port_opt with 68 | Some p -> p 69 | None -> 70 match file_config with 71 | Some { port = Some p; _ } -> p 72 | _ -> 8080 73 in 74 let root_str = 75 match root_opt with 76 | Some r -> r 77 | None -> 78 match file_config with 79 | Some { root = Some r; _ } -> r 80 | _ -> "." 81 in 82 let limits = build_limits ?config:file_config () in 83 let fs = Eio.Stdenv.fs env in 84 let root = Eio.Path.(fs / root_str) in 85 let config = Httpzo.Server.{ port; root; limits } in 86 Eio.Switch.run @@ fun sw -> 87 Httpzo.Server.run ~net:(Eio.Stdenv.net env) ~sw config 88 89(* Cmdliner term *) 90let main_term = 91 let run port root = 92 Eio_main.run @@ fun env -> 93 let fs = Eio.Stdenv.fs env in 94 let xdg = Xdge.create fs "httpzo" in 95 run_server env xdg port root 96 in 97 Term.(const run $ port_arg $ root_arg) 98 99let cmd = 100 let doc = "Static file server using httpzo" in 101 let man = [ 102 `S Manpage.s_description; 103 `P "Serves static files over HTTP/1.1 with support for:"; 104 `I ("Range requests", "Partial content with byte ranges"); 105 `I ("ETag", "Weak entity tags for cache validation"); 106 `I ("If-None-Match", "Conditional requests returning 304"); 107 `I ("Keep-alive", "Connection reuse for HTTP/1.1"); 108 `S Manpage.s_files; 109 `P "Configuration is read from $(b,~/.config/httpzo/config.toml) or \ 110 the path specified by $(b,XDG_CONFIG_HOME)."; 111 `P "Example config.toml:"; 112 `Pre "port = 8080\n\ 113 root = \"/var/www/html\"\n\ 114 max_content_length = 104857600"; 115 `S Manpage.s_environment; 116 `P "$(b,HTTPZO_CONFIG_DIR): Override configuration directory."; 117 `P "$(b,XDG_CONFIG_HOME): XDG base directory for config files."; 118 ] in 119 let info = Cmd.info "httpzo" ~version:"0.1" ~doc ~man in 120 Cmd.v info main_term 121 122let () = exit (Cmd.eval cmd)