163 lines
5.1 KiB
OCaml
163 lines
5.1 KiB
OCaml
module Make (App_id : sig
|
|
val qualifier : string
|
|
|
|
val organization : string
|
|
|
|
val application : string
|
|
end) =
|
|
struct
|
|
module Project = struct
|
|
include Directories.Project_dirs (App_id)
|
|
|
|
let force_dir d name =
|
|
match d with
|
|
| None -> Fmt.failwith "can not compute %s directory path" name
|
|
| Some dir -> dir
|
|
|
|
let cache_dir = force_dir cache_dir "cache"
|
|
|
|
let config_dir = force_dir config_dir "config"
|
|
|
|
let data_dir = force_dir data_dir "data"
|
|
|
|
let data_local_dir = force_dir data_local_dir "data local"
|
|
|
|
let preference_dir = force_dir preference_dir "preference"
|
|
|
|
let runtime_dir = force_dir runtime_dir "runtime"
|
|
|
|
let state_dir = force_dir state_dir "state"
|
|
end
|
|
|
|
let config =
|
|
let filename = Fpath.(Project.config_dir / "config.scfg") in
|
|
match Scfg.Parse.from_file filename with
|
|
| Error (`Msg _msg) ->
|
|
(* TODO: warn to say there is no config file! *)
|
|
[]
|
|
| Ok config -> config
|
|
|
|
open Scfg.Query
|
|
|
|
let port =
|
|
let directive = get_dir "port" config in
|
|
match directive with
|
|
| None ->
|
|
(* TODO: warn to say we use a default port! *)
|
|
8080
|
|
| Some directive -> (
|
|
let param = get_param_int 0 directive in
|
|
match param with
|
|
| Error (`Msg msg) -> Fmt.failwith "%s" msg
|
|
| Ok port -> port )
|
|
|
|
let listen () =
|
|
let inet_addr = Unix.inet_addr_any in
|
|
let sockaddr = Unix.ADDR_INET (inet_addr, port) in
|
|
sockaddr
|
|
|
|
let stop = Httpcats.Server.stop ()
|
|
|
|
let prepare_content content =
|
|
let content_type = Content.to_mimetype content |> Mimetype.to_string in
|
|
let content =
|
|
match content with
|
|
(* | Content.Css sheet -> Fmt.str "%a@\n" Css.pp_string_css sheet *)
|
|
| Html doc -> Html_doc.to_string doc
|
|
| JavaScript program ->
|
|
let open Js_of_ocaml_compiler in
|
|
let accept_unnamed_var = true in
|
|
let buffer = Buffer.create 4096 in
|
|
let pp = Pretty_print.to_buffer buffer in
|
|
let _source_map : Source_map.info =
|
|
Js_output.program ~accept_unnamed_var pp program
|
|
in
|
|
(* setting a charset is invalid for JavaScript ! *)
|
|
Buffer.contents buffer
|
|
| Txt txt -> txt
|
|
| Unsafe { content; _ } -> content
|
|
in
|
|
let content_length = String.length content in
|
|
let headers =
|
|
[ ("content-type", content_type)
|
|
; ("content-length", string_of_int content_length)
|
|
]
|
|
in
|
|
(headers, content)
|
|
|
|
let status_of_response = function
|
|
| Ok _ -> `OK
|
|
| Error (status, _) -> Status.to_httpcats status
|
|
|
|
let status_headers_of_response = function
|
|
| Ok content -> begin
|
|
match Content.to_mimetype content with
|
|
| Audio_aac | Audio_midi | Audio_mp3 | Audio_wav | Audio_weba | Image_jpeg
|
|
| Image_png | Image_gif | Image_bmp | Image_ico | Image_svg | Image_webp
|
|
| Font_ttf | Text_css | Text_javascript
|
|
| Unsafe (_, true) ->
|
|
[ ("Cache-Control", "max-age=151200") ]
|
|
| Text_html | Text_plain
|
|
| Unsafe (_, false)
|
|
| Application_epub | Application_json | Application_jsonld
|
|
| Application_gz | Application_tar | Application_xml
|
|
| Application_atom_xml | Application_zip | Application_7z | Text_csv
|
|
| Video_avi | Video_mp4 | Video_webm ->
|
|
[]
|
|
end
|
|
| Error
|
|
( ( Status.See_other redirect
|
|
| Found redirect
|
|
| Moved_permanently redirect )
|
|
, _ ) ->
|
|
[ ("Location", redirect) ]
|
|
| Error _ -> []
|
|
|
|
let content_of_response = function
|
|
| Ok content -> content
|
|
| Error (_, content) -> Content.Html content
|
|
|
|
(* TODO :/ *)
|
|
let downgrade_status : Httpcats.Status.t -> H1.Status.t = function
|
|
| `Misdirected_request -> assert false
|
|
| `Code _ -> assert false
|
|
| #H1.Status.standard as t -> t
|
|
|
|
let respond reqd headers status content =
|
|
match reqd with
|
|
| `V1 reqd ->
|
|
let open H1 in
|
|
let headers = Headers.of_list headers in
|
|
let status = downgrade_status status in
|
|
let resp = Response.create ~headers status in
|
|
Reqd.respond_with_string reqd resp content
|
|
| `V2 reqd ->
|
|
let open H2 in
|
|
let headers = Headers.of_list headers in
|
|
let resp = Response.create ~headers status in
|
|
Reqd.respond_with_string reqd resp content
|
|
|
|
let mk_server ~(handler : Handler.t) =
|
|
let handler (_ : [ `Tcp of Miou_unix.file_descr | `Tls of _ ])
|
|
(reqd : Httpcats.Server.reqd) =
|
|
let ({ Request.route; _ } as request) = Request.of_reqd reqd in
|
|
let response = handler route request in
|
|
let status = status_of_response response in
|
|
let status_headers = status_headers_of_response response in
|
|
let content = content_of_response response in
|
|
let session = Session.load request in
|
|
let session_headers = Session.make_send_headers session in
|
|
let content_headers, content = prepare_content content in
|
|
let headers = session_headers @ content_headers @ status_headers in
|
|
respond reqd headers status content
|
|
in
|
|
let file_descr = listen () in
|
|
Httpcats.Server.clear ~stop ~handler file_descr;
|
|
()
|
|
|
|
let () = Sys.set_signal Sys.sigpipe Sys.Signal_ignore
|
|
|
|
let run ~(handler : Handler.t) =
|
|
let server () = mk_server ~handler in
|
|
Miou_unix.run server
|
|
end
|