ocaml http/1, http/2 and websocket client and server library

docs: document request body handling and HTTP/2 large body support

- Add Request Body Handling section to request-lifecycle.md
- Document lazy body reading with body_reader
- Explain streaming for large bodies with read_stream()
- Document max_body_size configuration
- Note HTTP/2 flow control is automatic

- Update http2.md client documentation
- Document h2c (cleartext HTTP/2) support
- Add POST request examples
- Document large body handling with flow control
- Fix outdated 'no h2c support' note

- Update basic-requests.md
- Add section on large request body support
- Note automatic HTTP/2 flow control handling

- Add cross-reference in responses.md to body handling

Changed files
+170 -9
docs
+14
docs/client/basic-requests.md
··· 53 53 ~body:{|{"name": "Bob"}|} 54 54 ``` 55 55 56 + ### Large Request Bodies 57 + 58 + Both HTTP/1.1 and HTTP/2 handle large request bodies correctly. No special configuration is needed for multi-megabyte uploads: 59 + 60 + ```ocaml 61 + (* Upload a large JSON payload *) 62 + let large_body = generate_report () (* Could be several MB *) 63 + match Client.request_post client url ~body:large_body with 64 + | Ok resp -> Printf.printf "Uploaded successfully\n" 65 + | Error _ -> () 66 + ``` 67 + 68 + For HTTP/2, flow control is handled automatically—the client respects the server's flow control window and waits for `WINDOW_UPDATE` frames as needed. 69 + 56 70 ## Configuration 57 71 58 72 Customize client behavior with config modifiers:
+61 -6
docs/client/http2.md
··· 1 1 # HTTP/2 Client 2 2 3 - HCS supports HTTP/2 with automatic protocol selection via ALPN. 3 + HCS supports HTTP/2 over TLS (h2) and cleartext (h2c). 4 4 5 5 ## Forcing HTTP/2 6 6 ··· 14 14 15 15 This configures: 16 16 - Preferred protocol set to HTTP/2 17 - - ALPN set to negotiate `h2` 17 + - ALPN set to negotiate `h2` for HTTPS connections 18 18 19 19 ## Protocol Selection 20 20 ··· 22 22 23 23 1. **Explicit configuration**: `with_http2` or `with_http11` 24 24 2. **ALPN negotiation**: For HTTPS, if ALPN protocols include `h2` 25 - 3. **Scheme**: HTTP/2 requires HTTPS (no h2c support currently) 25 + 3. **Cleartext h2c**: For HTTP URLs with `preferred_protocol = Some HTTP_2` 26 26 27 27 ```ocaml 28 28 (* Force HTTP/1.1 *) ··· 30 30 Client.default_config 31 31 |> Client.with_http11 32 32 33 - (* Force HTTP/2 *) 33 + (* Force HTTP/2 (h2 for HTTPS, h2c for HTTP) *) 34 34 let h2_config = 35 35 Client.default_config 36 36 |> Client.with_http2 ··· 39 39 let auto_config = Client.default_config 40 40 ``` 41 41 42 + ## HTTP/2 Cleartext (h2c) 43 + 44 + For local development or internal services, use h2c (HTTP/2 without TLS): 45 + 46 + ```ocaml 47 + let config = 48 + { Client.default_config with 49 + preferred_protocol = Some Client.HTTP_2 } 50 + 51 + let client = Client.create ~sw ~net ~clock ~config () in 52 + 53 + (* This uses h2c *) 54 + match Client.request client "http://localhost:8080/api" with 55 + | Ok resp -> Printf.printf "Status: %d\n" resp.status 56 + | Error _ -> () 57 + ``` 58 + 42 59 ## Checking Protocol Used 43 60 44 61 ```ocaml ··· 50 67 | Error _ -> () 51 68 ``` 52 69 70 + ## POST Requests 71 + 72 + POST requests work the same as HTTP/1.1: 73 + 74 + ```ocaml 75 + let config = Client.default_config |> Client.with_http2 in 76 + let client = Client.create ~sw ~net ~clock ~config () in 77 + 78 + let body = {|{"name": "Alice", "email": "alice@example.com"}|} in 79 + match Client.request_post client "https://api.example.com/users" ~body with 80 + | Ok resp -> Printf.printf "Created: %s\n" resp.body 81 + | Error err -> handle_error err 82 + ``` 83 + 84 + ### Large Request Bodies 85 + 86 + HTTP/2 handles large request bodies (multi-megabyte uploads) correctly. Flow control is managed automatically—the client sends data up to the flow control window, waits for `WINDOW_UPDATE` frames from the server, then continues. 87 + 88 + ```ocaml 89 + (* Upload a large file *) 90 + let large_body = load_file "data.json" in (* Could be several MB *) 91 + match Client.request_post client url ~body:large_body with 92 + | Ok resp -> Printf.printf "Uploaded %d bytes\n" (String.length large_body) 93 + | Error _ -> () 94 + ``` 95 + 96 + No special configuration is needed. The default flow control window (64KB) works for any body size. 97 + 53 98 ## HTTP/2 Benefits 54 99 55 100 When using HTTP/2: ··· 57 102 - **Multiplexing**: Multiple requests share one connection 58 103 - **Header compression**: HPACK reduces header overhead 59 104 - **Binary framing**: More efficient than HTTP/1.1 text format 105 + - **Flow control**: Automatic back-pressure for large transfers 60 106 61 107 ## Connection Reuse 62 108 ··· 133 179 | Ok resp -> process_response resp 134 180 ``` 135 181 136 - ## Streaming (Not Yet Supported) 182 + ## Response Handling 183 + 184 + Responses are buffered in memory. For large responses, configure a size limit: 185 + 186 + ```ocaml 187 + let config = 188 + Client.default_config 189 + |> Client.with_http2 190 + |> Client.with_max_response_body (Int64.of_int (50 * 1024 * 1024)) (* 50MB *) 191 + ``` 137 192 138 - Currently, responses are buffered in memory. Streaming support for HTTP/2 is planned for a future release. 193 + Streaming response support is planned for a future release.
+91 -3
docs/guides/request-lifecycle.md
··· 31 31 type request = { 32 32 meth : H1.Method.t; (* GET, POST, etc. *) 33 33 target : string; (* Path + query string *) 34 - headers : (string * string) list; 35 - body : string; 36 - version : protocol_version; (* HTTP_1_1 or HTTP_2 *) 34 + headers : H1.Headers.t; 35 + body_reader : body_reader; (* Lazy body reader *) 37 36 } 38 37 ``` 39 38 40 39 The `target` contains the raw request target. For `/users?id=5`, the target is `/users?id=5`. 40 + 41 + ## Request Body Handling 42 + 43 + Request bodies are read lazily—they're only fetched from the network when you access them. This allows efficient handling of requests where the body might not be needed. 44 + 45 + ### Reading the Entire Body 46 + 47 + For most cases, read the full body as a string: 48 + 49 + ```ocaml 50 + let handler _params req = 51 + let body = Request.body req in 52 + (* Process body... *) 53 + Response.ok "Received" 54 + ``` 55 + 56 + Or equivalently: 57 + 58 + ```ocaml 59 + let body = req.body_reader.read () 60 + ``` 61 + 62 + The body is buffered in memory. For very large bodies, consider streaming. 63 + 64 + ### Streaming Large Bodies 65 + 66 + For large uploads (files, bulk data), read the body in chunks to avoid memory pressure: 67 + 68 + ```ocaml 69 + let handler _params req = 70 + let stream = Request.body_reader req in 71 + let rec process () = 72 + match stream.read_stream () with 73 + | None -> () (* End of body *) 74 + | Some chunk -> 75 + (* Process chunk (Cstruct.t) *) 76 + write_to_file chunk; 77 + process () 78 + in 79 + process (); 80 + Response.ok "Upload complete" 81 + ``` 82 + 83 + Each call to `read_stream ()` returns: 84 + - `Some chunk` - A `Cstruct.t` containing the next chunk of data 85 + - `None` - End of body reached 86 + 87 + ### Ignoring the Body 88 + 89 + If you don't need the body (e.g., for GET requests), you can ignore it. For methods that typically have no body (GET, HEAD, OPTIONS), HCS automatically closes the body reader. 90 + 91 + For other methods where you want to explicitly skip the body: 92 + 93 + ```ocaml 94 + let handler _params req = 95 + req.body_reader.close (); 96 + Response.ok "Ignored body" 97 + ``` 98 + 99 + ### Body Size Limits 100 + 101 + Configure `max_body_size` to reject bodies exceeding a threshold: 102 + 103 + ```ocaml 104 + let config = 105 + Server.default_config 106 + |> Server.with_max_body_size (Int64.of_int (10 * 1024 * 1024)) (* 10MB *) 107 + ``` 108 + 109 + If a request body exceeds this limit, HCS returns `413 Request Entity Too Large`. 110 + 111 + ### Large Bodies and HTTP/2 112 + 113 + Both HTTP/1.1 and HTTP/2 support arbitrarily large request bodies. HTTP/2 uses flow control with a default window size of 64KB—HCS handles `WINDOW_UPDATE` frames automatically, so large uploads work without configuration. 114 + 115 + ### Form Data 116 + 117 + For `application/x-www-form-urlencoded` bodies: 118 + 119 + ```ocaml 120 + let handler _params req = 121 + let data = Request.form_data req in 122 + let name = List.assoc_opt "name" data in 123 + (* ... *) 124 + ``` 125 + 126 + ### Multipart Forms 127 + 128 + For file uploads with `multipart/form-data`, see the [Multipart module](../recipes/file-upload.md). 41 129 42 130 ## Three-Layer Plug Architecture 43 131
+4
docs/guides/responses.md
··· 304 304 ``` 305 305 306 306 Or use the `Negotiate` plug for automatic handling. 307 + 308 + ## See Also 309 + 310 + - [Request Body Handling](request-lifecycle.md#request-body-handling) - Reading and streaming request bodies