package tiny_httpd
Minimal HTTP server using threads
Install
Dune Dependency
Authors
Maintainers
Sources
tiny_httpd-0.19.tbz
sha256=55b6eed7d2e41b9b2a282b344e01ca010abdba66cbbbbe5adab18303b9ff8360
sha512=98c599627b9cd98f641d389e022f9ddc025bfba838793267904b94a01b0b40b3b4cfd8f5a8d8214aaa947958e45cecbe1df751692a461f929802714d7af50e06
doc/src/tiny_httpd.core/request.ml.html
Source file request.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 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
open Common_ type 'body t = { meth: Meth.t; host: string; client_addr: Unix.sockaddr; headers: Headers.t; mutable meta: Hmap.t; http_version: int * int; path: string; path_components: string list; query: (string * string) list; body: 'body; start_time: float; } let headers self = self.headers let host self = self.host let client_addr self = self.client_addr let meth self = self.meth let path self = self.path let body self = self.body let start_time self = self.start_time let query self = self.query let get_header ?f self h = Headers.get ?f h self.headers let remove_header k self = { self with headers = Headers.remove k self.headers } let add_meta self k v = self.meta <- Hmap.add k v self.meta let get_meta self k = Hmap.find k self.meta let get_meta_exn self k = Hmap.get k self.meta let get_header_int self h = match get_header self h with | Some x -> (try Some (int_of_string x) with _ -> None) | None -> None let set_header k v self = { self with headers = Headers.set k v self.headers } let update_headers f self = { self with headers = f self.headers } let set_body b self = { self with body = b } (** Should we close the connection after this request? *) let close_after_req (self : _ t) : bool = match self.http_version with | 1, 1 -> get_header self "connection" = Some "close" | 1, 0 -> not (get_header self "connection" = Some "keep-alive") | _ -> false let pp_comp_ out comp = Format.fprintf out "[%s]" (String.concat ";" @@ List.map (Printf.sprintf "%S") comp) let pp_query out q = Format.fprintf out "[%s]" (String.concat ";" @@ List.map (fun (a, b) -> Printf.sprintf "%S,%S" a b) q) let pp_with ?(mask_header = fun _ -> false) ?(headers_to_mask = [ "authorization"; "cookie" ]) ?(show_query = true) ?(pp_body = fun out _ -> Format.pp_print_string out "?") () out self : unit = let pp_query out q = if show_query then pp_query out q else Format.fprintf out "<hidden>" in let headers_to_mask = List.rev_map String.lowercase_ascii headers_to_mask in (* hide some headers *) let headers = List.map (fun (k, v) -> let = List.mem k headers_to_mask || mask_header k in if hidden then k, "<hidden>" else k, v) self.headers in Format.fprintf out "{@[meth=%s;@ host=%s;@ headers=[@[%a@]];@ path=%S;@ body=%a;@ \ path_components=%a;@ query=%a@]}" (Meth.to_string self.meth) self.host Headers.pp headers self.path pp_body self.body pp_comp_ self.path_components pp_query self.query let pp_ out self : unit = pp_with () out self let pp out self : unit = let pp_body out b = Format.fprintf out "%S" b in pp_with ~pp_body () out self (* decode a "chunked" stream into a normal stream *) let read_stream_chunked_ ~bytes (bs : #IO.Input.t) : IO.Input.t = Log.debug (fun k -> k "body: start reading chunked stream..."); IO.Input.read_chunked ~bytes ~fail:(fun s -> Bad_req (400, s)) bs let limit_body_size_ ~max_size ~bytes (bs : #IO.Input.t) : IO.Input.t = Log.debug (fun k -> k "limit size of body to max-size=%d" max_size); IO.Input.limit_size_to ~max_size ~close_rec:false ~bytes bs let limit_body_size ~max_size ~bytes (req : IO.Input.t t) : IO.Input.t t = { req with body = limit_body_size_ ~max_size ~bytes req.body } (** read exactly [size] bytes from the stream *) let read_exactly ~size ~bytes (bs : #IO.Input.t) : IO.Input.t = Log.debug (fun k -> k "body: must read exactly %d bytes" size); IO.Input.reading_exactly bs ~close_rec:false ~bytes ~size (* parse request, but not body (yet) *) let parse_req_start ~client_addr ~get_time_s ~buf (bs : IO.Input.t) : unit t option resp_result = try let line = IO.Input.read_line_using ~buf bs in Log.debug (fun k -> k "parse request line: %S" line); if line <> "" && line.[String.length line - 1] <> '\r' then bad_reqf 400 "invalid status line, not ending in CRLF"; let start_time = get_time_s () in let meth, path, version = try let off = ref 0 in let meth = Parse_.word line off in let path = Parse_.word line off in let http_version = Parse_.word line off in let version = match http_version with | "HTTP/1.1" -> 1 | "HTTP/1.0" -> 0 | v -> invalid_arg (spf "unsupported HTTP version: %S" v) in meth, path, version with | Invalid_argument msg -> Log.error (fun k -> k "invalid request line: %S: %s" line msg); raise (Bad_req (400, "Invalid request line")) | exn -> Log.error (fun k -> k "invalid request line: %S: %s" line (Printexc.to_string exn)); raise (Bad_req (400, "Invalid request line")) in let meth = Meth.of_string meth in Log.debug (fun k -> k "got meth: %S, path %S" (Meth.to_string meth) path); let headers = Headers.parse_ ~buf bs in let host = match Headers.get "Host" headers with | None -> bad_reqf 400 "No 'Host' header in request" | Some h -> h in let path_components, query = Util.split_query path in let path_components = Util.split_on_slash path_components in let query = match Util.parse_query query with | Ok l -> l | Error e -> bad_reqf 400 "invalid query: %S" e in let req = { meth; query; host; meta = Hmap.empty; client_addr; path; path_components; headers; http_version = 1, version; body = (); start_time; } in Ok (Some req) with | End_of_file | Sys_error _ | Unix.Unix_error _ -> Ok None | Bad_req (c, s) -> Error (c, s) | e -> Error (400, Printexc.to_string e) (* parse body, given the headers. @param tr_stream a transformation of the input stream. *) let parse_body_ ~tr_stream ~bytes (req : IO.Input.t t) : IO.Input.t t resp_result = try let size, has_size = match Headers.get_exn "Content-Length" req.headers |> int_of_string with | n -> n, true (* body of fixed size *) | exception Not_found -> 0, false | exception _ -> bad_reqf 400 "invalid content-length" in let body = match get_header ~f:String.trim req "Transfer-Encoding" with | None -> read_exactly ~size ~bytes @@ tr_stream req.body | Some "chunked" when has_size -> bad_reqf 400 "specifying both transfer-encoding and content-length" | Some "chunked" -> (* body sent by chunks *) let bs : IO.Input.t = read_stream_chunked_ ~bytes @@ tr_stream req.body in if size > 0 then ( (* TODO: ensure we recycle [bytes] when the new input is closed *) let bytes = Bytes.create 4096 in limit_body_size_ ~max_size:size ~bytes bs ) else bs | Some s -> bad_reqf 500 "cannot handle transfer encoding: %s" s in Ok { req with body } with | End_of_file -> Error (400, "unexpected end of file") | Bad_req (c, s) -> Error (c, s) | e -> Error (400, Printexc.to_string e) let read_body_full ?bytes ?buf_size (self : IO.Input.t t) : string t = try let buf = match bytes with | Some b -> Buf.of_bytes b | None -> Buf.create ?size:buf_size () in let body = IO.Input.read_all_using ~buf self.body in { self with body } with | Bad_req _ as e -> raise e | e -> bad_reqf 500 "failed to read body: %s" (Printexc.to_string e) module Private_ = struct let close_after_req = close_after_req let parse_req_start = parse_req_start let parse_req_start_exn ?(buf = Buf.create ()) ~client_addr ~get_time_s bs = parse_req_start ~client_addr ~get_time_s ~buf bs |> unwrap_resp_result let parse_body ?(bytes = Bytes.create 4096) req bs : _ t = parse_body_ ~tr_stream:(fun s -> s) ~bytes { req with body = bs } |> unwrap_resp_result let[@inline] set_body body self = { self with body } end
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>