File: http.ml

package info (click to toggle)
ocaml-dune 3.20.2-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 33,564 kB
  • sloc: ml: 175,178; asm: 28,570; ansic: 5,251; sh: 1,096; lisp: 625; makefile: 148; python: 125; cpp: 48; javascript: 10
file content (104 lines) | stat: -rw-r--r-- 2,729 bytes parent folder | download | duplicates (2)
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
open Stdune

module Server = struct
  type t =
    { sock : Unix.file_descr
    ; addr : Unix.sockaddr
    }

  type session = in_channel * out_channel

  let close_session (_, out) = Out_channel.close out

  let make addr =
    let sock = Unix.socket ~cloexec:true Unix.PF_INET Unix.SOCK_STREAM 0 in
    { sock; addr }
  ;;

  let port t =
    match Unix.getsockname t.sock with
    | Unix.ADDR_INET (_, port) -> port
    | ADDR_UNIX _ -> failwith "no port defined"
  ;;

  let start t =
    Unix.setsockopt t.sock Unix.SO_REUSEADDR true;
    Unix.bind t.sock t.addr;
    Unix.listen t.sock 1
  ;;

  let accept_request (in_, _) =
    let rec loop () =
      match In_channel.input_line in_ with
      | Some "\r" | None -> ()
      | Some _ -> loop ()
    in
    loop ()
  ;;

  let auto_shutdown_seconds =
    match Sys.getenv_opt "DUNE_WEBSERVER_TIMEOUT" with
    | None -> 5.
    | Some s -> Float.of_string s |> Option.value_exn
  ;;

  let accept t ~f =
    let descr, _sockaddr =
      let read_fds, _write_fds, _excpt_fds =
        Unix.select [ t.sock ] [] [] auto_shutdown_seconds
      in
      match read_fds with
      | _ :: _ -> Unix.accept ~cloexec:true t.sock
      | [] ->
        Format.eprintf "Exiting after timeout@.";
        failwith "timeout"
    in
    let out = Unix.out_channel_of_descr descr in
    let in_ = Unix.in_channel_of_descr descr in
    let session = in_, out in
    Exn.protect ~f:(fun () -> f session) ~finally:(fun () -> close_session session)
  ;;

  let stop t = Unix.close t.sock

  let respond (_, out) ~status ~content_length =
    let status =
      match status with
      | `Ok -> "200 OK"
      | `Not_found -> "404 Not Found"
    in
    Printf.fprintf
      out
      "HTTP/1.1 %s\r\nConnection: close\r\nContent-Length: %Ld\r\n\r\n%!"
      status
      content_length
  ;;

  let respond_file session ~file =
    In_channel.with_open_bin file (fun chan ->
      let content_length = In_channel.length chan in
      respond session ~status:`Ok ~content_length;
      let bytes = Bytes.create 65536 in
      let to_write = ref (Int64.to_int content_length) in
      let out = snd session in
      let rec loop () =
        let size = In_channel.input chan bytes 0 (Bytes.length bytes) in
        if size > 0
        then (
          to_write := !to_write - size;
          Out_channel.output out bytes 0 size;
          loop ())
      in
      loop ();
      assert (!to_write = 0);
      Out_channel.flush out)
  ;;

  let respond session ~status ~content =
    let content_length = Int64.of_int (String.length content) in
    respond session ~status ~content_length;
    let out = snd session in
    Out_channel.output_string out content;
    Out_channel.flush out
  ;;
end