FastCGI implementation in OCaml
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 *)
10type 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 *)
16val pp_role : Format.formatter -> role -> unit
17
18(** [role_of_begin_request record] extracts role from BEGIN_REQUEST record *)
19val role_of_begin_request : Fastcgi_record.t -> (role, string) result
20
21(** {1 Request Context} *)
22
23(** Immutable request context containing all request data *)
24type 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 *)
34val pp : Format.formatter -> t -> unit
35
36(** [create record] creates request context from BEGIN_REQUEST record *)
37val 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. *)
44val 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.
47 Returns the accumulated parameters. *)
48val read_params_from_flow : sw:Eio.Switch.t -> Eio.Buf_read.t -> (Fastcgi_record.KV.t, string) result
49
50(** [read_stdin_from_flow ~sw buf_read] reads STDIN stream from buf_read until empty record.
51 Returns the accumulated data. *)
52val read_stdin_from_flow : sw:Eio.Switch.t -> Eio.Buf_read.t -> (string, string) result
53
54(** {1 Response Generation} *)
55
56(** Response status codes *)
57type app_status = int
58type 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 *)
65val 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. *)
70val 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 *)
79val write_error_response : t -> 'a Eio.Flow.sink -> protocol_status -> unit
80
81(** [write_abort_response request sink] writes END_REQUEST for aborted request *)
82val write_abort_response : t -> 'a Eio.Flow.sink -> unit
83
84(** {1 High-level Request Processing} *)
85
86(** Request handler function type *)
87type 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. *)
94val 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. *)
102val 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. *)
115val 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. *)
123val 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 *)
133val is_stream_terminator : Fastcgi_record.t -> bool
134
135(** [stream_records_to_string records] concatenates content from stream records *)
136val 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 *)
139val 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 *)
145val 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 *)
152val write_stream_records : Fastcgi_record.t list -> 'a Eio.Flow.sink -> unit
153
154(** {1 Example Usage} *)
155
156(** {2 Simple Handler}
157{[
158let 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{[
175let () =
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*)