Httpz provides a stack-allocated HTTP/1.1 request parser that operates on a 32KB bigarray buffer with zero heap allocations during parsing. The parser is RFC 7230 compliant and includes built-in security features.
The parser returns a Buf_read.status value indicating the result:
match status with| Buf_read.Complete -> (* Request fully parsed and valid *) let method_ = req.#meth in let target = req.#target in let version = req.#version in (* Process the request... *)
Content-related headers are cached in the request structure for efficient access:
(* Content-Length (or -1L if not present) *)let content_len = req.#content_length inif Int64_u.compare content_len (Int64_u.of_int64 (-1L)) <> 0 then (* Content-Length is present *) let len = Int64_u.to_int64 content_len in (* ... *)(* Transfer-Encoding: chunked *)if req.#is_chunked then (* Use Chunk.parse for body *) parse_chunked_body buf reqelse (* Fixed-length body *) read_body_content buf req(* Connection: keep-alive *)if req.#keep_alive then (* Keep connection open for next request *) handle_persistent_connection ()else (* Close after response *) close_after_response ()(* Expect: 100-continue *)if req.#expect_continue then (* Send 100 Continue before reading body *) send_100_continue ()
Headers are returned as a stack-allocated list. Content headers (Content-Length, Transfer-Encoding, Connection, Expect) are excluded from this list as they’re cached in the request structure.
(* Find by known header name *)let host_hdr = Header.find headers Header_name.Host inmatch host_hdr with| Some hdr -> let host = Span.to_string buf hdr.value in (* Use host value *)| None -> (* Host header not present *)
List.iter (fun hdr -> match hdr.Header.name with | Header_name.Host -> let host = Span.to_string buf hdr.value in (* ... *) | Header_name.User_agent -> let ua = Span.to_string buf hdr.value in (* ... *) | Header_name.Other -> (* Custom header - use name_span for comparison *) if Span.equal_caseless buf hdr.name_span "x-custom-header" then let value = Span.to_string buf hdr.value in (* ... *) | _ -> ()) headers
Headers and request data reference the buffer via Span.t (offset + length). Extract strings only when needed to minimize allocations.
Check if the complete body is available in the buffer:
(* Check if body is fully in buffer *)if Req.body_in_buffer ~len:len16 req then ( (* Get body span *) let body_span = Req.body_span ~len:len16 req in if Span.len body_span > 0 then ( (* Extract body content *) let body_str = Span.to_string buf body_span in (* Process body... *) )) else ( (* Need to read more data *) let needed = Req.body_bytes_needed ~len:len16 req in read_more_bytes needed)
For chunked transfer encoding (req.#is_chunked = true), use the Chunk module to parse the body instead of Req.body_span.
open Httpzlet handle_request socket = (* Create reusable buffer *) let buf = create_buffer () in (* Read from socket *) let len = Unix.read socket buf 0 buffer_size in let len16 = Buf_read.i16 len in (* Parse request *) let #(status, req, headers) = parse buf ~len:len16 ~limits:default_limits in match status with | Buf_read.Complete -> (* Extract request data *) let method_ = req.#meth in let target = Span.to_string buf req.#target in let content_len = req.#content_length in (* Find headers *) let host = match Header.find headers Header_name.Host with | Some hdr -> Span.to_string buf hdr.value | None -> "unknown" in (* Handle request... *) Printf.printf "Method: %s, Target: %s, Host: %s\n" (Method.to_string method_) target host; (* Check for body *) if req.#is_chunked then handle_chunked_body buf req else if Int64_u.compare content_len (Int64_u.of_int64 0L) > 0 then handle_fixed_body buf req ~len:len16 else (* No body *) () | Buf_read.Partial -> (* Need more data *) read_more_and_retry socket buf | Buf_read.Bare_cr_detected | Buf_read.Ambiguous_framing -> (* Security violation *) send_400_response socket | Buf_read.Headers_too_large | Buf_read.Content_length_overflow -> send_413_response socket | _ -> send_400_response socket