This guide shows you how to parse HTTP/1.1 requests using httpz with zero heap allocations. You’ll learn the fundamental concepts and see real examples from the source code.
Here’s a minimal example that parses an HTTP GET request:
open Baselet parse_request request_string = (* Create a 32KB buffer - allocate once, reuse for all requests *) let buf = Httpz.create_buffer () in (* Copy request into buffer *) let len = String.length request_string in for i = 0 to len - 1 do Bigarray.Array1.set buf i (String.get request_string i) done; (* Parse with default security limits *) let #(status, req, headers) = Httpz.parse buf ~len:(Httpz.Buf_read.i16 len) ~limits:Httpz.default_limits in (* Check if parsing succeeded *) match status with | Httpz.Buf_read.Complete -> (* Access request fields from unboxed record *) Stdio.printf "Method: %s\n" (Httpz.Method.to_string req.#meth); Stdio.printf "Target: %s\n" (Httpz.Span.to_string buf req.#target); Stdio.printf "Version: HTTP/%s\n" (match req.#version with | Httpz.Version.Http_1_0 -> "1.0" | Httpz.Version.Http_1_1 -> "1.1"); (* Iterate headers *) List.iter headers ~f:(fun hdr -> let name = match hdr.Httpz.Header.name with | Httpz.Header.Name.Host -> "Host" | Httpz.Header.Name.Content_type -> "Content-Type" | Httpz.Header.Name.Other -> Httpz.Span.to_string buf hdr.Httpz.Header.name_span | _ -> "<other>" in let value = Httpz.Span.to_string buf hdr.Httpz.Header.value in Stdio.printf " %s: %s\n" name value ) | Httpz.Buf_read.Partial -> Stdio.print_endline "Need more data" | _ -> Stdio.printf "Parse error: %s\n" (Httpz.Buf_read.status_to_string status)let () = let request = "GET /index.html HTTP/1.1\r\nHost: example.com\r\n\r\n" in parse_request request
The #(status, req, headers) syntax destructures an unboxed tuple returned by the parser. All values are stack-allocated.
The req is an unboxed record containing parsed request data:
type req = #{ meth : Method.t; (* GET, POST, PUT, etc. *) target : Span.t; (* Request target as span into buffer *) version : Version.t; (* HTTP/1.0 or HTTP/1.1 *) body_off : int16#; (* Offset where body starts *) content_length : int64#; (* Content-Length value, -1 if absent *) is_chunked : bool; (* Transfer-Encoding: chunked *) keep_alive : bool; (* Connection: keep-alive *) expect_continue : bool; (* Expect: 100-continue *)}
Content headers (Content-Length, Transfer-Encoding, Connection, Expect) are cached in the request struct and excluded from the header list for efficiency.
The headers is a local list (stack-allocated) of parsed headers:
type header = { name : Header_name.t; (* Typed header name *) name_span : Span.t; (* Span for unknown headers *) value : Span.t; (* Header value as span *)}
let handle_post buf len = let #(status, req, headers) = Httpz.parse buf ~len:(Httpz.Buf_read.i16 len) ~limits:Httpz.default_limits in match status with | Httpz.Buf_read.Complete -> (* Check if body is completely in buffer *) if Httpz.Req.body_in_buffer ~len:(Httpz.Buf_read.i16 len) req then ( (* Get body as span *) let body = Httpz.Req.body_span ~len:(Httpz.Buf_read.i16 len) req in (* Access body content directly from buffer *) let body_str = Httpz.Span.to_string buf body in Stdio.printf "Body: %s\n" body_str; ) else ( (* Need more data *) let needed = Httpz.Req.body_bytes_needed ~len:(Httpz.Buf_read.i16 len) req in Stdio.printf "Need %d more bytes\n" (Httpz.Buf_read.to_int needed) ) | _ -> Stdio.printf "Parse error: %s\n" (Httpz.Buf_read.status_to_string status)
let find_content_type headers buf = (* Find by known header name *) match Httpz.Header.find headers Httpz.Header.Name.Content_type with | Some hdr -> Stdio.printf "Content-Type: %s\n" (Httpz.Span.to_string buf hdr.Httpz.Header.value) | None -> Stdio.print_endline "No Content-Type header"let find_custom_header headers buf = (* Find by string name (case-insensitive) *) match Httpz.Header.find_string buf headers "X-Custom-Header" with | Some hdr -> Stdio.printf "X-Custom-Header: %s\n" (Httpz.Span.to_string buf hdr.Httpz.Header.value) | None -> Stdio.print_endline "No X-Custom-Header"
let handle_chunked req buf = if req.#is_chunked then ( Stdio.print_endline "Request uses chunked encoding"; (* Use Httpz.Chunk.parse to read chunks *) (* See Chunk module documentation for details *) )