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"