this repo has no description
2
fork

Configure Feed

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

Refactor to separate Flow operations from protocol parsing

Move all Eio.Flow-related functions from fastcgi_request to the
higher-level fastcgi module. This creates a cleaner separation where
fastcgi_request handles only protocol parsing/serialization using
Buf_read/Buf_write, while fastcgi provides the Flow-based interface
for connection and request handling.

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

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

+235 -239
+1 -1
bin/fcgi_server.ml
··· 11 11 @@ fun flow addr -> 12 12 Eio.traceln "Accepted connection from %a" Eio.Net.Sockaddr.pp addr; 13 13 (* Here you would handle the FastCGI protocol, but for simplicity, we just echo a string. *) 14 - let req = Fastcgi.Request.read_request_from_flow ~sw flow in 14 + let req = Fastcgi.read_request_from_flow ~sw flow in 15 15 match req with 16 16 | Error msg -> 17 17 Eio.traceln "Failed to read request: %s" msg;
-3
lib/dune
··· 7 7 fastcgi_record 8 8 fastcgi_request 9 9 ) 10 - (modules_without_implementation 11 - fastcgi 12 - ) 13 10 )
+102
lib/fastcgi.ml
··· 1 + module Record = Fastcgi_record 2 + 3 + (** Request-level state machine and application interface *) 4 + module Request = Fastcgi_request 5 + 6 + (** Request handler function type *) 7 + type handler = Request.t -> 8 + stdout:Eio.Flow.sink_ty Eio.Resource.t -> 9 + stderr:Eio.Flow.sink_ty Eio.Resource.t -> 10 + Request.app_status 11 + 12 + (** [read_request_from_flow ~sw flow] reads a complete FastCGI request from flow. 13 + Processes BEGIN_REQUEST, PARAMS, STDIN, and DATA records until complete. 14 + Returns the populated request context. *) 15 + let read_request_from_flow ~sw:_ flow = 16 + let buf_read = Eio.Buf_read.of_flow flow ~max_size:1000000 in 17 + Request.read_request buf_read 18 + 19 + (** [write_response ~sw request ~stdout ~stderr sink app_status] writes FastCGI response. 20 + Reads from stdout and stderr flows, converts to FastCGI records, and writes to sink. 21 + Automatically handles stream termination and END_REQUEST. *) 22 + let write_response ~sw:_ request ~stdout ~stderr sink app_status = 23 + (* Read stdout content *) 24 + let stdout_buf = Buffer.create 4096 in 25 + Eio.Flow.copy stdout (Eio.Flow.buffer_sink stdout_buf); 26 + let stdout_content = Buffer.contents stdout_buf in 27 + 28 + (* Read stderr content *) 29 + let stderr_buf = Buffer.create 1024 in 30 + Eio.Flow.copy stderr (Eio.Flow.buffer_sink stderr_buf); 31 + let stderr_content = Buffer.contents stderr_buf in 32 + 33 + (* Write response using Buf_write *) 34 + Eio.Buf_write.with_flow sink (fun buf_write -> 35 + Request.write_stdout_records buf_write request.Request.request_id stdout_content; 36 + Request.write_stderr_records buf_write request.Request.request_id stderr_content; 37 + Request.write_end_request buf_write request.Request.request_id app_status Request.Request_complete 38 + ) 39 + 40 + (** [process_request ~sw request handler sink] processes complete request. 41 + Calls handler with flows for stdout/stderr output, then writes response to sink. *) 42 + let process_request ~sw request handler sink = 43 + (* Create in-memory flows for stdout and stderr *) 44 + let stdout_buf = Buffer.create 4096 in 45 + let stderr_buf = Buffer.create 1024 in 46 + let stdout_sink = Eio.Flow.buffer_sink stdout_buf in 47 + let stderr_sink = Eio.Flow.buffer_sink stderr_buf in 48 + 49 + (* Call handler *) 50 + let app_status = handler request ~stdout:stdout_sink ~stderr:stderr_sink in 51 + 52 + (* Convert buffers to sources and write response *) 53 + let stdout_source = Eio.Flow.string_source (Buffer.contents stdout_buf) in 54 + let stderr_source = Eio.Flow.string_source (Buffer.contents stderr_buf) in 55 + 56 + write_response ~sw request ~stdout:stdout_source ~stderr:stderr_source sink app_status 57 + 58 + (** [process_request_with_flows ~sw request ~stdout ~stderr sink app_status] 59 + processes request using provided output flows. *) 60 + let process_request_with_flows ~sw request ~stdout ~stderr sink app_status = 61 + write_response ~sw request ~stdout ~stderr sink app_status 62 + 63 + (** {1 Connection Management} *) 64 + 65 + (** [handle_connection ~sw flow handler] handles complete FastCGI connection. 66 + Reads requests from flow, processes them with handler, multiplexes responses. 67 + Continues until connection is closed. *) 68 + let handle_connection ~sw flow handler = 69 + let rec loop () = 70 + try 71 + (* Read next request *) 72 + match read_request_from_flow ~sw flow with 73 + | Error msg -> 74 + (* Log error and continue or close connection *) 75 + Printf.eprintf "Error reading request: %s\n%!" msg 76 + | Ok request -> 77 + (* Process request *) 78 + let response_buf = Buffer.create 4096 in 79 + let response_sink = Eio.Flow.buffer_sink response_buf in 80 + 81 + process_request ~sw request handler response_sink; 82 + 83 + (* Write response to connection *) 84 + let response_data = Buffer.contents response_buf in 85 + Eio.Flow.copy (Eio.Flow.string_source response_data) flow; 86 + 87 + (* Continue if keep_conn is true *) 88 + if request.Request.keep_conn then 89 + loop () 90 + with 91 + | End_of_file -> () (* Connection closed *) 92 + | exn -> 93 + Printf.eprintf "Connection error: %s\n%!" (Printexc.to_string exn) 94 + in 95 + loop () 96 + 97 + (** [serve ~sw ~backlog ~port handler] creates FastCGI server. 98 + Listens on port, accepts connections, handles each with handler. *) 99 + let serve ~sw:_ ~backlog:_ ~port:_ _handler = 100 + (* This would typically use Eio.Net to create a listening socket *) 101 + (* For now, we'll provide a placeholder implementation *) 102 + failwith "serve: Implementation requires Eio.Net integration"
+64 -1
lib/fastcgi.mli
··· 3 3 This library provides a complete implementation of the FastCGI protocol 4 4 for building high-performance web applications in OCaml. *) 5 5 6 + 6 7 (** {1 Core Protocol Components} *) 7 8 8 9 (** Record-level protocol handling *) 9 10 module Record = Fastcgi_record 10 11 11 12 (** Request-level state machine and application interface *) 12 - module Request = Fastcgi_request 13 + module Request = Fastcgi_request 14 + 15 + (** {1 High-level Request Processing} *) 16 + 17 + (** Request handler function type *) 18 + type handler = Request.t -> 19 + stdout:Eio.Flow.sink_ty Eio.Resource.t -> 20 + stderr:Eio.Flow.sink_ty Eio.Resource.t -> 21 + Request.app_status 22 + 23 + (** [read_request_from_flow ~sw flow] reads a complete FastCGI request from flow. 24 + Processes BEGIN_REQUEST, PARAMS, STDIN, and DATA records until complete. 25 + Returns the populated request context. *) 26 + val read_request_from_flow : sw:Eio.Switch.t -> 'a Eio.Flow.source -> (Request.t, string) result 27 + 28 + (** [write_response ~sw request ~stdout ~stderr sink app_status] writes FastCGI response. 29 + Reads from stdout and stderr flows, converts to FastCGI records, and writes to sink. 30 + Automatically handles stream termination and END_REQUEST. *) 31 + val write_response : 32 + sw:Eio.Switch.t -> 33 + Request.t -> 34 + stdout:'a Eio.Flow.source -> 35 + stderr:'a Eio.Flow.source -> 36 + 'a Eio.Flow.sink -> 37 + Request.app_status -> unit 38 + 39 + (** [process_request ~sw request handler sink] processes complete request. 40 + Calls handler with flows for stdout/stderr output, then writes response to sink. *) 41 + val process_request : 42 + sw:Eio.Switch.t -> 43 + Request.t -> 44 + handler -> 45 + Eio.Flow.sink_ty Eio.Resource.t -> unit 46 + 47 + (** [process_request_with_flows ~sw request ~stdout ~stderr sink app_status] 48 + processes request using provided output flows. *) 49 + val process_request_with_flows : 50 + sw:Eio.Switch.t -> 51 + Request.t -> 52 + stdout:'a Eio.Flow.source -> 53 + stderr:'a Eio.Flow.source -> 54 + 'a Eio.Flow.sink -> 55 + Request.app_status -> unit 56 + 57 + (** {1 Connection Management} *) 58 + 59 + (** [handle_connection ~sw flow handler] handles complete FastCGI connection. 60 + Reads requests from flow, processes them with handler, multiplexes responses. 61 + Continues until connection is closed. *) 62 + val handle_connection : 63 + sw:Eio.Switch.t -> 64 + Eio.Flow.two_way_ty Eio.Resource.t -> 65 + handler -> 66 + unit 67 + 68 + (** [serve ~sw ~backlog ~port handler] creates FastCGI server. 69 + Listens on port, accepts connections, handles each with handler. *) 70 + val serve : 71 + sw:Eio.Switch.t -> 72 + backlog:int -> 73 + port:int -> 74 + handler -> 75 + unit
+47 -130
lib/fastcgi_request.ml
··· 79 79 String.length record.content = 0 80 80 81 81 82 - let read_params_from_flow ~sw:_ buf_read = 82 + let read_params buf_read = 83 83 Printf.eprintf "[DEBUG] read_params_from_flow: Starting\n%!"; 84 84 let params = ref KV.empty in 85 85 let rec loop () = ··· 114 114 in 115 115 loop () 116 116 117 - let read_stdin_from_flow ~sw:_ buf_read = 117 + let read_stdin buf_read = 118 118 Printf.eprintf "[DEBUG] read_stdin_from_flow: Starting\n%!"; 119 119 let data = Buffer.create 1024 in 120 120 let rec loop () = ··· 148 148 loop () 149 149 150 150 (** Read DATA stream for Filter role *) 151 - let read_data_from_flow buf_read = 151 + let read_data buf_read = 152 152 let data_buf = Buffer.create 1024 in 153 153 let rec read_data () = 154 154 try ··· 168 168 read_data () 169 169 170 170 (** Read request streams based on role *) 171 - let read_request_streams ~sw request buf_read = 171 + let read_request_streams request buf_read = 172 172 Printf.eprintf "[DEBUG] read_request_streams: Processing role=%s\n%!" 173 173 (Format.asprintf "%a" pp_role request.role); 174 174 match request.role with ··· 177 177 Ok request 178 178 | Responder -> 179 179 Printf.eprintf "[DEBUG] read_request_streams: Responder role, reading STDIN\n%!"; 180 - let* stdin_data = read_stdin_from_flow ~sw buf_read in 180 + let* stdin_data = read_stdin buf_read in 181 181 Printf.eprintf "[DEBUG] read_request_streams: Got STDIN data, %d bytes\n%!" (String.length stdin_data); 182 182 Ok { request with stdin_data } 183 183 | Filter -> 184 184 Printf.eprintf "[DEBUG] read_request_streams: Filter role, reading STDIN and DATA\n%!"; 185 - let* stdin_data = read_stdin_from_flow ~sw buf_read in 185 + let* stdin_data = read_stdin buf_read in 186 186 Printf.eprintf "[DEBUG] read_request_streams: Got STDIN data, %d bytes\n%!" (String.length stdin_data); 187 187 let request = { request with stdin_data } in 188 - let* data = read_data_from_flow buf_read in 188 + let* data = read_data buf_read in 189 189 Printf.eprintf "[DEBUG] read_request_streams: Got DATA stream, %d bytes\n%!" (String.length data); 190 190 Ok { request with data_stream = Some data } 191 191 192 - let read_request_from_flow ~sw flow = 193 - Printf.eprintf "[DEBUG] read_request_from_flow: Starting\n%!"; 194 - let buf_read = Eio.Buf_read.of_flow flow ~max_size:1000000 in 192 + let read_request buf_read = 193 + Printf.eprintf "[DEBUG] read_request: Starting\n%!"; 195 194 try 196 195 (* Read BEGIN_REQUEST *) 197 - Printf.eprintf "[DEBUG] read_request_from_flow: Reading BEGIN_REQUEST record\n%!"; 196 + Printf.eprintf "[DEBUG] read_request: Reading BEGIN_REQUEST record\n%!"; 198 197 let begin_record = Fastcgi_record.read buf_read in 199 - Printf.eprintf "[DEBUG] read_request_from_flow: Got BEGIN_REQUEST record: %s\n%!" 198 + Printf.eprintf "[DEBUG] read_request: Got BEGIN_REQUEST record: %s\n%!" 200 199 (Format.asprintf "%a" (Fastcgi_record.pp ~max_content_len:50) begin_record); 201 200 let* request = create begin_record in 202 - Printf.eprintf "[DEBUG] read_request_from_flow: Created request with role=%s, id=%d\n%!" 201 + Printf.eprintf "[DEBUG] read_request: Created request with role=%s, id=%d\n%!" 203 202 (Format.asprintf "%a" pp_role request.role) request.request_id; 204 203 (* Read PARAMS stream *) 205 - Printf.eprintf "[DEBUG] read_request_from_flow: Reading PARAMS stream\n%!"; 206 - let* params = read_params_from_flow ~sw buf_read in 207 - Printf.eprintf "[DEBUG] read_request_from_flow: Got %d params\n%!" (Fastcgi_record.KV.cardinal params); 204 + Printf.eprintf "[DEBUG] read_request: Reading PARAMS stream\n%!"; 205 + let* params = read_params buf_read in 206 + Printf.eprintf "[DEBUG] read_request: Got %d params\n%!" (Fastcgi_record.KV.cardinal params); 208 207 let request = { request with params } in 209 208 (* Read remaining streams based on role *) 210 - Printf.eprintf "[DEBUG] read_request_from_flow: Reading streams for role=%s\n%!" 209 + Printf.eprintf "[DEBUG] read_request: Reading streams for role=%s\n%!" 211 210 (Format.asprintf "%a" pp_role request.role); 212 - let result = read_request_streams ~sw request buf_read in 213 - Printf.eprintf "[DEBUG] read_request_from_flow: Finished reading request\n%!"; 211 + let result = read_request_streams request buf_read in 212 + Printf.eprintf "[DEBUG] read_request: Finished reading request\n%!"; 214 213 result 215 214 with 216 215 | End_of_file -> 217 - Printf.eprintf "[DEBUG] read_request_from_flow: Hit End_of_file\n%!"; 216 + Printf.eprintf "[DEBUG] read_request: Hit End_of_file\n%!"; 218 217 Error "Unexpected end of stream" 219 218 | exn -> 220 - Printf.eprintf "[DEBUG] read_request_from_flow: Exception: %s\n%!" (Printexc.to_string exn); 219 + Printf.eprintf "[DEBUG] read_request: Exception: %s\n%!" (Printexc.to_string exn); 221 220 Error (Printf.sprintf "Error reading request: %s" (Printexc.to_string exn)) 222 221 223 222 (** {1 Response Generation} *) ··· 273 272 274 273 List.rev !records 275 274 276 - let flow_to_stream_records ~sw:_ ~request_id ~record_type flow = 277 - (* Read entire flow content *) 278 - let buf = Buffer.create 4096 in 279 - Eio.Flow.copy flow (Eio.Flow.buffer_sink buf); 280 - let content = Buffer.contents buf in 281 - string_to_stream_records ~request_id ~record_type content 282 - 283 - let write_stream_records records sink = 284 - (* Create a function to serialize a single record to a string *) 285 - let serialize_record record = 286 - let buf = Buffer.create 512 in 287 - let buf_sink = Eio.Flow.buffer_sink buf in 288 - Eio.Buf_write.with_flow buf_sink (fun buf_write -> 289 - Fastcgi_record.write buf_write record 290 - ); 291 - Buffer.contents buf 275 + let write_stream_records buf_write request_id record_type content = 276 + let max_chunk = 65535 in (* FastCGI max record content length *) 277 + let len = String.length content in 278 + 279 + let rec chunk_string pos = 280 + if pos >= len then 281 + () (* Empty terminator will be added separately *) 282 + else 283 + let chunk_len = min max_chunk (len - pos) in 284 + let chunk = String.sub content pos chunk_len in 285 + let record = Fastcgi_record.create ~version:1 ~record:record_type ~request_id ~content:chunk in 286 + Fastcgi_record.write buf_write record; 287 + chunk_string (pos + chunk_len) 292 288 in 293 289 294 - (* Serialize all records and write to sink *) 295 - List.iter (fun record -> 296 - let serialized = serialize_record record in 297 - Eio.Flow.copy_string serialized sink 298 - ) records 290 + chunk_string 0; 291 + 292 + (* Add stream terminator *) 293 + let terminator = Fastcgi_record.create ~version:1 ~record:record_type ~request_id ~content:"" in 294 + Fastcgi_record.write buf_write terminator 299 295 300 - let make_end_request ~request_id ~app_status ~protocol_status = 296 + let write_stdout_records buf_write request_id content = 297 + write_stream_records buf_write request_id Stdout content 298 + 299 + let write_stderr_records buf_write request_id content = 300 + write_stream_records buf_write request_id Stderr content 301 + 302 + let write_end_request buf_write request_id app_status protocol_status = 301 303 let content = 302 304 let buf = Bytes.create 8 in 303 305 Bytes.set_int32_be buf 0 (Int32.of_int app_status); ··· 307 309 Bytes.set_uint8 buf 7 0; (* reserved *) 308 310 Bytes.to_string buf 309 311 in 310 - Fastcgi_record.create ~version:1 ~record:End_request ~request_id ~content 311 - 312 - let write_response ~sw request ~stdout ~stderr sink app_status = 313 - (* Convert stdout flow to STDOUT records *) 314 - let stdout_records = flow_to_stream_records ~sw ~request_id:request.request_id ~record_type:Stdout stdout in 315 - 316 - (* Convert stderr flow to STDERR records *) 317 - let stderr_records = flow_to_stream_records ~sw ~request_id:request.request_id ~record_type:Stderr stderr in 318 - 319 - (* Create END_REQUEST record *) 320 - let end_record = make_end_request ~request_id:request.request_id ~app_status ~protocol_status:Request_complete in 321 - 322 - (* Write all records *) 323 - let all_records = stdout_records @ stderr_records @ [end_record] in 324 - write_stream_records all_records sink 325 - 326 - let write_error_response request sink proto_status = 327 - let end_record = make_end_request ~request_id:request.request_id ~app_status:1 ~protocol_status:proto_status in 328 - write_stream_records [end_record] sink 329 - 330 - let write_abort_response request sink = 331 - let end_record = make_end_request ~request_id:request.request_id ~app_status:0 ~protocol_status:Request_complete in 332 - write_stream_records [end_record] sink 333 - 334 - (** {1 High-level Request Processing} *) 335 - 336 - type handler = t -> 337 - stdout:Eio.Flow.sink_ty Eio.Resource.t -> 338 - stderr:Eio.Flow.sink_ty Eio.Resource.t -> 339 - app_status 340 - 341 - let process_request ~sw request handler sink = 342 - (* Create in-memory flows for stdout and stderr *) 343 - let stdout_buf = Buffer.create 4096 in 344 - let stderr_buf = Buffer.create 1024 in 345 - let stdout_sink = Eio.Flow.buffer_sink stdout_buf in 346 - let stderr_sink = Eio.Flow.buffer_sink stderr_buf in 347 - 348 - (* Call handler *) 349 - let app_status = handler request ~stdout:stdout_sink ~stderr:stderr_sink in 350 - 351 - (* Convert buffers to sources and write response *) 352 - let stdout_source = Eio.Flow.string_source (Buffer.contents stdout_buf) in 353 - let stderr_source = Eio.Flow.string_source (Buffer.contents stderr_buf) in 354 - 355 - write_response ~sw request ~stdout:stdout_source ~stderr:stderr_source sink app_status 356 - 357 - let process_request_with_flows ~sw request ~stdout ~stderr sink app_status = 358 - write_response ~sw request ~stdout ~stderr sink app_status 359 - 360 - (** {1 Connection Management} *) 361 - 362 - let handle_connection ~sw flow handler = 363 - let _buf_read = Eio.Buf_read.of_flow flow ~max_size:1000000 in 364 - let _buf_write = Eio.Buf_write.create 4096 in 365 - 366 - let rec loop () = 367 - try 368 - (* Read next request *) 369 - match read_request_from_flow ~sw flow with 370 - | Error msg -> 371 - (* Log error and continue or close connection *) 372 - Printf.eprintf "Error reading request: %s\n%!" msg 373 - | Ok request -> 374 - (* Process request *) 375 - let response_buf = Buffer.create 4096 in 376 - let response_sink = Eio.Flow.buffer_sink response_buf in 377 - 378 - process_request ~sw request handler response_sink; 379 - 380 - (* Write response to connection *) 381 - let response_data = Buffer.contents response_buf in 382 - Eio.Flow.copy (Eio.Flow.string_source response_data) flow; 383 - 384 - (* Continue if keep_conn is true *) 385 - if request.keep_conn then 386 - loop () 387 - with 388 - | End_of_file -> () (* Connection closed *) 389 - | exn -> 390 - Printf.eprintf "Connection error: %s\n%!" (Printexc.to_string exn) 391 - in 392 - loop () 312 + let record = Fastcgi_record.create ~version:1 ~record:End_request ~request_id ~content in 313 + Fastcgi_record.write buf_write record 393 314 394 - let serve ~sw:_ ~backlog:_ ~port:_ _handler = 395 - (* This would typically use Eio.Net to create a listening socket *) 396 - (* For now, we'll provide a placeholder implementation *) 397 - failwith "serve: Implementation requires Eio.Net integration"
+21 -104
lib/fastcgi_request.mli
··· 38 38 39 39 (** {1 Stream Processing} *) 40 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 buf_read] reads PARAMS stream from buf_read until empty record. 41 + (** [read_params buf_read] reads PARAMS stream from buf_read until empty record. 47 42 Returns the accumulated parameters. *) 48 - val read_params_from_flow : sw:Eio.Switch.t -> Eio.Buf_read.t -> (Fastcgi_record.KV.t, string) result 43 + val read_params : Eio.Buf_read.t -> (Fastcgi_record.KV.t, string) result 49 44 50 - (** [read_stdin_from_flow ~sw buf_read] reads STDIN stream from buf_read until empty record. 45 + (** [read_stdin buf_read] reads STDIN stream from buf_read until empty record. 51 46 Returns the accumulated data. *) 52 - val read_stdin_from_flow : sw:Eio.Switch.t -> Eio.Buf_read.t -> (string, string) result 47 + val read_stdin : Eio.Buf_read.t -> (string, string) result 48 + 49 + (** [read_data buf_read] reads DATA stream from buf_read until empty record. 50 + Returns the accumulated data for Filter role. *) 51 + val read_data : Eio.Buf_read.t -> (string, string) result 52 + 53 + (** [read_request buf_read] reads a complete FastCGI request from buf_read. 54 + Processes BEGIN_REQUEST, PARAMS, STDIN, and DATA records until complete. 55 + Returns the populated request context. *) 56 + val read_request : Eio.Buf_read.t -> (t, string) result 53 57 54 58 (** {1 Response Generation} *) 55 59 ··· 64 68 (** [pp_protocol_status ppf status] pretty-prints protocol status *) 65 69 val pp_protocol_status : Format.formatter -> protocol_status -> unit 66 70 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 71 + (** [write_stdout_records buf_write request_id content] writes STDOUT stream records. 72 + Splits content into chunks and writes with terminator. *) 73 + val write_stdout_records : Eio.Buf_write.t -> Fastcgi_record.request_id -> string -> unit 77 74 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 75 + (** [write_stderr_records buf_write request_id content] writes STDERR stream records. 76 + Splits content into chunks and writes with terminator. *) 77 + val write_stderr_records : Eio.Buf_write.t -> Fastcgi_record.request_id -> string -> unit 80 78 81 - (** [write_abort_response request sink] writes END_REQUEST for aborted request *) 82 - val write_abort_response : t -> 'a Eio.Flow.sink -> unit 79 + (** [write_end_request buf_write request_id app_status protocol_status] writes END_REQUEST record. *) 80 + val write_end_request : Eio.Buf_write.t -> Fastcgi_record.request_id -> app_status -> protocol_status -> unit 83 81 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 82 130 83 (** {1 Utilities} *) 131 84 ··· 141 94 record_type:Fastcgi_record.record -> 142 95 string -> Fastcgi_record.t list 143 96 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 97 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 - *)