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*)