FastCGI implementation in OCaml
1module Record = Fastcgi_record 2 3(** Request-level state machine and application interface *) 4module Request = Fastcgi_request 5 6(** Request handler function type *) 7type 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. *) 15let 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. *) 22let 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. *) 42let 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. *) 60let 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. *) 68let 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. *) 99let 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"