package vif

  1. Overview
  2. Docs

Source file vif_handler_unix.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
open Vif_core

type ('c, 'value) t = (Httpcats.Server.flow, 'c, 'value) Handler.t

let pwd = Fpath.v (Unix.getcwd ())
let tree = Conan_light.tree
let ( let@ ) finally fn = Fun.protect ~finally fn

let file ?offset path =
  let open Flux in
  let buf = Bytes.create 0x7ff in
  let fn bqueue =
    let fd = Unix.openfile path Unix.[ O_RDONLY ] 0o644 in
    let resource = Miou.Ownership.create ~finally:Unix.close fd in
    Miou.Ownership.own resource;
    let@ () =
     fun () ->
      Miou.Ownership.release resource;
      Bqueue.close bqueue
    in
    let _ =
      match offset with
      | Some offset -> Unix.lseek fd offset Unix.SEEK_SET
      | None -> 0
    in
    let rec go () =
      let len = Unix.read fd buf 0 (Bytes.length buf) in
      if len > 0 then (
        let str = Bytes.sub_string buf 0 len in
        Bqueue.put bqueue str; go ())
    in
    go ()
  in
  Source.with_task ~size:0x7ff fn

let sha256sum path =
  let path = Fpath.to_string path in
  if Sys.file_exists path = false || Sys.is_directory path then
    invalid_arg "sha256sum";
  let fd = Unix.openfile path Unix.[ O_RDONLY ] 0o644 in
  let finally () = Unix.close fd in
  Fun.protect ~finally @@ fun () ->
  let stat = Unix.fstat fd in
  let ba =
    Unix.map_file fd Bigarray.char Bigarray.c_layout false
      [| stat.Unix.st_size |]
  in
  let ba = Bigarray.array1_of_genarray ba in
  let hash = Digestif.SHA256.digest_bigstring ba in
  Digestif.SHA256.to_hex hash

let mime_type path =
  match Conan_unix.run_with_tree Conan_light.tree (Fpath.to_string path) with
  | Ok m ->
      Option.value ~default:"application/octet-stream" (Conan.Metadata.mime m)
  | Error _ -> "application/octet-stream"
  | exception _ -> "application/octet-stream"

let cached_on_client_side ?etag req target =
  let hdrs = Request.headers req in
  match (Headers.get hdrs "if-none-match", etag) with
  | Some hash', None ->
      let hash = sha256sum target in
      String.equal hash hash'
  | Some hash', Some hash -> String.equal hash hash'
  | None, _ -> false

let valid ~top target =
  Fpath.is_prefix top target
  && Sys.file_exists (Fpath.to_string target)
  && Sys.is_directory (Fpath.to_string target) = false

let pp_msg ppf (`Msg msg) = Fmt.string ppf msg

let trim lst =
  let lst = List.drop_while (( = ) "") lst in
  let lst = List.drop_while (( = ) "") (List.rev lst) in
  List.rev lst

module K = struct
  type t = Fpath.t

  let equal = Fpath.equal
  let hash = Hashtbl.hash
end

module V = struct
  type t = { mtime: float; mime: string }

  let weight _ = 1
end

module Cache = Lru.M.Make (K) (V)

let cached_on_server_size stat abs_path cache =
  match Cache.find abs_path cache with
  | Some { mtime; mime } when mtime >= stat.Unix.st_mtime -> Some mime
  | Some _ ->
      Cache.remove abs_path cache;
      None
  | None -> None

let static ?(top = pwd) =
  ();
  let cache = Cache.create ~random:true 0x100 in
  fun req target _server _ ->
    let target = String.split_on_char '/' target in
    let target = trim target in
    let target = String.concat "/" target in
    let abs_path =
      let ( let* ) = Result.bind in
      let* x = Fpath.of_string target in
      Ok Fpath.(normalize (top // x))
    in
    match (Request.meth req, abs_path) with
    | `GET, Ok abs_path when valid ~top abs_path -> begin
        let ( let* ) = Response.bind in
        let process =
          if cached_on_client_side req abs_path then
            let* () = Response.with_string req "" in
            Response.respond `Not_modified
          else
            let stat = Unix.stat (Fpath.to_string abs_path) in
            let mime =
              match cached_on_server_size stat abs_path cache with
              | Some mime -> mime
              | None ->
                  let mime = mime_type abs_path in
                  let value = { V.mtime= stat.Unix.st_mtime; mime } in
                  Cache.add abs_path value cache;
                  mime
            in
            let src = file (Fpath.to_string abs_path) in
            let* _ = Response.content_length stat.Unix.st_size in
            let field = "content-type" in
            let* () = Response.add ~field mime in
            let field = "etag" in
            let* () = Response.add ~field (sha256sum abs_path) in
            let* () = Response.with_source req src in
            Response.respond `OK
        in
        Some process
      end
    | _ -> None