An OCaml webserver, but the allocating version (vs httpz which doesnt)
at main 138 lines 4.9 kB view raw
1(* request_parse.ml - HTTP request parsing logic *) 2 3open Base 4 5(* Connection header disposition *) 6type conn_value = Conn_default | Conn_close | Conn_keep_alive 7 8(* Header parsing state *) 9type header_state = 10 { count : int 11 ; content_len : int64 12 ; chunked : bool 13 ; conn : conn_value 14 ; has_cl : bool 15 ; has_te : bool 16 ; has_host : bool 17 ; expect_continue : bool 18 } 19 20let initial_header_state = 21 { count = 0 22 ; content_len = -1L 23 ; chunked = false 24 ; conn = Conn_default 25 ; has_cl = false 26 ; has_te = false 27 ; has_host = false 28 ; expect_continue = false 29 } 30 31(* Helper to create error result with empty request *) 32let error_result status = 33 ( status 34 , { Req.meth = Method.Get 35 ; target = Span.make ~off:0 ~len:0 36 ; version = Version.Http_1_1 37 ; body_off = 0 38 ; content_length = -1L 39 ; is_chunked = false 40 ; keep_alive = true 41 ; expect_continue = false 42 } 43 , ([] : Header.t list) ) 44 45(* Build successful request from parsed components and state *) 46let build_request ~meth ~target ~version ~body_off st ~headers = 47 let keep_alive = 48 match st.conn with 49 | Conn_close -> false 50 | Conn_keep_alive -> true 51 | Conn_default -> Poly.( = ) version Version.Http_1_1 52 in 53 let req = 54 { Req.meth 55 ; target 56 ; version 57 ; body_off 58 ; content_length = st.content_len 59 ; is_chunked = st.chunked 60 ; keep_alive 61 ; expect_continue = st.expect_continue 62 } 63 in 64 (Buf_read.Complete, req, headers) 65 66(* Determine Connection header value *) 67let parse_connection_value buf value_span ~default = 68 if Span.equal_caseless buf value_span "close" then Conn_close 69 else if Span.equal_caseless buf value_span "keep-alive" then Conn_keep_alive 70 else default 71 72(* Parse headers using Parser combinators. Raises Err.Parse_error on failure. 73 Position is threaded explicitly for zero allocation. *) 74let rec parse_headers_loop pst ~pos ~acc st ~limits = 75 let open Buf_read in 76 if Parser.is_headers_end pst ~pos then ( 77 let pos = Parser.end_headers pst ~pos in 78 (pos, st, acc) 79 ) 80 else ( 81 Err.when_ (st.count >= limits.max_header_count) Err.Headers_too_large; 82 let (name, name_span, value_span, pos) = Parser.parse_header pst ~pos in 83 Err.when_ (has_bare_cr pst.buf ~pos:(Span.off value_span) ~len:(Span.len value_span)) 84 Err.Bare_cr_detected; 85 let next_count = st.count + 1 in 86 match name with 87 | Header_name.Content_length -> 88 Err.when_ st.has_te Err.Ambiguous_framing; 89 let (parsed_len, overflow) = 90 Span.parse_int64_limited pst.buf value_span ~max_value:limits.max_content_length 91 in 92 Err.when_ overflow Err.Content_length_overflow; 93 parse_headers_loop pst ~pos ~acc ~limits 94 { st with count = next_count; content_len = parsed_len; has_cl = true } 95 | Header_name.Transfer_encoding -> 96 Err.when_ st.has_cl Err.Ambiguous_framing; 97 let is_chunked = Span.equal_caseless pst.buf value_span "chunked" in 98 let is_identity = Span.equal_caseless pst.buf value_span "identity" in 99 Err.when_ (not (is_chunked || is_identity)) Err.Unsupported_transfer_encoding; 100 parse_headers_loop pst ~pos ~acc ~limits 101 { st with count = next_count; chunked = is_chunked; has_te = true } 102 | Header_name.Host -> 103 let hdr = { Header.name; name_span; value = value_span } in 104 parse_headers_loop pst ~pos ~acc:(hdr :: acc) ~limits 105 { st with count = next_count; has_host = true } 106 | Header_name.Connection -> 107 let new_conn = parse_connection_value pst.buf value_span ~default:st.conn in 108 parse_headers_loop pst ~pos ~acc ~limits 109 { st with count = next_count; conn = new_conn } 110 | Header_name.Expect -> 111 let is_continue = Span.equal_caseless pst.buf value_span "100-continue" in 112 parse_headers_loop pst ~pos ~acc ~limits 113 { st with count = next_count; expect_continue = is_continue || st.expect_continue } 114 | _ -> 115 let hdr = { Header.name; name_span; value = value_span } in 116 parse_headers_loop pst ~pos ~acc:(hdr :: acc) ~limits 117 { st with count = next_count } 118 ) 119 120(* Parse HTTP request with configurable limits and full RFC 7230 validation. 121 Uses Parser combinators for cleaner, more maintainable parsing. *) 122let parse buf ~len ~limits = 123 let open Buf_read in 124 if len > buffer_size || len > limits.max_header_size then 125 error_result Headers_too_large 126 else 127 try 128 let pst = Parser.make buf ~len in 129 let (meth, target, version, pos) = Parser.request_line pst ~pos:0 in 130 let (body_off, st, headers) = 131 parse_headers_loop pst ~pos ~acc:[] initial_header_state ~limits 132 in 133 (* Only missing Host header needs end-of-parse check *) 134 match (version, st.has_host) with 135 | (Version.Http_1_1, false) -> error_result Missing_host_header 136 | _ -> build_request ~meth ~target ~version ~body_off st ~headers 137 with Err.Parse_error status -> 138 error_result status