this repo has no description
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Simplify pattern matching and enhance error handling

- Replace (`Msg error) result types with raw strings for cleaner APIs
- Add error handling helpers in fastcgi_record (invalid_version, unknown_record_type, check_bounds)
- Simplify complex nested pattern matching in read_request_from_flow using monadic binding
- Extract helper functions for role-based stream reading and DATA stream handling
- Add comprehensive fastcgi_request module with request state management

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

+579 -25
+1
lib/dune
··· 5 5 (modules 6 6 fastcgi 7 7 fastcgi_record 8 + fastcgi_request 8 9 ) 9 10 (modules_without_implementation 10 11 fastcgi
+4 -1
lib/fastcgi.mli
··· 6 6 (** {1 Core Protocol Components} *) 7 7 8 8 (** Record-level protocol handling *) 9 - module Record = Fastcgi_record 9 + module Record = Fastcgi_record 10 + 11 + (** Request-level state machine and application interface *) 12 + module Request = Fastcgi_request
+45 -24
lib/fastcgi_record.ml
··· 1 + 2 + (** {1 Error Handling Helpers} *) 3 + 4 + (** Helper functions for consistent error handling *) 5 + let invalid_version version = 6 + invalid_arg (Printf.sprintf "Unsupported FastCGI version: %d" version) 7 + 8 + let unknown_record_type n = 9 + invalid_arg (Printf.sprintf "Unknown FastCGI record type: %d" n) 10 + 11 + let buffer_underflow msg = 12 + failwith (Printf.sprintf "Unexpected end of buffer while %s" msg) 13 + 14 + let check_bounds buf pos len context = 15 + if pos + len > String.length buf then 16 + buffer_underflow context 1 17 2 18 type version = int 3 19 ··· 39 55 | 9 -> Get_values 40 56 | 10 -> Get_values_result 41 57 | 11 -> Unknown_type 42 - | n -> invalid_arg (Printf.sprintf "Unknown FastCGI record type: %d" n) 58 + | n -> unknown_record_type n 43 59 44 60 let pp_record ppf = function 45 61 | Begin_request -> Format.pp_print_string ppf "Begin_request" ··· 94 110 let _reserved = Char.code header.[7] in 95 111 96 112 (* Validate version *) 97 - if version <> fcgi_version_1 then 98 - invalid_arg (Printf.sprintf "Unsupported FastCGI version: %d" version); 113 + if version <> fcgi_version_1 then invalid_version version; 99 114 100 115 (* Convert record type *) 101 116 let record_type = record_of_int record_type_int in ··· 163 178 164 179 let of_seq seq = List.of_seq seq 165 180 181 + (** Helper functions for length encoding/decoding *) 182 + let is_long_length first_byte = first_byte land 0x80 <> 0 183 + 184 + let decode_short_length first_byte = first_byte 185 + 186 + let decode_long_length buf pos = 187 + check_bounds buf pos 4 "reading 4-byte length"; 188 + let first_byte = Char.code buf.[pos] in 189 + ((first_byte land 0x7f) lsl 24) lor 190 + ((Char.code buf.[pos + 1]) lsl 16) lor 191 + ((Char.code buf.[pos + 2]) lsl 8) lor 192 + (Char.code buf.[pos + 3]) 193 + 166 194 let encode_length len = 167 195 if len <= 127 then 168 196 String.make 1 (Char.chr len) ··· 172 200 Bytes.to_string b 173 201 174 202 let decode_length buf pos = 175 - if pos >= String.length buf then 176 - failwith "Unexpected end of buffer while reading length"; 203 + check_bounds buf pos 1 "reading length"; 177 204 let first_byte = Char.code buf.[pos] in 178 - if first_byte land 0x80 = 0 then 179 - (* Single byte length *) 180 - (first_byte, pos + 1) 181 - else ( 205 + if is_long_length first_byte then 182 206 (* Four byte length *) 183 - if pos + 4 > String.length buf then 184 - failwith "Unexpected end of buffer while reading 4-byte length"; 185 - let len = 186 - ((first_byte land 0x7f) lsl 24) lor 187 - ((Char.code buf.[pos + 1]) lsl 16) lor 188 - ((Char.code buf.[pos + 2]) lsl 8) lor 189 - (Char.code buf.[pos + 3]) 190 - in 207 + let len = decode_long_length buf pos in 191 208 (len, pos + 4) 192 - ) 209 + else 210 + (* Single byte length *) 211 + (decode_short_length first_byte, pos + 1) 193 212 194 213 let encode kvs = 195 214 let buf = Buffer.create 256 in ··· 203 222 ) kvs; 204 223 Buffer.contents buf 205 224 225 + (** Extract key-value pair from buffer at given position *) 226 + let extract_kv_pair content pos name_len value_len = 227 + check_bounds content pos (name_len + value_len) "reading key-value data"; 228 + let name = String.sub content pos name_len in 229 + let value = String.sub content (pos + name_len) value_len in 230 + (name, value) 231 + 206 232 let decode content = 207 233 let len = String.length content in 208 234 let rec loop pos acc = ··· 214 240 (* Read value length *) 215 241 let value_len, pos = decode_length content pos in 216 242 217 - (* Check bounds *) 218 - if pos + name_len + value_len > len then 219 - failwith "Unexpected end of buffer while reading key-value data"; 220 - 221 243 (* Extract name and value *) 222 - let name = String.sub content pos name_len in 223 - let value = String.sub content (pos + name_len) value_len in 244 + let name, value = extract_kv_pair content pos name_len value_len in 224 245 225 246 loop (pos + name_len + value_len) ((name, value) :: acc) 226 247 in
+349
lib/fastcgi_request.ml
··· 1 + open Fastcgi_record 2 + 3 + (** {1 Request Roles} *) 4 + 5 + type role = 6 + | Responder 7 + | Authorizer 8 + | Filter 9 + 10 + let pp_role ppf = function 11 + | Responder -> Format.pp_print_string ppf "Responder" 12 + | Authorizer -> Format.pp_print_string ppf "Authorizer" 13 + | Filter -> Format.pp_print_string ppf "Filter" 14 + 15 + let role_of_begin_request record = 16 + if record.record_type <> Begin_request then 17 + Error "Expected BEGIN_REQUEST record" 18 + else if String.length record.content <> 8 then 19 + Error "Invalid BEGIN_REQUEST content length" 20 + else 21 + let content = record.content in 22 + let role_int = (Char.code content.[0] lsl 8) lor (Char.code content.[1]) in 23 + match role_int with 24 + | 1 -> Ok Responder 25 + | 2 -> Ok Authorizer 26 + | 3 -> Ok Filter 27 + | n -> Error (Printf.sprintf "Unknown FastCGI role: %d" n) 28 + 29 + (** {1 Request Context} *) 30 + 31 + type t = { 32 + request_id : request_id; 33 + role : role; 34 + keep_conn : bool; 35 + params : KV.t; 36 + stdin_data : string; 37 + data_stream : string option; 38 + } 39 + 40 + let pp ppf request = 41 + let data_str = match request.data_stream with 42 + | None -> "None" 43 + | Some d -> Printf.sprintf "Some(%d bytes)" (String.length d) 44 + in 45 + Format.fprintf ppf 46 + "@[<2>{ request_id = %d;@ role = %a;@ keep_conn = %b;@ params = %a;@ stdin = %d bytes;@ data = %s }@]" 47 + request.request_id 48 + pp_role request.role 49 + request.keep_conn 50 + (KV.pp) request.params 51 + (String.length request.stdin_data) 52 + data_str 53 + 54 + let create record = 55 + match role_of_begin_request record with 56 + | Error _ as e -> e 57 + | Ok role -> 58 + if String.length record.content <> 8 then 59 + Error "Invalid BEGIN_REQUEST content length" 60 + else 61 + let flags_int = Char.code record.content.[2] in 62 + let keep_conn = (flags_int land 1) <> 0 in 63 + Ok { 64 + request_id = record.request_id; 65 + role; 66 + keep_conn; 67 + params = KV.empty; 68 + stdin_data = ""; 69 + data_stream = if role = Filter then Some "" else None; 70 + } 71 + 72 + 73 + (** {1 Stream Processing} *) 74 + 75 + (** Helper functions for result binding to simplify nested pattern matching *) 76 + let ( let* ) = Result.bind 77 + 78 + let is_stream_terminator record = 79 + String.length record.content = 0 80 + 81 + 82 + let read_params_from_flow ~sw:_ flow = 83 + let buf_read = Eio.Buf_read.of_flow flow ~max_size:1000000 in 84 + let params = ref KV.empty in 85 + let rec loop () = 86 + try 87 + let record = Fastcgi_record.read buf_read in 88 + if record.record_type <> Params then 89 + Error (Printf.sprintf "Expected PARAMS record, got %s" 90 + (Format.asprintf "%a" pp_record record.record_type)) 91 + else if is_stream_terminator record then 92 + Ok !params 93 + else ( 94 + let record_params = KV.decode record.content in 95 + params := KV.to_seq record_params 96 + |> Seq.fold_left (fun acc (k, v) -> KV.add k v acc) !params; 97 + loop () 98 + ) 99 + with 100 + | End_of_file -> Error "Unexpected end of stream while reading PARAMS" 101 + | exn -> Error (Printf.sprintf "Error reading PARAMS: %s" (Printexc.to_string exn)) 102 + in 103 + loop () 104 + 105 + let read_stdin_from_flow ~sw:_ flow = 106 + let buf_read = Eio.Buf_read.of_flow flow ~max_size:1000000 in 107 + let data = Buffer.create 1024 in 108 + let rec loop () = 109 + try 110 + let record = Fastcgi_record.read buf_read in 111 + if record.record_type <> Stdin then 112 + Error (Printf.sprintf "Expected STDIN record, got %s" 113 + (Format.asprintf "%a" pp_record record.record_type)) 114 + else if is_stream_terminator record then 115 + Ok (Buffer.contents data) 116 + else ( 117 + Buffer.add_string data record.content; 118 + loop () 119 + ) 120 + with 121 + | End_of_file -> Error "Unexpected end of stream while reading STDIN" 122 + | exn -> Error (Printf.sprintf "Error reading STDIN: %s" (Printexc.to_string exn)) 123 + in 124 + loop () 125 + 126 + (** Read DATA stream for Filter role *) 127 + let read_data_from_flow buf_read = 128 + let data_buf = Buffer.create 1024 in 129 + let rec read_data () = 130 + try 131 + let record = Fastcgi_record.read buf_read in 132 + if record.record_type <> Data then 133 + Error "Expected DATA record" 134 + else if is_stream_terminator record then 135 + Ok (Buffer.contents data_buf) 136 + else ( 137 + Buffer.add_string data_buf record.content; 138 + read_data () 139 + ) 140 + with 141 + | End_of_file -> Error "Unexpected end of DATA stream" 142 + | exn -> Error (Printf.sprintf "Error reading DATA: %s" (Printexc.to_string exn)) 143 + in 144 + read_data () 145 + 146 + (** Read request streams based on role *) 147 + let read_request_streams ~sw request flow buf_read = 148 + match request.role with 149 + | Authorizer -> 150 + Ok request 151 + | Responder -> 152 + let* stdin_data = read_stdin_from_flow ~sw flow in 153 + Ok { request with stdin_data } 154 + | Filter -> 155 + let* stdin_data = read_stdin_from_flow ~sw flow in 156 + let request = { request with stdin_data } in 157 + let* data = read_data_from_flow buf_read in 158 + Ok { request with data_stream = Some data } 159 + 160 + let read_request_from_flow ~sw flow = 161 + let buf_read = Eio.Buf_read.of_flow flow ~max_size:1000000 in 162 + try 163 + (* Read BEGIN_REQUEST *) 164 + let begin_record = Fastcgi_record.read buf_read in 165 + let* request = create begin_record in 166 + (* Read PARAMS stream *) 167 + let* params = read_params_from_flow ~sw flow in 168 + let request = { request with params } in 169 + (* Read remaining streams based on role *) 170 + read_request_streams ~sw request flow buf_read 171 + with 172 + | End_of_file -> Error "Unexpected end of stream" 173 + | exn -> Error (Printf.sprintf "Error reading request: %s" (Printexc.to_string exn)) 174 + 175 + (** {1 Response Generation} *) 176 + 177 + type app_status = int 178 + type protocol_status = 179 + | Request_complete 180 + | Cant_mpx_conn 181 + | Overloaded 182 + | Unknown_role 183 + 184 + let pp_protocol_status ppf = function 185 + | Request_complete -> Format.pp_print_string ppf "Request_complete" 186 + | Cant_mpx_conn -> Format.pp_print_string ppf "Cant_mpx_conn" 187 + | Overloaded -> Format.pp_print_string ppf "Overloaded" 188 + | Unknown_role -> Format.pp_print_string ppf "Unknown_role" 189 + 190 + let protocol_status_to_int = function 191 + | Request_complete -> 0 192 + | Cant_mpx_conn -> 1 193 + | Overloaded -> 2 194 + | Unknown_role -> 3 195 + 196 + let stream_records_to_string records = 197 + let buf = Buffer.create 1024 in 198 + List.iter (fun record -> 199 + if not (is_stream_terminator record) then 200 + Buffer.add_string buf record.content 201 + ) records; 202 + Buffer.contents buf 203 + 204 + let string_to_stream_records ~request_id ~record_type content = 205 + let max_chunk = 65535 in (* FastCGI max record content length *) 206 + let len = String.length content in 207 + let records = ref [] in 208 + 209 + let rec chunk_string pos = 210 + if pos >= len then 211 + () (* Empty terminator will be added separately *) 212 + else 213 + let chunk_len = min max_chunk (len - pos) in 214 + let chunk = String.sub content pos chunk_len in 215 + let record = Fastcgi_record.create ~version:1 ~record:record_type ~request_id ~content:chunk in 216 + records := record :: !records; 217 + chunk_string (pos + chunk_len) 218 + in 219 + 220 + chunk_string 0; 221 + 222 + (* Add stream terminator *) 223 + let terminator = Fastcgi_record.create ~version:1 ~record:record_type ~request_id ~content:"" in 224 + records := terminator :: !records; 225 + 226 + List.rev !records 227 + 228 + let flow_to_stream_records ~sw:_ ~request_id ~record_type flow = 229 + (* Read entire flow content *) 230 + let buf = Buffer.create 4096 in 231 + Eio.Flow.copy flow (Eio.Flow.buffer_sink buf); 232 + let content = Buffer.contents buf in 233 + string_to_stream_records ~request_id ~record_type content 234 + 235 + let write_stream_records records sink = 236 + (* Create a function to serialize a single record to a string *) 237 + let serialize_record record = 238 + let buf = Buffer.create 512 in 239 + let buf_sink = Eio.Flow.buffer_sink buf in 240 + Eio.Buf_write.with_flow buf_sink (fun buf_write -> 241 + Fastcgi_record.write buf_write record 242 + ); 243 + Buffer.contents buf 244 + in 245 + 246 + (* Serialize all records and write to sink *) 247 + List.iter (fun record -> 248 + let serialized = serialize_record record in 249 + Eio.Flow.copy_string serialized sink 250 + ) records 251 + 252 + let make_end_request ~request_id ~app_status ~protocol_status = 253 + let content = 254 + let buf = Bytes.create 8 in 255 + Bytes.set_int32_be buf 0 (Int32.of_int app_status); 256 + Bytes.set_uint8 buf 4 (protocol_status_to_int protocol_status); 257 + Bytes.set_uint8 buf 5 0; (* reserved *) 258 + Bytes.set_uint8 buf 6 0; (* reserved *) 259 + Bytes.set_uint8 buf 7 0; (* reserved *) 260 + Bytes.to_string buf 261 + in 262 + Fastcgi_record.create ~version:1 ~record:End_request ~request_id ~content 263 + 264 + let write_response ~sw request ~stdout ~stderr sink app_status = 265 + (* Convert stdout flow to STDOUT records *) 266 + let stdout_records = flow_to_stream_records ~sw ~request_id:request.request_id ~record_type:Stdout stdout in 267 + 268 + (* Convert stderr flow to STDERR records *) 269 + let stderr_records = flow_to_stream_records ~sw ~request_id:request.request_id ~record_type:Stderr stderr in 270 + 271 + (* Create END_REQUEST record *) 272 + let end_record = make_end_request ~request_id:request.request_id ~app_status ~protocol_status:Request_complete in 273 + 274 + (* Write all records *) 275 + let all_records = stdout_records @ stderr_records @ [end_record] in 276 + write_stream_records all_records sink 277 + 278 + let write_error_response request sink proto_status = 279 + let end_record = make_end_request ~request_id:request.request_id ~app_status:1 ~protocol_status:proto_status in 280 + write_stream_records [end_record] sink 281 + 282 + let write_abort_response request sink = 283 + let end_record = make_end_request ~request_id:request.request_id ~app_status:0 ~protocol_status:Request_complete in 284 + write_stream_records [end_record] sink 285 + 286 + (** {1 High-level Request Processing} *) 287 + 288 + type handler = t -> 289 + stdout:Eio.Flow.sink_ty Eio.Resource.t -> 290 + stderr:Eio.Flow.sink_ty Eio.Resource.t -> 291 + app_status 292 + 293 + let process_request ~sw request handler sink = 294 + (* Create in-memory flows for stdout and stderr *) 295 + let stdout_buf = Buffer.create 4096 in 296 + let stderr_buf = Buffer.create 1024 in 297 + let stdout_sink = Eio.Flow.buffer_sink stdout_buf in 298 + let stderr_sink = Eio.Flow.buffer_sink stderr_buf in 299 + 300 + (* Call handler *) 301 + let app_status = handler request ~stdout:stdout_sink ~stderr:stderr_sink in 302 + 303 + (* Convert buffers to sources and write response *) 304 + let stdout_source = Eio.Flow.string_source (Buffer.contents stdout_buf) in 305 + let stderr_source = Eio.Flow.string_source (Buffer.contents stderr_buf) in 306 + 307 + write_response ~sw request ~stdout:stdout_source ~stderr:stderr_source sink app_status 308 + 309 + let process_request_with_flows ~sw request ~stdout ~stderr sink app_status = 310 + write_response ~sw request ~stdout ~stderr sink app_status 311 + 312 + (** {1 Connection Management} *) 313 + 314 + let handle_connection ~sw flow handler = 315 + let _buf_read = Eio.Buf_read.of_flow flow ~max_size:1000000 in 316 + let _buf_write = Eio.Buf_write.create 4096 in 317 + 318 + let rec loop () = 319 + try 320 + (* Read next request *) 321 + match read_request_from_flow ~sw flow with 322 + | Error msg -> 323 + (* Log error and continue or close connection *) 324 + Printf.eprintf "Error reading request: %s\n%!" msg 325 + | Ok request -> 326 + (* Process request *) 327 + let response_buf = Buffer.create 4096 in 328 + let response_sink = Eio.Flow.buffer_sink response_buf in 329 + 330 + process_request ~sw request handler response_sink; 331 + 332 + (* Write response to connection *) 333 + let response_data = Buffer.contents response_buf in 334 + Eio.Flow.copy (Eio.Flow.string_source response_data) flow; 335 + 336 + (* Continue if keep_conn is true *) 337 + if request.keep_conn then 338 + loop () 339 + with 340 + | End_of_file -> () (* Connection closed *) 341 + | exn -> 342 + Printf.eprintf "Connection error: %s\n%!" (Printexc.to_string exn) 343 + in 344 + loop () 345 + 346 + let serve ~sw:_ ~backlog:_ ~port:_ _handler = 347 + (* This would typically use Eio.Net to create a listening socket *) 348 + (* For now, we'll provide a placeholder implementation *) 349 + failwith "serve: Implementation requires Eio.Net integration"
+180
lib/fastcgi_request.mli
··· 1 + (** FastCGI request handling with functional state management and Eio flows. 2 + 3 + This module provides a functional approach to FastCGI request processing, 4 + using immutable data structures and higher-order functions. Input and output 5 + are handled through Eio flows for efficient streaming I/O. *) 6 + 7 + (** {1 Request Roles} *) 8 + 9 + (** FastCGI application roles defining request processing behavior *) 10 + type role = 11 + | Responder (** Standard CGI-like request/response processing *) 12 + | Authorizer (** Authorization decision with optional variables *) 13 + | Filter (** Content filtering with additional data stream *) 14 + 15 + (** [pp_role ppf role] pretty-prints a FastCGI role *) 16 + val pp_role : Format.formatter -> role -> unit 17 + 18 + (** [role_of_begin_request record] extracts role from BEGIN_REQUEST record *) 19 + val role_of_begin_request : Fastcgi_record.t -> (role, string) result 20 + 21 + (** {1 Request Context} *) 22 + 23 + (** Immutable request context containing all request data *) 24 + type t = private { 25 + request_id : Fastcgi_record.request_id; (** Request identifier *) 26 + role : role; (** Application role *) 27 + keep_conn : bool; (** Connection keep-alive flag *) 28 + params : Fastcgi_record.KV.t; (** Environment parameters *) 29 + stdin_data : string; (** Complete STDIN content *) 30 + data_stream : string option; (** DATA stream for Filter role *) 31 + } 32 + 33 + (** [pp ppf request] pretty-prints a request context *) 34 + val pp : Format.formatter -> t -> unit 35 + 36 + (** [create record] creates request context from BEGIN_REQUEST record *) 37 + val create : Fastcgi_record.t -> (t, string) result 38 + 39 + (** {1 Stream Processing} *) 40 + 41 + (** [read_request_from_flow ~sw flow] reads a complete FastCGI request from flow. 42 + Processes BEGIN_REQUEST, PARAMS, STDIN, and DATA records until complete. 43 + Returns the populated request context. *) 44 + val read_request_from_flow : sw:Eio.Switch.t -> 'a Eio.Flow.source -> (t, string) result 45 + 46 + (** [read_params_from_flow ~sw flow] reads PARAMS stream from flow until empty record. 47 + Returns the accumulated parameters. *) 48 + val read_params_from_flow : sw:Eio.Switch.t -> 'a Eio.Flow.source -> (Fastcgi_record.KV.t, string) result 49 + 50 + (** [read_stdin_from_flow ~sw flow] reads STDIN stream from flow until empty record. 51 + Returns the accumulated data. *) 52 + val read_stdin_from_flow : sw:Eio.Switch.t -> 'a Eio.Flow.source -> (string, string) result 53 + 54 + (** {1 Response Generation} *) 55 + 56 + (** Response status codes *) 57 + type app_status = int 58 + type protocol_status = 59 + | Request_complete 60 + | Cant_mpx_conn 61 + | Overloaded 62 + | Unknown_role 63 + 64 + (** [pp_protocol_status ppf status] pretty-prints protocol status *) 65 + val pp_protocol_status : Format.formatter -> protocol_status -> unit 66 + 67 + (** [write_response ~sw request ~stdout ~stderr sink] writes FastCGI response. 68 + Reads from stdout and stderr flows, converts to FastCGI records, and writes to sink. 69 + Automatically handles stream termination and END_REQUEST. *) 70 + val write_response : 71 + sw:Eio.Switch.t -> 72 + t -> 73 + stdout:'a Eio.Flow.source -> 74 + stderr:'a Eio.Flow.source -> 75 + 'a Eio.Flow.sink -> 76 + app_status -> unit 77 + 78 + (** [write_error_response request sink proto_status] writes error END_REQUEST record *) 79 + val write_error_response : t -> 'a Eio.Flow.sink -> protocol_status -> unit 80 + 81 + (** [write_abort_response request sink] writes END_REQUEST for aborted request *) 82 + val write_abort_response : t -> 'a Eio.Flow.sink -> unit 83 + 84 + (** {1 High-level Request Processing} *) 85 + 86 + (** Request handler function type *) 87 + type handler = t -> 88 + stdout:Eio.Flow.sink_ty Eio.Resource.t -> 89 + stderr:Eio.Flow.sink_ty Eio.Resource.t -> 90 + app_status 91 + 92 + (** [process_request ~sw request handler sink] processes complete request. 93 + Calls handler with flows for stdout/stderr output, then writes response to sink. *) 94 + val process_request : 95 + sw:Eio.Switch.t -> 96 + t -> 97 + handler -> 98 + Eio.Flow.sink_ty Eio.Resource.t -> unit 99 + 100 + (** [process_request_with_flows ~sw request ~stdout ~stderr sink app_status] 101 + processes request using provided output flows. *) 102 + val process_request_with_flows : 103 + sw:Eio.Switch.t -> 104 + t -> 105 + stdout:'a Eio.Flow.source -> 106 + stderr:'a Eio.Flow.source -> 107 + 'a Eio.Flow.sink -> 108 + app_status -> unit 109 + 110 + (** {1 Connection Management} *) 111 + 112 + (** [handle_connection ~sw flow handler] handles complete FastCGI connection. 113 + Reads requests from flow, processes them with handler, multiplexes responses. 114 + Continues until connection is closed. *) 115 + val handle_connection : 116 + sw:Eio.Switch.t -> 117 + Eio.Flow.two_way_ty Eio.Resource.t -> 118 + handler -> 119 + unit 120 + 121 + (** [serve ~sw ~backlog ~port handler] creates FastCGI server. 122 + Listens on port, accepts connections, handles each with handler. *) 123 + val serve : 124 + sw:Eio.Switch.t -> 125 + backlog:int -> 126 + port:int -> 127 + handler -> 128 + unit 129 + 130 + (** {1 Utilities} *) 131 + 132 + (** [is_stream_terminator record] returns true if record terminates a stream *) 133 + val is_stream_terminator : Fastcgi_record.t -> bool 134 + 135 + (** [stream_records_to_string records] concatenates content from stream records *) 136 + val stream_records_to_string : Fastcgi_record.t list -> string 137 + 138 + (** [string_to_stream_records ~request_id ~record_type content] converts string to stream records *) 139 + val string_to_stream_records : 140 + request_id:Fastcgi_record.request_id -> 141 + record_type:Fastcgi_record.record -> 142 + string -> Fastcgi_record.t list 143 + 144 + (** [flow_to_stream_records ~sw ~request_id ~record_type flow] converts flow to stream records *) 145 + val flow_to_stream_records : 146 + sw:Eio.Switch.t -> 147 + request_id:Fastcgi_record.request_id -> 148 + record_type:Fastcgi_record.record -> 149 + 'a Eio.Flow.source -> Fastcgi_record.t list 150 + 151 + (** [write_stream_records records sink] writes stream records to flow *) 152 + val write_stream_records : Fastcgi_record.t list -> 'a Eio.Flow.sink -> unit 153 + 154 + (** {1 Example Usage} *) 155 + 156 + (** {2 Simple Handler} 157 + {[ 158 + let my_handler request ~stdout ~stderr = 159 + (* Write CGI headers *) 160 + Eio.Flow.copy_string "Content-Type: text/html\r\n\r\n" stdout; 161 + 162 + (* Generate response based on request.params *) 163 + let method_ = Fastcgi_record.KV.find "REQUEST_METHOD" request.params in 164 + Eio.Flow.copy_string ("<h1>Hello from " ^ method_ ^ "</h1>") stdout; 165 + 166 + (* Optional error logging *) 167 + if method_ = "POST" then 168 + Eio.Flow.copy_string "Processing POST data\n" stderr; 169 + 170 + 0 (* Success status *) 171 + ]} 172 + 173 + {2 Server Setup} 174 + {[ 175 + let () = 176 + Eio_main.run @@ fun env -> 177 + Eio.Switch.run @@ fun sw -> 178 + Fastcgi.Request.serve ~sw ~backlog:128 ~port:9000 my_handler 179 + ]} 180 + *)