···11-(* Basic filter that uppercases text content *)
22-33-open Fastcgi
44-55-let uppercase_filter_logic filter_req response =
66- let input = filter_req.Filter.data_stream in
77- let output = response.stdout in
88-99- (* Simple text transformation: read input and write uppercase output *)
1010- let buffer = Cstruct.create 4096 in
1111- let rec process () =
1212- try
1313- let n = Eio.Flow.single_read input buffer in
1414- if n > 0 then begin
1515- let text = Cstruct.to_string ~len:n buffer in
1616- let uppercase_text = String.uppercase_ascii text in
1717- Eio.Flow.copy_string uppercase_text output;
1818- process ()
1919- end
2020- with
2121- | End_of_file -> ()
2222- in
2323- process ()
2424-2525-let filter_handler request response =
2626- let filter_req = Filter.request_of_fastcgi request in
2727-2828- (* Write HTTP headers *)
2929- Eio.Flow.copy_string "Status: 200 OK\r\n" response.stdout;
3030- Eio.Flow.copy_string "Content-Type: text/plain\r\n" response.stdout;
3131- Eio.Flow.copy_string "\r\n" response.stdout;
3232-3333- (* Process the data stream *)
3434- uppercase_filter_logic filter_req response;
3535-3636- { app_status = 0; protocol_status = Request_complete }
3737-3838-let () = Eio_main.run @@ fun env ->
3939- let net = Eio.Stdenv.net env in
4040- Eio.Switch.run @@ fun sw ->
4141- Server.run_default ~sw ~net
4242- ~handler:(Handler.Filter filter_handler)
4343- ~listen_address:(`Tcp ("127.0.0.1", 9003))
···11-(** FastCGI protocol implementation for OCaml using Eio.
22-33- This library provides a type-safe, high-performance implementation of the
44- FastCGI protocol using OCaml's Eio effects-based IO library. It supports
55- all three FastCGI roles: Responder, Authorizer, and Filter.
66-77- FastCGI is an open extension to CGI that provides high performance for all
88- Internet applications without the penalties of Web server APIs. Unlike
99- traditional CGI programs that are started for each request, FastCGI
1010- applications are long-lived processes that can handle multiple requests
1111- over persistent connections.
1212-1313- The library follows Eio conventions for structured concurrency,
1414- capability-based security, and resource management. All network I/O is
1515- performed using Eio's effects system, enabling high-performance concurrent
1616- request processing.
1717-1818- {2 Key Features}
1919-2020- - {b Type Safety}: Leverages OCaml's type system to prevent protocol errors
2121- - {b High Performance}: Connection multiplexing and keep-alive for efficiency
2222- - {b Structured Concurrency}: Automatic resource cleanup using Eio switches
2323- - {b All FastCGI Roles}: Complete support for Responder, Authorizer, and Filter
2424- - {b Standards Compliant}: Full implementation of FastCGI Protocol 1.0
2525-2626- {2 Usage Example}
2727-2828- {[
2929- let hello_handler request response =
3030- let http_req = Responder.request_of_fastcgi request in
3131- let http_resp = Responder.response_of_fastcgi response in
3232-3333- http_resp.write_status 200;
3434- http_resp.write_header "Content-Type" "text/html";
3535- http_resp.write_body "<h1>Hello, FastCGI!</h1>";
3636- http_resp.finish ();
3737-3838- { app_status = 0; protocol_status = Request_complete }
3939-4040- let () = Eio_main.run @@ fun env ->
4141- let net = Eio.Stdenv.net env in
4242- Switch.run @@ fun sw ->
4343- Server.run_default ~sw ~net
4444- ~handler:(Handler.Responder hello_handler)
4545- ~listen_address:(`Tcp (Eio.Net.Sockaddr.stream, 9000))
4646- ]}
4747-4848- {2 References}
4949-5050- - {{:https://github.com/ocaml-multicore/eio} Eio Documentation} *)
5151-5252-(** {1 Core Types} *)
5353-5454-(** Re-export core types from Fastcgi_types.
5555-5656- This includes all the fundamental FastCGI protocol types such as
5757- [record_type], [role], [request], [response], and protocol constants.
5858- This includes all the fundamental FastCGI protocol types. *)
5959-include module type of Fastcgi_types
6060-6161-(** {1 Protocol Implementation} *)
6262-6363-(** Wire protocol parsing and serialization. *)
6464-module Protocol = Fastcgi_protocol
6565-6666-(** {1 Role-Specific Implementations} *)
6767-6868-(** Responder role implementation. *)
6969-module Responder = Fastcgi_responder
7070-7171-(** Authorizer role implementation. *)
7272-module Authorizer = Fastcgi_authorizer
7373-7474-(** Filter role implementation. *)
7575-module Filter = Fastcgi_filter
7676-7777-(** {1 Application Interface} *)
7878-7979-(** Application handler signatures.
8080-8181- This module defines the core handler types that applications implement
8282- to process FastCGI requests. Each handler type corresponds to one of
8383- the three FastCGI roles: Responder, Authorizer, and Filter. *)
8484-module Handler : sig
8585- (** Responder handler: process HTTP request and generate response.
8686-8787- This is the most common handler type, equivalent to traditional CGI.
8888- A Responder FastCGI application has the same purpose as a CGI/1.1 program:
8989- it receives all the information associated with an HTTP request and generates
9090- an HTTP response.
9191-9292- @param request Complete FastCGI request with parameters and input streams
9393- @param response Output streams for writing response data
9494- @return Response result with application and protocol status *)
9595- type 'a responder = 'a request -> 'a response -> response_result
9696-9797- (** Authorizer handler: make authorization decision.
9898-9999- An Authorizer FastCGI application receives all the information associated
100100- with an HTTP request and generates an authorized/unauthorized decision.
101101- In case of an authorized decision, the Authorizer can also associate
102102- name-value pairs with the HTTP request.
103103-104104- @param request FastCGI request with authentication context
105105- @param response Output streams for authorization response
106106- @return Response result indicating authorization decision *)
107107- type 'a authorizer = 'a request -> 'a response -> response_result
108108-109109- (** Filter handler: process data stream with filtering.
110110-111111- A Filter FastCGI application receives all the information associated
112112- with an HTTP request, plus an extra stream of data from a file stored
113113- on the Web server, and generates a 'filtered' version of the data stream
114114- as an HTTP response.
115115-116116- @param request FastCGI request including both HTTP context and file data
117117- @param response Output streams for filtered response
118118- @return Response result with filtered content status *)
119119- type 'a filter = 'a request -> 'a response -> response_result
120120-121121- (** Generic handler that can handle any role.
122122-123123- This variant type allows applications to support multiple roles or
124124- to be configured at runtime for different roles. The web server
125125- specifies the desired role in each Begin_request record. *)
126126- type 'a handler =
127127- | Responder of 'a responder (** Handle HTTP requests and responses *)
128128- | Authorizer of 'a authorizer (** Handle authorization decisions *)
129129- | Filter of 'a filter (** Handle data filtering *)
130130-end
131131-132132-(** Application configuration.
133133-134134- This type encapsulates all the settings needed to configure a FastCGI
135135- application's behavior. It includes both resource limits and the
136136- application logic (handler function).
137137-138138- The configuration values affect how the application responds to
139139- FCGI_GET_VALUES management queries from the web server. *)
140140-type 'a app_config = {
141141- max_connections : int; (** Maximum concurrent transport connections
142142- this application will accept. Corresponds to
143143- FCGI_MAX_CONNS management variable. *)
144144- max_requests : int; (** Maximum concurrent requests this application
145145- will accept across all connections. Corresponds
146146- to FCGI_MAX_REQS management variable. *)
147147- multiplex_connections : bool; (** Whether this application can multiplex
148148- connections (handle concurrent requests over
149149- each connection). Corresponds to FCGI_MPXS_CONNS
150150- management variable. *)
151151- handler : 'a Handler.handler; (** Application request handler that implements
152152- the core application logic. *)
153153-}
154154-155155-(** {1 Connection Management} *)
156156-157157-(** Connection manager for handling FastCGI protocol.
158158-159159- This module provides low-level connection management for FastCGI applications.
160160- It handles the binary protocol details, record parsing/serialization, and
161161- request multiplexing over individual transport connections.
162162-163163- After a FastCGI process accepts a connection on its listening socket, the
164164- process executes a simple protocol to receive and send data. The protocol
165165- serves two purposes: First, it multiplexes a single transport connection
166166- between several independent FastCGI requests. Second, within each request
167167- the protocol provides several independent data streams in each direction. *)
168168-module Connection : sig
169169- (** Opaque connection type.
170170-171171- Represents a single transport connection from a web server. The connection
172172- handles protocol parsing, request demultiplexing, and stream management.
173173- Each connection can handle multiple concurrent requests if the application
174174- supports multiplexing. *)
175175- type 'a t
176176-177177- (** Connection statistics.
178178-179179- Provides runtime metrics about connection usage for monitoring and
180180- debugging purposes. These statistics can help tune application
181181- performance and detect potential issues. *)
182182- type stats = {
183183- active_requests : int; (** Number of requests currently being processed
184184- on this connection. *)
185185- total_requests : int; (** Total number of requests processed since
186186- connection establishment. *)
187187- bytes_sent : int; (** Total bytes sent to the web server (responses). *)
188188- bytes_received : int; (** Total bytes received from the web server (requests). *)
189189- }
190190-191191- (** Create a connection from a network flow.
192192-193193- Initializes a new FastCGI connection over an established transport.
194194- The connection will parse incoming records and manage request state
195195- according to the FastCGI protocol.
196196-197197- The connection handles a simple protocol to receive and send data,
198198- including record headers, content data, and proper multiplexing.
199199-200200- @param sw Switch for managing connection lifetime and automatic cleanup
201201- @param flow Two-way network flow for bidirectional communication
202202- @return New connection ready to process FastCGI requests
203203- @raise Invalid_argument if the flow is already closed *)
204204- val create : sw:Eio.Switch.t -> 'a Eio.Flow.two_way -> 'a t
205205-206206- (** Accept and process a single request on the connection.
207207-208208- Waits for the next FastCGI request to arrive on this connection,
209209- then processes it using the provided handler. This function handles
210210- all protocol details including record parsing, stream management,
211211- and response generation.
212212-213213- The function is concurrent-safe and can be called multiple times
214214- if the application supports request multiplexing.
215215-216216- @param conn Connection to process request on
217217- @param handler Application handler for the request
218218- @return Promise that resolves to the response result when processing completes
219219- @raise Fastcgi.Error.Fastcgi_error on protocol violations *)
220220- val process_request : 'a t -> 'a Handler.handler -> response_result Eio.Promise.t
221221-222222- (** Get connection statistics.
223223-224224- Returns current runtime statistics for this connection. This information
225225- can be used for monitoring, load balancing decisions, and performance
226226- tuning.
227227-228228- @param conn Connection to query
229229- @return Current statistics snapshot *)
230230- val stats : 'a t -> stats
231231-232232- (** Close the connection gracefully.
233233-234234- Closes the transport connection after completing any active requests.
235235- The Web server controls the lifetime of transport connections and can
236236- close a connection when no requests are active.
237237-238238- @param conn Connection to close *)
239239- val close : 'a t -> unit
240240-end
241241-242242-(** FastCGI server for accepting and managing connections.
243243-244244- This module provides a high-level server that listens for incoming
245245- connections and manages them according to FastCGI protocol requirements.
246246- It handles connection acceptance, lifecycle management, and graceful
247247- shutdown.
248248-249249- The server supports both Unix domain sockets and TCP sockets, as
250250- commonly used by web servers like nginx, Apache, and lighttpd. *)
251251-module Server : sig
252252- (** Server configuration.
253253-254254- Complete configuration for a FastCGI server, including application
255255- settings, network binding, and resource limits. *)
256256- type 'a config = {
257257- app : 'a app_config; (** Application configuration including handlers
258258- and capability limits. *)
259259- listen_address : [
260260- | `Unix of string (** Unix domain socket path. Common for local
261261- communication with web servers. *)
262262- | `Tcp of string * int (** TCP socket address (host) and port.
263263- Used for remote FastCGI servers. *)
264264- ];
265265- backlog : int; (** Listen socket backlog size. Controls how
266266- many pending connections can be queued. *)
267267- max_connections : int; (** Maximum concurrent connections to accept.
268268- Should align with app.max_connections. *)
269269- }
270270-271271- (** Run a FastCGI server.
272272-273273- Starts a FastCGI server with the specified configuration. The server
274274- will listen for connections, accept them within the configured limits,
275275- and process requests using the application handler.
276276-277277- The server implements the standard FastCGI application lifecycle:
278278- a FastCGI application calls accept() on the socket referred to by file
279279- descriptor FCGI_LISTENSOCK_FILENO to accept a new transport connection.
280280-281281- This function blocks until the switch is cancelled or an error occurs.
282282-283283- @param sw Switch for managing server lifetime and automatic cleanup
284284- @param net Network capability for creating listening sockets
285285- @param config Complete server configuration
286286- @raise Sys_error if unable to bind to the specified address *)
287287- val run :
288288- sw:Eio.Switch.t ->
289289- net:[< `Generic | `Unix ] Eio.Net.ty Eio.Resource.t ->
290290- _ config ->
291291- unit
292292-293293- (** Run server with default configuration.
294294-295295- Convenience function to start a FastCGI server with sensible defaults.
296296- Useful for simple applications that don't need custom configuration.
297297-298298- Default settings:
299299- - max_connections: 10
300300- - max_requests: 50
301301- - multiplex_connections: true
302302- - backlog: 5
303303-304304- @param sw Switch for managing server lifetime
305305- @param net Network capability for creating sockets
306306- @param handler Application handler function
307307- @param listen_address Address to listen on (Unix socket or TCP)
308308- @raise Sys_error if unable to bind to the specified address *)
309309- val run_default :
310310- sw:Eio.Switch.t ->
311311- net:[< `Generic | `Unix ] Eio.Net.ty Eio.Resource.t ->
312312- handler:_ Handler.handler ->
313313- listen_address:[`Unix of string | `Tcp of string * int] ->
314314- unit
315315-end
316316-317317-(** {1 Error Handling} *)
318318-319319-(** FastCGI specific errors. *)
320320-module Error : sig
321321- (** Error types. *)
322322- type t =
323323- | Protocol_error of string (** Protocol violation *)
324324- | Invalid_record of string (** Malformed record *)
325325- | Unsupported_version of int (** Unsupported protocol version *)
326326- | Unknown_record_type of int (** Unknown record type *)
327327- | Request_id_conflict of request_id (** Duplicate request ID *)
328328- | Connection_closed (** Connection unexpectedly closed *)
329329- | Application_error of string (** Application-specific error *)
330330-331331- (** FastCGI exception. *)
332332- exception Fastcgi_error of t
333333-334334- (** Convert error to string description. *)
335335- val to_string : t -> string
336336-337337- (** Raise a FastCGI error. *)
338338- val raise : t -> 'a
339339-end
340340-341341-(** {1 Resource Management} *)
342342-343343-(** Resource management utilities. *)
344344-module Resource : sig
345345- (** Request context with automatic cleanup. *)
346346- type 'a request_context = {
347347- request : 'a request;
348348- response : 'a response;
349349- switch : Eio.Switch.t; (** Switch for request-scoped resources *)
350350- }
351351-352352- (** Create a request context with automatic resource management.
353353-354354- @param sw Parent switch
355355- @param request FastCGI request
356356- @param f Function to execute with request context
357357- @return Result of function execution *)
358358- val with_request_context :
359359- sw:Eio.Switch.t ->
360360- 'a request ->
361361- ('a request_context -> 'b) ->
362362- 'b
363363-364364- (** Buffer management for efficient I/O. *)
365365- module Buffer : sig
366366- (** Buffer type. *)
367367- type t
368368-369369- (** Create a buffer with specified size. *)
370370- val create : size:int -> t
371371-372372- (** Read data into buffer from source.
373373-374374- @param buffer Buffer to read into
375375- @param source Source to read from
376376- @return Number of bytes read *)
377377- val read_into : t -> 'a Eio.Flow.source -> int
378378-379379- (** Write data from buffer to sink.
380380-381381- @param buffer Buffer to write from
382382- @param sink Sink to write to
383383- @param length Number of bytes to write *)
384384- val write_from : t -> 'a Eio.Flow.sink -> int -> unit
385385-386386- (** Clear buffer contents. *)
387387- val clear : t -> unit
388388- end
389389-end
-229
lib/fastcgi_authorizer.mli
···11-(** Authorizer role implementation for FastCGI.
22-33- The Authorizer role performs access control decisions for web requests.
44- An Authorizer FastCGI application receives all the information associated
55- with an HTTP request and generates an authorized/unauthorized decision.
66-77- In case of an authorized decision, the Authorizer can associate name-value
88- pairs with the HTTP request for use by subsequent processing stages. When
99- giving an unauthorized decision, the Authorizer sends a complete response
1010- to the HTTP client with appropriate error information.
1111-1212- This role is commonly used for:
1313- - Authentication and authorization checks
1414- - Access control based on IP addresses, user roles, or request attributes
1515- - Rate limiting and quota enforcement
1616- - Custom security policies
1717-1818- The web server may proceed with additional access checks after a successful
1919- authorization, including requests to other Authorizers, depending on its
2020- configuration. *)
2121-2222-(** {1 Authorization Types} *)
2323-2424-(** Authorization result.
2525-2626- Represents the outcome of an authorization decision. The Authorizer can
2727- either grant access (possibly with additional variables) or deny access
2828- with a complete error response. *)
2929-type auth_result =
3030- | Authorized of (string * string) list (** Request is authorized. The list contains
3131- variable bindings that will be added to
3232- the request environment for subsequent
3333- processing stages. *)
3434- | Unauthorized of {
3535- status : int; (** HTTP status code for the error response
3636- (typically 401, 403, or 429). *)
3737- headers : (string * string) list; (** HTTP headers for the error response.
3838- May include WWW-Authenticate, etc. *)
3939- body : string; (** Error response body content sent to the client. *)
4040- } (** Request is denied. The web server will send this error
4141- response directly to the client without further processing. *)
4242-4343-(** Authorization request information.
4444-4545- Contains the relevant information from an HTTP request needed to make
4646- authorization decisions. This is extracted from the CGI environment
4747- variables provided by the web server. *)
4848-type auth_request = {
4949- method_ : string; (** HTTP method (GET, POST, etc.). *)
5050- uri : string; (** Request URI being accessed. *)
5151- remote_addr : string option; (** Client IP address, if available.
5252- Useful for IP-based access control. *)
5353- remote_user : string option; (** Authenticated username, if any.
5454- Set by prior authentication stages. *)
5555- auth_type : string option; (** Authentication method used (Basic, Bearer, etc.).
5656- Indicates how the user was authenticated. *)
5757- headers : (string * string) list; (** HTTP headers from the request.
5858- May contain authorization headers, user agents, etc. *)
5959-}
6060-6161-(** {1 Conversion Functions} *)
6262-6363-(** Convert FastCGI request to authorization request.
6464-6565- Extracts authorization-relevant information from FastCGI
6666- parameters and creates an auth request object.
6767-6868- @param request FastCGI request
6969- @return Authorization request *)
7070-val request_of_fastcgi : 'a Fastcgi_types.request -> auth_request
7171-7272-(** Convert authorization result to FastCGI response.
7373-7474- Writes the authorization decision to the FastCGI response
7575- streams in the correct format.
7676-7777- @param result Authorization decision
7878- @param response FastCGI response
7979- @return Response result *)
8080-val response_of_result : auth_result -> 'a Fastcgi_types.response -> Fastcgi_types.response_result
8181-8282-(** {1 Handler Utilities} *)
8383-8484-(** Authorizer handler type. *)
8585-type 'a authorizer_handler = 'a Fastcgi_types.request -> 'a Fastcgi_types.response -> Fastcgi_types.response_result
8686-8787-(** Convenience handler wrapper for authorization handlers.
8888-8989- Converts an authorization handler function into a FastCGI handler.
9090- This allows writing handlers that work with auth request/result
9191- types instead of raw FastCGI types.
9292-9393- @param handler Authorization handler function
9494- @return FastCGI authorizer handler *)
9595-val auth_handler :
9696- (auth_request -> auth_result) ->
9797- 'a authorizer_handler
9898-9999-(** {1 Common Authorization Patterns} *)
100100-101101-(** Create a simple allow/deny authorizer.
102102-103103- @param predicate Function that returns true for authorized requests
104104- @return Authorization result *)
105105-val simple_authorizer : (auth_request -> bool) -> (auth_request -> auth_result)
106106-107107-(** IP-based authorization.
108108-109109- @param allowed_ips List of allowed IP addresses/ranges
110110- @param request Authorization request
111111- @return Authorization result *)
112112-val ip_authorizer : string list -> auth_request -> auth_result
113113-114114-(** User-based authorization.
115115-116116- @param allowed_users List of allowed usernames
117117- @param request Authorization request
118118- @return Authorization result *)
119119-val user_authorizer : string list -> auth_request -> auth_result
120120-121121-(** Role-based authorization.
122122-123123- @param get_user_roles Function to get roles for a user
124124- @param required_roles Roles required for access
125125- @param request Authorization request
126126- @return Authorization result *)
127127-val role_authorizer :
128128- get_user_roles:(string -> string list) ->
129129- required_roles:string list ->
130130- auth_request ->
131131- auth_result
132132-133133-(** Time-based authorization.
134134-135135- @param allowed_hours List of allowed hours (0-23)
136136- @param request Authorization request
137137- @return Authorization result *)
138138-val time_authorizer : int list -> auth_request -> auth_result
139139-140140-(** {1 Authentication Integration} *)
141141-142142-(** Basic authentication handler.
143143-144144- @param realm Authentication realm
145145- @param verify_credentials Function to verify username/password
146146- @param request Authorization request
147147- @return Authorization result *)
148148-val basic_auth :
149149- realm:string ->
150150- verify_credentials:(string -> string -> bool) ->
151151- auth_request ->
152152- auth_result
153153-154154-(** Bearer token authentication.
155155-156156- @param verify_token Function to verify and decode token
157157- @param request Authorization request
158158- @return Authorization result *)
159159-val bearer_auth :
160160- verify_token:(string -> (string * (string * string) list) option) ->
161161- auth_request ->
162162- auth_result
163163-164164-(** API key authentication.
165165-166166- @param header_name Header containing API key
167167- @param verify_key Function to verify API key
168168- @param request Authorization request
169169- @return Authorization result *)
170170-val api_key_auth :
171171- header_name:string ->
172172- verify_key:(string -> (string * (string * string) list) option) ->
173173- auth_request ->
174174- auth_result
175175-176176-(** {1 Response Helpers} *)
177177-178178-(** Create a standard 401 Unauthorized response.
179179-180180- @param realm Authentication realm
181181- @param message Error message
182182- @return Unauthorized result *)
183183-val unauthorized_response :
184184- realm:string ->
185185- message:string ->
186186- auth_result
187187-188188-(** Create a standard 403 Forbidden response.
189189-190190- @param message Error message
191191- @return Unauthorized result *)
192192-val forbidden_response :
193193- message:string ->
194194- auth_result
195195-196196-(** Create a custom error response.
197197-198198- @param status HTTP status code
199199- @param headers Additional headers
200200- @param body Response body
201201- @return Unauthorized result *)
202202-val error_response :
203203- status:int ->
204204- headers:(string * string) list ->
205205- body:string ->
206206- auth_result
207207-208208-(** {1 Variable Binding} *)
209209-210210-(** Create authorized response with variables.
211211-212212- Variables are passed to subsequent FastCGI applications
213213- as environment variables with "Variable-" prefix.
214214-215215- @param variables Name-value pairs to bind
216216- @return Authorized result *)
217217-val authorized_with_variables : (string * string) list -> auth_result
218218-219219-(** Add authentication information as variables.
220220-221221- @param user Authenticated username
222222- @param auth_type Authentication method
223223- @param additional Additional variables
224224- @return Variable bindings *)
225225-val auth_variables :
226226- user:string ->
227227- auth_type:string ->
228228- additional:(string * string) list ->
229229- (string * string) list
-329
lib/fastcgi_filter.mli
···11-(** Filter role implementation for FastCGI.
22-33- The Filter role processes data streams with filtering capabilities.
44- A Filter FastCGI application receives all the information associated with
55- an HTTP request, plus an extra stream of data from a file stored on the
66- web server, and generates a "filtered" version of the data stream as an
77- HTTP response.
88-99- This role is useful for:
1010- - Dynamic content transformation (e.g., server-side includes, templating)
1111- - Format conversion (e.g., Markdown to HTML, image resizing)
1212- - Content compression and optimization
1313- - Real-time content modification based on request context
1414-1515- A Filter is similar in functionality to a Responder that takes a data file
1616- as a parameter. The key difference is that with a Filter, both the data file
1717- and the Filter itself can be access controlled using the web server's access
1818- control mechanisms, while a Responder that takes the name of a data file as
1919- a parameter must perform its own access control checks on the data file.
2020-2121- The web server presents the Filter with environment variables first, then
2222- standard input (normally form POST data), and finally the data file input
2323- that needs to be processed. *)
2424-2525-(** {1 Filter Types} *)
2626-2727-(** Filter request with data stream.
2828-2929- Represents a complete Filter request including both the HTTP request
3030- context and the data stream that needs to be processed. *)
3131-type 'a filter_request = {
3232- request : 'a Fastcgi_types.request; (** Base FastCGI request containing HTTP context,
3333- parameters, and form data. *)
3434- data_stream : 'a Eio.Flow.source; (** File data stream to be filtered.
3535- This is the content that needs processing. *)
3636- data_last_modified : float option; (** File modification time as seconds since epoch.
3737- Useful for caching and conditional requests. *)
3838- data_length : int option; (** Expected length of the data stream in bytes.
3939- May be None if length is unknown. *)
4040-}
4141-4242-(** Filter metadata about the data being processed. *)
4343-type filter_metadata = {
4444- filename : string option; (** Original filename *)
4545- mime_type : string option; (** MIME type of data *)
4646- last_modified : float option; (** Last modification time *)
4747- size : int option; (** Data size in bytes *)
4848- etag : string option; (** Entity tag for caching *)
4949-}
5050-5151-(** Filter context with additional processing information. *)
5252-type 'a filter_context = {
5353- filter_request : 'a filter_request; (** Request and data *)
5454- metadata : filter_metadata; (** File metadata *)
5555- cache_control : cache_policy; (** Caching policy *)
5656-}
5757-5858-(** Cache policy for filtered content. *)
5959-and cache_policy =
6060- | No_cache (** No caching *)
6161- | Cache_for of int (** Cache for N seconds *)
6262- | Cache_until of float (** Cache until timestamp *)
6363- | Conditional_cache of (filter_metadata -> bool) (** Conditional caching *)
6464-6565-(** {1 Conversion Functions} *)
6666-6767-(** Convert FastCGI request to filter request.
6868-6969- Extracts filter-specific information from FastCGI streams
7070- and creates a filter request object.
7171-7272- @param request FastCGI request
7373- @return Filter request with data stream *)
7474-val request_of_fastcgi : 'a Fastcgi_types.request -> 'a filter_request
7575-7676-(** Create filter context with metadata.
7777-7878- @param filter_request Filter request
7979- @param metadata File metadata
8080- @param cache_control Caching policy
8181- @return Filter context *)
8282-val context_of_request :
8383- 'a filter_request ->
8484- metadata:filter_metadata ->
8585- cache_control:cache_policy ->
8686- 'a filter_context
8787-8888-(** {1 Handler Utilities} *)
8989-9090-(** Filter handler type. *)
9191-type 'a filter_handler = 'a Fastcgi_types.request -> 'a Fastcgi_types.response -> Fastcgi_types.response_result
9292-9393-(** Convenience handler wrapper for filter handlers.
9494-9595- Converts a filter handler function into a FastCGI handler.
9696- This allows writing handlers that work with filter request/response
9797- types instead of raw FastCGI types.
9898-9999- @param handler Filter handler function
100100- @return FastCGI filter handler *)
101101-102102-val filter_handler :
103103- ('a filter_request -> 'a Fastcgi_types.response -> unit) ->
104104- 'a filter_handler
105105-106106-(** Context-aware filter handler.
107107-108108- Similar to filter_handler but provides additional context
109109- including metadata and caching information.
110110-111111- @param handler Context-aware filter handler
112112- @return FastCGI filter handler *)
113113-val context_filter_handler :
114114- ('a filter_context -> 'a Fastcgi_types.response -> unit) ->
115115- 'a filter_handler
116116-117117-(** {1 Common Filter Operations} *)
118118-119119-(** Text processing filters. *)
120120-module Text : sig
121121- (** Convert text encoding.
122122-123123- @param from_encoding Source encoding
124124- @param to_encoding Target encoding
125125- @param input Text stream
126126- @param output Filtered stream *)
127127- val convert_encoding :
128128- from_encoding:string ->
129129- to_encoding:string ->
130130- input:'a Eio.Flow.source ->
131131- output:'a Eio.Flow.sink ->
132132- unit
133133-134134- (** Find and replace text patterns.
135135-136136- @param pattern Regular expression pattern
137137- @param replacement Replacement text
138138- @param input Text stream
139139- @param output Filtered stream *)
140140- val find_replace :
141141- pattern:string ->
142142- replacement:string ->
143143- input:'a Eio.Flow.source ->
144144- output:'a Eio.Flow.sink ->
145145- unit
146146-147147- (** Template substitution.
148148-149149- @param variables Variable substitutions
150150- @param input Template stream
151151- @param output Processed stream *)
152152- val template_substitute :
153153- variables:(string * string) list ->
154154- input:'a Eio.Flow.source ->
155155- output:'a Eio.Flow.sink ->
156156- unit
157157-158158- (** Markdown to HTML conversion.
159159-160160- @param options Markdown processing options
161161- @param input Markdown stream
162162- @param output HTML stream *)
163163- val markdown_to_html :
164164- options:string list ->
165165- input:'a Eio.Flow.source ->
166166- output:'a Eio.Flow.sink ->
167167- unit
168168-end
169169-170170-(** Image processing filters. *)
171171-module Image : sig
172172- (** Image resize operation.
173173-174174- @param width Target width
175175- @param height Target height
176176- @param input Image stream
177177- @param output Resized stream *)
178178- val resize :
179179- width:int ->
180180- height:int ->
181181- input:'a Eio.Flow.source ->
182182- output:'a Eio.Flow.sink ->
183183- unit
184184-185185- (** Image format conversion.
186186-187187- @param format Target format (jpeg, png, etc.)
188188- @param quality Quality setting (for lossy formats)
189189- @param input Image stream
190190- @param output Converted stream *)
191191- val convert_format :
192192- format:string ->
193193- quality:int option ->
194194- input:'a Eio.Flow.source ->
195195- output:'a Eio.Flow.sink ->
196196- unit
197197-198198- (** Image compression.
199199-200200- @param level Compression level
201201- @param input Image stream
202202- @param output Compressed stream *)
203203- val compress :
204204- level:int ->
205205- input:'a Eio.Flow.source ->
206206- output:'a Eio.Flow.sink ->
207207- unit
208208-end
209209-210210-(** Data compression filters. *)
211211-module Compression : sig
212212- (** Gzip compression.
213213-214214- @param level Compression level (1-9)
215215- @param input Data stream
216216- @param output Compressed stream *)
217217- val gzip :
218218- level:int ->
219219- input:'a Eio.Flow.source ->
220220- output:'a Eio.Flow.sink ->
221221- unit
222222-223223- (** Brotli compression.
224224-225225- @param level Compression level
226226- @param input Data stream
227227- @param output Compressed stream *)
228228- val brotli :
229229- level:int ->
230230- input:'a Eio.Flow.source ->
231231- output:'a Eio.Flow.sink ->
232232- unit
233233-234234- (** Auto-detect and compress.
235235-236236- @param preferred_formats Preferred compression formats
237237- @param accept_encoding Client Accept-Encoding header
238238- @param input Data stream
239239- @param output Compressed stream *)
240240- val auto_compress :
241241- preferred_formats:string list ->
242242- accept_encoding:string option ->
243243- input:'a Eio.Flow.source ->
244244- output:'a Eio.Flow.sink ->
245245- string option (** Returns content-encoding used *)
246246-end
247247-248248-(** {1 Caching and Optimization} *)
249249-250250-(** Check if content should be served from cache.
251251-252252- @param metadata File metadata
253253- @param if_modified_since Client If-Modified-Since header
254254- @param if_none_match Client If-None-Match header
255255- @return True if content is not modified *)
256256-val check_not_modified :
257257- metadata:filter_metadata ->
258258- if_modified_since:string option ->
259259- if_none_match:string option ->
260260- bool
261261-262262-(** Generate appropriate cache headers.
263263-264264- @param metadata File metadata
265265- @param cache_policy Caching policy
266266- @return List of cache-related headers *)
267267-val generate_cache_headers :
268268- metadata:filter_metadata ->
269269- cache_policy:cache_policy ->
270270- (string * string) list
271271-272272-(** {1 Streaming Operations} *)
273273-274274-(** Stream processor for chunk-by-chunk filtering. *)
275275-type 'a stream_processor = {
276276- process_chunk : bytes -> bytes option; (** Process a data chunk *)
277277- finalize : unit -> bytes option; (** Finalize and get remaining data *)
278278- reset : unit -> unit; (** Reset processor state *)
279279-}
280280-281281-(** Create a streaming filter.
282282-283283- @param processor Stream processor
284284- @param input Source stream
285285- @param output Target stream *)
286286-val streaming_filter :
287287- processor:'a stream_processor ->
288288- input:'a Eio.Flow.source ->
289289- output:'a Eio.Flow.sink ->
290290- unit
291291-292292-(** Chain multiple filters together.
293293-294294- @param filters List of filter functions
295295- @param input Source stream
296296- @param output Target stream *)
297297-val chain_filters :
298298- filters:(('a Eio.Flow.source -> 'a Eio.Flow.sink -> unit) list) ->
299299- input:'a Eio.Flow.source ->
300300- output:'a Eio.Flow.sink ->
301301- unit
302302-303303-(** {1 Error Handling} *)
304304-305305-(** Filter-specific errors. *)
306306-type filter_error =
307307- | Data_corruption of string (** Data corruption detected *)
308308- | Unsupported_format of string (** Unsupported file format *)
309309- | Processing_failed of string (** Filter processing failed *)
310310- | Resource_exhausted of string (** Out of memory/disk space *)
311311-312312-(** Filter exception. *)
313313-exception Filter_error of filter_error
314314-315315-(** Convert filter error to string. *)
316316-val filter_error_to_string : filter_error -> string
317317-318318-(** Safe filter wrapper that handles errors gracefully.
319319-320320- @param fallback Fallback function if filter fails
321321- @param filter Primary filter function
322322- @param input Source stream
323323- @param output Target stream *)
324324-val safe_filter :
325325- fallback:('a Eio.Flow.source -> 'a Eio.Flow.sink -> unit) ->
326326- filter:('a Eio.Flow.source -> 'a Eio.Flow.sink -> unit) ->
327327- input:'a Eio.Flow.source ->
328328- output:'a Eio.Flow.sink ->
329329- unit
-414
lib/fastcgi_protocol.mli
···11-(** FastCGI wire protocol parsing and serialization.
22-33- This module handles the low-level FastCGI protocol details including
44- record parsing, name-value pair encoding, and binary data serialization
55- using Eio's Buf_read and Buf_write modules.
66-77- The FastCGI protocol uses a binary record format where all data is
88- transmitted as 8-byte fixed headers followed by variable-length content
99- and optional padding. Multi-byte integers use network byte order for
1010- platform independence.
1111-1212- The protocol supports several key features:
1313- - Request multiplexing over single connections
1414- - Variable-length encoding for efficient name-value pairs
1515- - Stream-based data transmission with proper termination
1616- - Alignment padding for optimal performance
1717-1818- This module provides the low-level primitives for encoding and decoding
1919- these protocol elements, allowing higher-level modules to focus on
2020- application logic rather than binary protocol details. *)
2121-2222-(** Protocol parsing error.
2323-2424- Raised when invalid or malformed protocol data is encountered during
2525- parsing operations. This includes version mismatches, invalid record
2626- structures, malformed length encodings, and other protocol violations. *)
2727-exception Protocol_error of string
2828-2929-(** {1 Protocol Parsing}
3030-3131- These functions parse incoming FastCGI records and protocol elements
3232- from binary data streams. They validate protocol compliance and convert
3333- binary data into OCaml types for processing. *)
3434-3535-(** Parse a FastCGI record header from a buffer.
3636-3737- Reads exactly 8 bytes from the buffer to construct a record header.
3838- The header contains version, record type, request ID, content length,
3939- and padding length fields using network byte order.
4040-4141- The function validates that the version is supported (currently only
4242- version 1) and that the record type is recognized.
4343-4444- @param buf Input buffer positioned at the start of a record header
4545- @return Parsed record header with validated fields
4646- @raise Protocol_error if version is unsupported or data is malformed *)
4747-val parse_record_header : Eio.Buf_read.t -> Fastcgi_types.record_header
4848-4949-(** Parse a complete FastCGI record from a buffer.
5050-5151- Reads a record header followed by the specified amount of content data
5252- and padding. This is the primary function for reading FastCGI records
5353- from network streams.
5454-5555- The function ensures that exactly the number of content and padding
5656- bytes specified in the header are read, maintaining proper stream
5757- alignment for subsequent records.
5858-5959- @param buf Input buffer positioned at the start of a complete record
6060- @return Complete parsed record with header, content, and optional padding
6161- @raise Protocol_error if the record structure is invalid *)
6262-val parse_record : Eio.Buf_read.t -> Fastcgi_types.record
6363-6464-(** Parse begin request body from record content.
6565-6666- Extracts role and connection flags from the 8-byte Begin_request record
6767- body. The role indicates whether the application should act as a
6868- Responder, Authorizer, or Filter. The flags control connection lifetime
6969- management.
7070-7171- @param content Record content bytes (must be exactly 8 bytes)
7272- @return Begin request body with role and flags
7373- @raise Protocol_error if content length is wrong or role is unknown *)
7474-val parse_begin_request : bytes -> Fastcgi_types.begin_request_body
7575-7676-(** Parse end request body from record content.
7777-7878- Extracts application status and protocol status from the 8-byte
7979- End_request record body. The application status is similar to a
8080- program exit code, while protocol status indicates completion
8181- or rejection reasons.
8282-8383- @param content Record content bytes (must be exactly 8 bytes)
8484- @return End request body with status codes
8585- @raise Protocol_error if content length is wrong or status is invalid *)
8686-val parse_end_request : bytes -> Fastcgi_types.end_request_body
8787-8888-(** Parse name-value pairs from record content.
8989-9090- Decodes the compact binary encoding used for parameter transmission.
9191- Names and values can be up to 127 bytes (1-byte length) or longer
9292- (4-byte length with high bit set). This encoding allows efficient
9393- transmission of CGI environment variables and other parameters.
9494-9595- @param content Record content bytes containing encoded name-value pairs
9696- @return List of decoded name-value pairs
9797- @raise Protocol_error if the encoding is malformed *)
9898-val parse_name_value_pairs : bytes -> Fastcgi_types.name_value_pair list
9999-100100-(** {1 Protocol Serialization}
101101-102102- These functions serialize OCaml types into the binary FastCGI protocol
103103- format for transmission to web servers. All multi-byte integers are
104104- written in network byte order for platform independence. *)
105105-106106-(** Write a FastCGI record header to a buffer.
107107-108108- Serializes a record header into exactly 8 bytes using network byte order.
109109- The header format is: version (1 byte), type (1 byte), request ID (2 bytes),
110110- content length (2 bytes), padding length (1 byte), reserved (1 byte).
111111-112112- @param buf Output buffer to write the header to
113113- @param header Record header to serialize *)
114114-val write_record_header : Eio.Buf_write.t -> Fastcgi_types.record_header -> unit
115115-116116-(** Write a complete FastCGI record to a buffer.
117117-118118- Serializes a complete record including header, content data, and padding.
119119- This is the primary function for sending FastCGI records over network
120120- connections. The function ensures proper alignment by writing any
121121- specified padding bytes.
122122-123123- @param buf Output buffer to write the complete record to
124124- @param record Complete record with header, content, and optional padding *)
125125-val write_record : Eio.Buf_write.t -> Fastcgi_types.record -> unit
126126-127127-(** Write begin request body to bytes.
128128-129129- Serializes role and connection flags into the 8-byte Begin_request record
130130- body format. The first 2 bytes contain the role, the next byte contains
131131- flags, and the remaining 5 bytes are reserved (set to zero).
132132-133133- @param body Begin request body with role and flags
134134- @return 8-byte serialized record body *)
135135-val write_begin_request : Fastcgi_types.begin_request_body -> bytes
136136-137137-(** Write end request body to bytes.
138138-139139- Serializes application and protocol status into the 8-byte End_request
140140- record body format. The first 4 bytes contain the application status,
141141- the next byte contains the protocol status, and the remaining 3 bytes
142142- are reserved.
143143-144144- @param body End request body with status codes
145145- @return 8-byte serialized record body *)
146146-val write_end_request : Fastcgi_types.end_request_body -> bytes
147147-148148-(** Write name-value pairs to bytes.
149149-150150- Serializes name-value pairs using the compact binary encoding where
151151- each pair is encoded as: name length, value length, name data, value data.
152152- Lengths up to 127 bytes use 1-byte encoding; longer lengths use 4-byte
153153- encoding with the high bit set.
154154-155155- @param pairs List of name-value pairs to encode
156156- @return Serialized bytes containing all encoded pairs *)
157157-val write_name_value_pairs : Fastcgi_types.name_value_pair list -> bytes
158158-159159-(** {1 Type Conversions} *)
160160-161161-(** Convert record type to integer. *)
162162-val record_type_to_int : Fastcgi_types.record_type -> int
163163-164164-(** Convert integer to record type.
165165-166166- @param i Integer value
167167- @return Record type
168168- @raise Protocol_error if unknown *)
169169-val record_type_of_int : int -> Fastcgi_types.record_type
170170-171171-(** Convert role to integer. *)
172172-val role_to_int : Fastcgi_types.role -> int
173173-174174-(** Convert integer to role.
175175-176176- @param i Integer value
177177- @return Role
178178- @raise Protocol_error if unknown *)
179179-val role_of_int : int -> Fastcgi_types.role
180180-181181-(** Convert protocol status to integer. *)
182182-val protocol_status_to_int : Fastcgi_types.protocol_status -> int
183183-184184-(** Convert integer to protocol status.
185185-186186- @param i Integer value
187187- @return Protocol status
188188- @raise Protocol_error if unknown *)
189189-val protocol_status_of_int : int -> Fastcgi_types.protocol_status
190190-191191-(** {1 Length Encoding}
192192-193193- FastCGI uses a variable-length encoding for efficient transmission of
194194- length values in name-value pairs. This encoding minimizes overhead
195195- for short strings while supporting arbitrarily long values. *)
196196-197197-(** Encode a length value using FastCGI variable-length encoding.
198198-199199- Short lengths (0-127) are encoded in a single byte with the high bit
200200- clear. Longer lengths (128 and above) are encoded in 4 bytes with the
201201- high bit of the first byte set to 1, and the remaining 31 bits containing
202202- the actual length value.
203203-204204- This encoding provides optimal space efficiency for typical use cases
205205- where most parameter names and values are relatively short.
206206-207207- @param length Length value to encode (must be non-negative)
208208- @return 1-byte or 4-byte encoded length
209209- @raise Invalid_argument if length is negative *)
210210-val encode_length : int -> bytes
211211-212212-(** Decode a length value from FastCGI variable-length encoding.
213213-214214- Reads either 1 or 4 bytes from the buffer depending on the high bit
215215- of the first byte. Returns both the decoded length and the number of
216216- bytes consumed to allow proper buffer advancement.
217217-218218- @param buf Input buffer positioned at the start of an encoded length
219219- @return Tuple of (decoded length, bytes consumed)
220220- @raise Protocol_error if the encoding is invalid or buffer is too short *)
221221-val decode_length : Eio.Buf_read.t -> int * int
222222-223223-(** {1 Record Construction}
224224-225225- These convenience functions create properly formatted FastCGI records
226226- with correct headers and content. They handle the binary encoding details
227227- and ensure protocol compliance. *)
228228-229229-(** Create a begin request record.
230230-231231- Constructs a Begin_request record to initiate a new FastCGI request.
232232- This record type is sent by web servers to start request processing
233233- and specifies the role the application should play.
234234-235235- The record contains the application role (Responder, Authorizer, or Filter)
236236- and connection flags that control whether the connection should be kept
237237- alive after the request completes.
238238-239239- @param request_id Unique request identifier for multiplexing
240240- @param role Role the application should play for this request
241241- @param flags Connection management flags
242242- @return Complete Begin_request record ready for transmission *)
243243-val make_begin_request :
244244- request_id:Fastcgi_types.request_id ->
245245- role:Fastcgi_types.role ->
246246- flags:Fastcgi_types.connection_flags ->
247247- Fastcgi_types.record
248248-249249-(** Create an end request record.
250250-251251- Constructs an End_request record to complete a FastCGI request.
252252- This record is sent by applications to indicate request completion
253253- and provide both application-level and protocol-level status information.
254254-255255- The application status is similar to a program exit code, where 0
256256- indicates success and non-zero values indicate errors. The protocol
257257- status indicates whether the request was completed normally or rejected
258258- for protocol-related reasons.
259259-260260- @param request_id Request ID that is being completed
261261- @param app_status Application exit status (0 for success)
262262- @param protocol_status Protocol completion status
263263- @return Complete End_request record ready for transmission *)
264264-val make_end_request :
265265- request_id:Fastcgi_types.request_id ->
266266- app_status:Fastcgi_types.app_status ->
267267- protocol_status:Fastcgi_types.protocol_status ->
268268- Fastcgi_types.record
269269-270270-(** Create a params record.
271271-272272- @param request_id Request ID
273273- @param params Parameter name-value pairs
274274- @return Params record *)
275275-val make_params_record :
276276- request_id:Fastcgi_types.request_id ->
277277- params:(string * string) list ->
278278- Fastcgi_types.record
279279-280280-(** Create a stream record (stdin, stdout, stderr, data).
281281-282282- @param record_type Stream record type
283283- @param request_id Request ID
284284- @param data Stream data
285285- @return Stream record *)
286286-val make_stream_record :
287287- record_type:Fastcgi_types.record_type ->
288288- request_id:Fastcgi_types.request_id ->
289289- data:bytes ->
290290- Fastcgi_types.record
291291-292292-(** Create an empty stream record (marks end of stream).
293293-294294- @param record_type Stream record type
295295- @param request_id Request ID
296296- @return Empty stream record *)
297297-val make_empty_stream_record :
298298- record_type:Fastcgi_types.record_type ->
299299- request_id:Fastcgi_types.request_id ->
300300- Fastcgi_types.record
301301-302302-(** Create a get values record.
303303-304304- @param variables Variable names to query
305305- @return Get values record *)
306306-val make_get_values_record :
307307- variables:string list ->
308308- Fastcgi_types.record
309309-310310-(** Create a get values result record.
311311-312312- @param values Variable name-value pairs
313313- @return Get values result record *)
314314-val make_get_values_result_record :
315315- values:(string * string) list ->
316316- Fastcgi_types.record
317317-318318-(** Create an unknown type record.
319319-320320- @param unknown_type Unknown record type value
321321- @return Unknown type record *)
322322-val make_unknown_type_record :
323323- unknown_type:int ->
324324- Fastcgi_types.record
325325-326326-(** Create an abort request record.
327327-328328- @param request_id Request ID to abort
329329- @return Abort request record *)
330330-val make_abort_request_record :
331331- request_id:Fastcgi_types.request_id ->
332332- Fastcgi_types.record
333333-334334-(** {1 Validation}
335335-336336- These functions validate protocol elements for compliance with FastCGI
337337- requirements. They help detect malformed data and ensure protocol
338338- correctness during parsing and construction. *)
339339-340340-(** Validate a record header.
341341-342342- Checks that a record header contains valid values for all fields.
343343- This includes verifying the protocol version, record type bounds,
344344- content length limits, and padding length constraints.
345345-346346- @param header Record header to validate
347347- @return True if the header is valid and protocol-compliant
348348- @raise Protocol_error if any field contains invalid values *)
349349-val validate_record_header : Fastcgi_types.record_header -> bool
350350-351351-(** Validate a complete record.
352352-353353- Performs comprehensive validation of a complete record including
354354- header validation and content/padding length consistency checks.
355355- Ensures the record can be safely transmitted or processed.
356356-357357- @param record Complete record to validate
358358- @return True if the entire record is valid and well-formed
359359- @raise Protocol_error if any part of the record is invalid *)
360360-val validate_record : Fastcgi_types.record -> bool
361361-362362-(** Check if a record type is a management record.
363363-364364- Management records use request ID 0 and contain protocol-level information
365365- such as capability queries (Get_values) and unknown type responses.
366366- They are not associated with any specific request.
367367-368368- @param record_type Record type to classify
369369- @return True if this is a management record type *)
370370-val is_management_record : Fastcgi_types.record_type -> bool
371371-372372-(** Check if a record type is a stream record.
373373-374374- Stream records (Params, Stdin, Stdout, Stderr, Data) can be sent in
375375- multiple parts and are terminated with an empty record of the same type.
376376- This allows streaming of arbitrarily large data without buffering.
377377-378378- @param record_type Record type to classify
379379- @return True if this is a stream record type *)
380380-val is_stream_record : Fastcgi_types.record_type -> bool
381381-382382-(** {1 Protocol Constants}
383383-384384- Essential constants and utility functions for FastCGI protocol
385385- implementation. These values are defined by the protocol specification. *)
386386-387387-(** Null request ID for management records.
388388-389389- Management records always use request ID 0 to indicate they are not
390390- associated with any specific request. Applications must use non-zero
391391- request IDs for all application records. *)
392392-val null_request_id : Fastcgi_types.request_id
393393-394394-(** Maximum content length for a single record.
395395-396396- FastCGI records can contain at most 65535 bytes of content data.
397397- Larger data must be split across multiple records of the same type. *)
398398-val max_content_length : int
399399-400400-(** Maximum padding length for a record.
401401-402402- Records can have at most 255 bytes of padding for alignment purposes.
403403- Padding bytes are ignored by the receiver. *)
404404-val max_padding_length : int
405405-406406-(** Calculate optimal padding for 8-byte alignment.
407407-408408- Computes the number of padding bytes needed to align the total record
409409- size to an 8-byte boundary. This provides optimal performance on most
410410- architectures by ensuring proper memory alignment.
411411-412412- @param content_length Length of the record content data
413413- @return Number of padding bytes needed (0-7) *)
414414-val calculate_padding : int -> int
-222
lib/fastcgi_responder.mli
···11-(** Responder role implementation for FastCGI.
22-33- The Responder role is the most common FastCGI role, equivalent to
44- traditional CGI applications. It handles HTTP requests and generates
55- HTTP responses.
66-77- A Responder FastCGI application has the same purpose as a CGI/1.1 program:
88- it receives all the information associated with an HTTP request and generates
99- an HTTP response. This includes CGI environment variables, request headers,
1010- request body data, and produces HTTP response headers and body content.
1111-1212- The key difference from traditional CGI is that Responder applications are
1313- long-lived processes that can handle multiple requests efficiently, avoiding
1414- the overhead of process creation for each request.
1515-1616- This module provides high-level abstractions for working with HTTP requests
1717- and responses, automatically handling the conversion between FastCGI protocol
1818- elements and familiar HTTP concepts. *)
1919-2020-(** {1 HTTP Request/Response Types} *)
2121-2222-(** CGI-style environment variables.
2323-2424- A list of name-value pairs containing the CGI environment variables
2525- passed from the web server. These typically include variables like
2626- REQUEST_METHOD, REQUEST_URI, HTTP_HOST, CONTENT_TYPE, etc. *)
2727-type cgi_env = (string * string) list
2828-2929-(** HTTP request information extracted from FastCGI parameters.
3030-3131- This type represents an HTTP request in a convenient form for application
3232- processing. It extracts and parses the most commonly needed information
3333- from the CGI environment variables provided by the web server. *)
3434-type 'a http_request = {
3535- method_ : string; (** HTTP method (GET, POST, PUT, DELETE, etc.).
3636- Extracted from REQUEST_METHOD CGI variable. *)
3737- uri : string; (** Request URI path component, without query string.
3838- Extracted from REQUEST_URI or SCRIPT_NAME + PATH_INFO. *)
3939- query_string : string; (** URL query string parameters (after the '?' in the URL).
4040- Extracted from QUERY_STRING CGI variable. *)
4141- content_type : string option; (** MIME type of the request body, if present.
4242- Extracted from CONTENT_TYPE CGI variable. *)
4343- content_length : int option; (** Length of the request body in bytes, if known.
4444- Extracted from CONTENT_LENGTH CGI variable. *)
4545- headers : (string * string) list; (** HTTP headers sent by the client. Extracted from
4646- CGI variables with HTTP_ prefix. *)
4747- body : 'a Eio.Flow.source; (** Request body data stream. For POST requests,
4848- this contains form data or payload content. *)
4949-}
5050-5151-(** HTTP response builder for constructing responses.
5252-5353- This type provides a streaming interface for generating HTTP responses.
5454- It handles proper HTTP formatting including status line, headers, and
5555- body content. The response is written incrementally to avoid buffering
5656- large responses in memory. *)
5757-type 'a http_response = {
5858- write_status : int -> unit; (** Set the HTTP status code (200, 404, 500, etc.).
5959- Must be called before writing headers or body. *)
6060- write_header : string -> string -> unit; (** Add an HTTP response header.
6161- Common headers include Content-Type, Set-Cookie, etc. *)
6262- write_body : string -> unit; (** Write string content to the response body.
6363- Handles encoding and streaming automatically. *)
6464- write_body_chunk : bytes -> unit; (** Write binary data to the response body.
6565- Useful for file downloads or binary content. *)
6666- finish : unit -> unit; (** Complete the response and close the connection.
6767- Must be called to ensure proper termination. *)
6868-}
6969-7070-(** {1 Conversion Functions} *)
7171-7272-(** Convert FastCGI request to HTTP request.
7373-7474- Extracts HTTP-specific information from FastCGI parameters
7575- and creates an HTTP request object.
7676-7777- @param request FastCGI request
7878- @return HTTP request with extracted information *)
7979-val request_of_fastcgi : 'a Fastcgi_types.request -> 'a http_request
8080-8181-(** Create HTTP response writer from FastCGI response.
8282-8383- Wraps FastCGI response streams to provide HTTP-style
8484- response writing interface.
8585-8686- @param response FastCGI response
8787- @return HTTP response writer *)
8888-val response_of_fastcgi : 'a Fastcgi_types.response -> 'a http_response
8989-9090-(** {1 Handler Utilities} *)
9191-9292-(** Responder handler type. *)
9393-type 'a responder_handler = 'a Fastcgi_types.request -> 'a Fastcgi_types.response -> Fastcgi_types.response_result
9494-9595-(** Convenience handler wrapper for HTTP-style handlers.
9696-9797- Converts an HTTP-style handler function into a FastCGI handler.
9898- This allows writing handlers that work with HTTP request/response
9999- objects instead of raw FastCGI types.
100100-101101- @param handler HTTP handler function
102102- @return FastCGI responder handler *)
103103-val http_handler :
104104- ('a http_request -> 'a http_response -> unit) ->
105105- 'a responder_handler
106106-107107-(** {1 Common HTTP Operations} *)
108108-109109-(** Send a simple text response.
110110-111111- @param response HTTP response writer
112112- @param status HTTP status code
113113- @param content_type MIME type
114114- @param body Response body text *)
115115-val send_text_response :
116116- 'a http_response ->
117117- status:int ->
118118- content_type:string ->
119119- body:string ->
120120- unit
121121-122122-(** Send a JSON response.
123123-124124- @param response HTTP response writer
125125- @param status HTTP status code
126126- @param json JSON string *)
127127-val send_json_response :
128128- 'a http_response ->
129129- status:int ->
130130- json:string ->
131131- unit
132132-133133-(** Send an HTML response.
134134-135135- @param response HTTP response writer
136136- @param status HTTP status code
137137- @param html HTML content *)
138138-val send_html_response :
139139- 'a http_response ->
140140- status:int ->
141141- html:string ->
142142- unit
143143-144144-(** Send an error response.
145145-146146- @param response HTTP response writer
147147- @param status HTTP error status code
148148- @param message Error message *)
149149-val send_error_response :
150150- 'a http_response ->
151151- status:int ->
152152- message:string ->
153153- unit
154154-155155-(** Send a redirect response.
156156-157157- @param response HTTP response writer
158158- @param status Redirect status code (301, 302, etc.)
159159- @param location Target URL *)
160160-val send_redirect_response :
161161- 'a http_response ->
162162- status:int ->
163163- location:string ->
164164- unit
165165-166166-(** {1 Request Parsing} *)
167167-168168-(** Parse query string into parameter map.
169169-170170- @param query_string URL-encoded query string
171171- @return Association list of parameter name-value pairs *)
172172-val parse_query_string : string -> (string * string) list
173173-174174-(** Parse form data from request body.
175175-176176- Supports both application/x-www-form-urlencoded and
177177- multipart/form-data content types.
178178-179179- @param request HTTP request
180180- @return Association list of form field name-value pairs *)
181181-val parse_form_data : 'a http_request -> (string * string) list
182182-183183-(** Get request header value.
184184-185185- @param request HTTP request
186186- @param name Header name (case-insensitive)
187187- @return Header value if present *)
188188-val get_header : 'a http_request -> string -> string option
189189-190190-(** Get request parameter from query string or form data.
191191-192192- @param request HTTP request
193193- @param name Parameter name
194194- @return Parameter value if present *)
195195-val get_param : 'a http_request -> string -> string option
196196-197197-(** {1 File Operations} *)
198198-199199-(** File upload information. *)
200200-type 'a file_upload = {
201201- filename : string option; (** Original filename *)
202202- content_type : string option; (** MIME type *)
203203- size : int; (** File size in bytes *)
204204- data : 'a Eio.Flow.source; (** File content stream *)
205205-}
206206-207207-(** Parse file uploads from multipart form data.
208208-209209- @param request HTTP request with multipart content
210210- @return Association list of field name to file upload *)
211211-val parse_file_uploads : 'a http_request -> (string * 'a file_upload) list
212212-213213-(** Save uploaded file to filesystem.
214214-215215- @param fs Filesystem capability
216216- @param path Destination path
217217- @param upload File upload to save *)
218218-val save_upload :
219219- fs:Eio.Fs.dir_ty Eio.Path.t ->
220220- path:string ->
221221- upload:'a file_upload ->
222222- unit
-444
lib/fastcgi_types.mli
···11-(** Core FastCGI types and constants.
22-33- This module defines the fundamental types used throughout the
44- FastCGI protocol implementation.
55-66- FastCGI is an open extension to CGI that provides high performance for
77- all Internet applications without the penalties of Web server APIs.
88- Unlike traditional CGI programs that are started for each request,
99- FastCGI applications are long-lived processes that can handle multiple
1010- requests over persistent connections. *)
1111-1212-(** {1 Core Types} *)
1313-1414-(** FastCGI protocol version.
1515-1616- The current protocol uses version 1. Future versions of the
1717- protocol may increment this value. Applications should check the version
1818- field in incoming records and reject unsupported versions.
1919-2020- Version 1 is represented by the value [1]. *)
2121-type version = int
2222-2323-(** FastCGI record types.
2424-2525- All data transmitted over a FastCGI connection is packaged in records.
2626- Each record has a type that determines how the record's content should
2727- be interpreted. The protocol defines both management records (for
2828- protocol-level communication) and application records (for request
2929- processing).
3030-3131- Management records contain information that is not specific to any web
3232- server request, such as capability queries and unknown type responses.
3333- Application records contain information about a particular request,
3434- identified by a non-zero request ID. *)
3535-type record_type =
3636- | Begin_request (** Starts a new request. Sent by the web server to begin
3737- processing. Contains the role and connection flags. *)
3838- | Abort_request (** Aborts an existing request. Sent by the web server
3939- when an HTTP client closes its connection while the
4040- request is still being processed. *)
4141- | End_request (** Completes a request. Sent by the application to
4242- terminate processing, either normally or due to
4343- an error condition. *)
4444- | Params (** Parameter name-value pairs. Used to transmit CGI-style
4545- environment variables from the web server to the
4646- application. Sent as a stream. *)
4747- | Stdin (** Standard input data. Contains the request body data
4848- that would normally be available on stdin in CGI.
4949- Sent as a stream. *)
5050- | Stdout (** Standard output data. Response data from the application
5151- to the web server. This becomes the HTTP response.
5252- Sent as a stream. *)
5353- | Stderr (** Standard error data. Error messages from the application.
5454- Used for logging and debugging. Sent as a stream. *)
5555- | Data (** Additional data stream. Used only in the Filter role
5656- to transmit file data that needs to be filtered.
5757- Sent as a stream. *)
5858- | Get_values (** Management record to query application variables.
5959- Allows the web server to discover application
6060- capabilities like max connections and multiplexing. *)
6161- | Get_values_result (** Management record response to Get_values. Contains
6262- the requested variable values. *)
6363- | Unknown_type (** Management record for unknown type handling. Sent by
6464- the application when it receives a record type it
6565- doesn't understand. *)
6666-6767-(** FastCGI application roles.
6868-6969- A FastCGI application can play one of several well-defined roles.
7070- The role determines what information the application receives and
7171- what it's expected to produce.
7272-7373- A FastCGI application plays one of several well-defined roles. The most
7474- familiar is the Responder role, in which the application receives all
7575- the information associated with an HTTP request and generates an HTTP
7676- response. *)
7777-type role =
7878- | Responder (** The most common role, equivalent to traditional CGI.
7979- The application receives all information associated with
8080- an HTTP request and generates an HTTP response. This
8181- includes environment variables, request headers, and
8282- request body data. *)
8383- | Authorizer (** Performs access control decisions. The application
8484- receives HTTP request information and generates an
8585- authorized/unauthorized decision. In case of authorization,
8686- it can associate additional variables with the request.
8787- For unauthorized requests, it provides a complete HTTP
8888- error response. *)
8989- | Filter (** Processes data streams with filtering. The application
9090- receives HTTP request information plus an additional
9191- data stream from a file stored on the web server, and
9292- generates a "filtered" version of the data as an HTTP
9393- response. Both the file and the filter can be access
9494- controlled. *)
9595-9696-(** Request ID for multiplexing multiple requests over a single connection.
9797-9898- FastCGI supports multiplexing, allowing multiple concurrent requests
9999- to be processed over a single transport connection. Each request is
100100- identified by a unique request ID within the scope of that connection.
101101-102102- The Web server re-uses FastCGI request IDs; the application keeps track
103103- of the current state of each request ID on a given transport connection.
104104- Request IDs should be small integers to allow efficient tracking using
105105- arrays rather than hash tables.
106106-107107- The value [0] is reserved for management records and is called the
108108- "null request ID". *)
109109-type request_id = int
110110-111111-(** Application-level status code.
112112-113113- When an application completes a request, it provides an application
114114- status code similar to the exit status of a traditional CGI program.
115115- A value of [0] indicates success, while non-zero values indicate
116116- various error conditions.
117117-118118- The application sets the appStatus component to the status code that
119119- the CGI program would have returned via the exit system call. *)
120120-type app_status = int
121121-122122-(** Protocol-level status codes.
123123-124124- In addition to application status, each request completion includes
125125- a protocol-level status that indicates whether the request was
126126- processed normally or rejected for protocol-related reasons.
127127-128128- These status codes allow the web server to understand why a request
129129- was not processed and take appropriate action. *)
130130-type protocol_status =
131131- | Request_complete (** Normal end of request. The application successfully
132132- processed the request and the appStatus field
133133- indicates the application-level result. *)
134134- | Cant_mpx_conn (** Rejecting a new request because the application
135135- cannot multiplex connections. This happens when
136136- a web server sends concurrent requests over one
137137- connection to an application that processes only
138138- one request at a time per connection. *)
139139- | Overloaded (** Rejecting a new request because the application
140140- is overloaded. This occurs when the application
141141- runs out of some resource, such as database
142142- connections or memory. *)
143143- | Unknown_role (** Rejecting a new request because the requested
144144- role is unknown to the application. This happens
145145- when the web server specifies a role that the
146146- application doesn't implement. *)
147147-148148-(** Connection flags for controlling connection behavior.
149149-150150- These flags are sent in Begin_request records to control how the
151151- connection should be managed after the request completes. *)
152152-type connection_flags = {
153153- keep_conn : bool; (** Keep connection open after request completion.
154154-155155- If false, the application closes the connection after
156156- responding to this request. If true, the application
157157- does not close the connection after responding to this
158158- request; the Web server retains responsibility for the
159159- connection.
160160-161161- This flag enables connection reuse for better performance,
162162- especially important for high-traffic applications. *)
163163-}
164164-165165-(** {1 Record Types} *)
166166-167167-(** FastCGI record header.
168168-169169- Every FastCGI record begins with an 8-byte header that identifies
170170- the record type, request ID, and content length. This fixed-length
171171- prefix allows efficient parsing and proper demultiplexing of records.
172172-173173- A FastCGI record consists of a fixed-length prefix followed by a
174174- variable number of content and padding bytes.
175175-176176- The header format is platform-independent and uses network byte order
177177- for multi-byte integers. *)
178178-type record_header = {
179179- version : int; (** Protocol version. Must be [1] for this specification.
180180- Future versions may increment this value. *)
181181- record_type : record_type; (** Type of this record, determining how to interpret
182182- the content data. *)
183183- request_id : request_id; (** Request ID this record belongs to. Value [0] is
184184- reserved for management records. *)
185185- content_length : int; (** Number of bytes in the content data. Must be
186186- between [0] and [65535]. *)
187187- padding_length : int; (** Number of padding bytes following content.
188188- Must be between [0] and [255]. Used for alignment. *)
189189-}
190190-191191-(** Begin request record body.
192192-193193- This record marks the start of a new request and specifies the role
194194- the application should play and connection management flags.
195195-196196- The Web server sends a FCGI_BEGIN_REQUEST record to start a request.
197197- The record body contains the role and flags that control request
198198- processing. *)
199199-type begin_request_body = {
200200- role : role; (** The role the web server expects the application
201201- to play for this request. *)
202202- flags : connection_flags; (** Flags controlling connection behavior after
203203- request completion. *)
204204-}
205205-206206-(** End request record body.
207207-208208- This record marks the completion of a request and provides both
209209- application-level and protocol-level status information.
210210-211211- The application sends a FCGI_END_REQUEST record to terminate a request,
212212- either because the application has processed the request or because the
213213- application has rejected the request. *)
214214-type end_request_body = {
215215- app_status : app_status; (** Application-level status code, similar to
216216- a CGI program's exit status. *)
217217- protocol_status : protocol_status; (** Protocol-level status indicating normal
218218- completion or rejection reason. *)
219219-}
220220-221221-(** Complete FastCGI record.
222222-223223- A complete record consists of the header, content data, and optional
224224- padding. The content interpretation depends on the record type.
225225-226226- Records support padding to allow senders to keep data aligned for more
227227- efficient processing. Experience with the X window system protocols shows
228228- the performance benefit of such alignment. *)
229229-type record = {
230230- header : record_header; (** Fixed 8-byte header with record metadata. *)
231231- content : bytes; (** Variable-length content data. Length must match
232232- header.content_length. *)
233233- padding : bytes option; (** Optional padding data for alignment. Length must
234234- match header.padding_length if present. *)
235235-}
236236-237237-(** Name-value pair for parameters.
238238-239239- FastCGI uses a compact binary encoding for transmitting name-value pairs
240240- such as CGI environment variables. This encoding supports both short
241241- and long names/values efficiently.
242242-243243- FastCGI transmits a name-value pair as the length of the name, followed
244244- by the length of the value, followed by the name, followed by the value.
245245- Lengths of 127 bytes and less can be encoded in one byte, while longer
246246- lengths are always encoded in four bytes. *)
247247-type name_value_pair = {
248248- name : string; (** Parameter name. For CGI compatibility, these are typically
249249- uppercase environment variable names like "REQUEST_METHOD". *)
250250- value : string; (** Parameter value. The value does not include a terminating
251251- null byte in the FastCGI encoding. *)
252252-}
253253-254254-(** {1 Request/Response Model} *)
255255-256256-(** Request context containing all information for a FastCGI request.
257257-258258- This high-level type aggregates all the information needed to process
259259- a FastCGI request. It combines the protocol-level details (request ID,
260260- role, flags) with the application data (parameters and input streams).
261261-262262- The request follows a two-level processing model: First, the protocol
263263- multiplexes a single transport connection between several independent
264264- FastCGI requests. Second, within each request the protocol provides
265265- several independent data streams in each direction. *)
266266-type 'a request = {
267267- request_id : request_id; (** Unique identifier for this request within
268268- the connection scope. Used for multiplexing. *)
269269- role : role; (** The role this application should play for
270270- this request (Responder, Authorizer, or Filter). *)
271271- flags : connection_flags; (** Connection management flags from the web server. *)
272272- params : (string * string) list; (** CGI-style environment variables transmitted
273273- via FCGI_PARAMS records. These provide request
274274- context like HTTP headers, server info, etc. *)
275275- stdin : 'a Eio.Flow.source; (** Standard input stream, containing request body
276276- data (equivalent to CGI stdin). For HTTP POST
277277- requests, this contains the form data or payload. *)
278278- data : 'a Eio.Flow.source option; (** Additional data stream, used only in Filter
279279- role. Contains file data that needs to be
280280- filtered. [None] for Responder and Authorizer. *)
281281-}
282282-283283-(** Response builder for constructing FastCGI responses.
284284-285285- This type provides the output streams for sending response data back
286286- to the web server. Following CGI conventions, it separates normal
287287- output from error output.
288288-289289- Both stdout and stderr data pass over a single transport connection from
290290- the application to the Web server, rather than requiring separate pipes
291291- as with CGI/1.1. *)
292292-type 'a response = {
293293- stdout : 'a Eio.Flow.sink; (** Standard output stream for response data.
294294- In Responder role, this contains the HTTP
295295- response including headers and body. *)
296296- stderr : 'a Eio.Flow.sink; (** Standard error stream for logging and debugging.
297297- All role protocols use the FCGI_STDERR stream
298298- just the way stderr is used in conventional
299299- applications programming: to report application-level
300300- errors in an intelligible way. *)
301301-}
302302-303303-(** Complete response with status.
304304-305305- When an application finishes processing a request, it must provide
306306- both application-level and protocol-level status information. This
307307- allows the web server to understand the outcome and take appropriate
308308- action.
309309-310310- This corresponds to the FCGI_END_REQUEST record sent by the application. *)
311311-type response_result = {
312312- app_status : app_status; (** Application exit status, equivalent to what
313313- a CGI program would return via exit(). *)
314314- protocol_status : protocol_status; (** Protocol-level completion status, indicating
315315- normal completion or various rejection reasons. *)
316316-}
317317-318318-(** {1 Protocol Constants} *)
319319-320320-(** Protocol version constant.
321321-322322- This constant represents FCGI_VERSION_1. This value should be used in
323323- the version field of all record headers. *)
324324-val version_1 : int
325325-326326-(** Standard file descriptor for FastCGI listening socket.
327327-328328- The Web server leaves a single file descriptor, FCGI_LISTENSOCK_FILENO,
329329- open when the application begins execution. This descriptor refers to a
330330- listening socket created by the Web server.
331331-332332- The value equals STDIN_FILENO (0). Applications can distinguish between
333333- CGI and FastCGI invocation by calling getpeername() on this descriptor. *)
334334-val listensock_fileno : int
335335-336336-(** {2 Record Type Constants}
337337-338338- These integer constants correspond to the record_type variants and are
339339- used in the binary protocol encoding. These are the values transmitted
340340- in the type field of record headers. *)
341341-342342-(** Value [1]. Starts a new request. *)
343343-val fcgi_begin_request : int
344344-345345-(** Value [2]. Aborts an existing request. *)
346346-val fcgi_abort_request : int
347347-348348-(** Value [3]. Completes a request. *)
349349-val fcgi_end_request : int
350350-351351-(** Value [4]. Parameter name-value pairs. *)
352352-val fcgi_params : int
353353-354354-(** Value [5]. Standard input data. *)
355355-val fcgi_stdin : int
356356-357357-(** Value [6]. Standard output data. *)
358358-val fcgi_stdout : int
359359-360360-(** Value [7]. Standard error data. *)
361361-val fcgi_stderr : int
362362-363363-(** Value [8]. Additional data stream (Filter). *)
364364-val fcgi_data : int
365365-366366-(** Value [9]. Query application variables. *)
367367-val fcgi_get_values : int
368368-369369-(** Value [10]. Response to Get_values. *)
370370-val fcgi_get_values_result : int
371371-372372-(** Value [11]. Unknown record type response. *)
373373-val fcgi_unknown_type : int
374374-375375-(** {2 Role Constants}
376376-377377- These integer constants correspond to the role variants and are used
378378- in Begin_request record bodies. These identify the role the web server
379379- expects the application to play. *)
380380-381381-(** Value [1]. Handle HTTP requests and generate responses. *)
382382-val fcgi_responder : int
383383-384384-(** Value [2]. Perform authorization decisions. *)
385385-val fcgi_authorizer : int
386386-387387-(** Value [3]. Process data streams with filtering. *)
388388-val fcgi_filter : int
389389-390390-(** {2 Flag Constants}
391391-392392- These constants are used in the flags field of Begin_request records
393393- to control connection behavior. *)
394394-395395-(** Value [1]. Keep connection open after request.
396396- If zero, the application closes the connection after
397397- responding to this request. If not zero, the application
398398- does not close the connection after responding to this
399399- request. *)
400400-val fcgi_keep_conn : int
401401-402402-(** {2 Protocol Limits}
403403-404404- These constants define the maximum sizes for various protocol elements,
405405- ensuring compatibility and preventing buffer overflows. *)
406406-407407-(** Value [65535]. Maximum bytes in record content.
408408- Between 0 and 65535 bytes of data, interpreted
409409- according to the record type. *)
410410-val max_content_length : int
411411-412412-(** Value [255]. Maximum bytes in record padding.
413413- Between 0 and 255 bytes of data, which are ignored. *)
414414-val max_padding_length : int
415415-416416-(** Value [8]. Fixed size of record headers.
417417- Number of bytes in a FCGI_Header. Future versions
418418- of the protocol will not reduce this number. *)
419419-val header_length : int
420420-421421-(** {2 Management Record Variables}
422422-423423- These string constants identify the standard management variables that
424424- can be queried using FCGI_GET_VALUES records. They allow the web server
425425- to discover application capabilities.
426426-427427- The initial set provides information to help the server perform application
428428- and connection management. *)
429429-430430-(** Variable name "FCGI_MAX_CONNS". The maximum number
431431- of concurrent transport connections this application
432432- will accept, e.g. "1" or "10". *)
433433-val fcgi_max_conns : string
434434-435435-(** Variable name "FCGI_MAX_REQS". The maximum number
436436- of concurrent requests this application will accept,
437437- e.g. "1" or "50". *)
438438-val fcgi_max_reqs : string
439439-440440-(** Variable name "FCGI_MPXS_CONNS". "0" if this
441441- application does not multiplex connections (i.e.
442442- handle concurrent requests over each connection),
443443- "1" otherwise. *)
444444-val fcgi_mpxs_conns : string
-612
spec/OCAML.md
···11-# OCaml FastCGI Specification
22-33-This document specifies an OCaml interface for the FastCGI protocol using the Eio effects-based IO library. The design follows Eio conventions for structured concurrency, capability-based security, and resource management.
44-55-## Table of Contents
66-77-1. [Overview](#overview)
88-2. [Core Types](#core-types)
99-3. [Protocol Constants](#protocol-constants)
1010-4. [Record Types](#record-types)
1111-5. [Request/Response Model](#requestresponse-model)
1212-6. [Application Interface](#application-interface)
1313-7. [Connection Management](#connection-management)
1414-8. [Role Implementations](#role-implementations)
1515-9. [Error Handling](#error-handling)
1616-10. [Resource Management](#resource-management)
1717-11. [Examples](#examples)
1818-1919-## Overview
2020-2121-The OCaml FastCGI implementation provides a high-level, type-safe interface for building FastCGI applications that can handle multiple concurrent requests efficiently. It leverages OCaml 5's effects system through Eio for structured concurrency and resource safety.
2222-2323-### Key Design Principles
2424-2525-- **Capability-based**: All external resources (network, filesystem) are explicitly passed as capabilities
2626-- **Structured concurrency**: Use Eio switches for automatic resource cleanup
2727-- **Type safety**: Leverage OCaml's type system to prevent protocol errors
2828-- **Performance**: Support connection multiplexing and keep-alive for efficiency
2929-- **Extensibility**: Support all three FastCGI roles with room for future extensions
3030-3131-## Core Types
3232-3333-```ocaml
3434-(** FastCGI protocol version *)
3535-type version = int
3636-3737-(** FastCGI record types *)
3838-type record_type =
3939- | Begin_request
4040- | Abort_request
4141- | End_request
4242- | Params
4343- | Stdin
4444- | Stdout
4545- | Stderr
4646- | Data
4747- | Get_values
4848- | Get_values_result
4949- | Unknown_type
5050-5151-(** FastCGI roles *)
5252-type role =
5353- | Responder (** Handle HTTP requests and generate responses *)
5454- | Authorizer (** Perform authorization decisions *)
5555- | Filter (** Process data streams with filtering *)
5656-5757-(** Request ID for multiplexing *)
5858-type request_id = int
5959-6060-(** Application status code *)
6161-type app_status = int
6262-6363-(** Protocol status codes *)
6464-type protocol_status =
6565- | Request_complete (** Normal completion *)
6666- | Cant_mpx_conn (** Cannot multiplex connection *)
6767- | Overloaded (** Application overloaded *)
6868- | Unknown_role (** Unknown role requested *)
6969-7070-(** Connection flags *)
7171-type connection_flags = {
7272- keep_conn : bool; (** Keep connection open after request *)
7373-}
7474-```
7575-7676-## Protocol Constants
7777-7878-```ocaml
7979-module Constants = struct
8080- (** Protocol version *)
8181- let version_1 = 1
8282-8383- (** Standard file descriptor for FastCGI listening socket *)
8484- let listensock_fileno = 0
8585-8686- (** Record type constants *)
8787- let fcgi_begin_request = 1
8888- let fcgi_abort_request = 2
8989- let fcgi_end_request = 3
9090- let fcgi_params = 4
9191- let fcgi_stdin = 5
9292- let fcgi_stdout = 6
9393- let fcgi_stderr = 7
9494- let fcgi_data = 8
9595- let fcgi_get_values = 9
9696- let fcgi_get_values_result = 10
9797- let fcgi_unknown_type = 11
9898-9999- (** Role constants *)
100100- let fcgi_responder = 1
101101- let fcgi_authorizer = 2
102102- let fcgi_filter = 3
103103-104104- (** Flag constants *)
105105- let fcgi_keep_conn = 1
106106-107107- (** Maximum sizes *)
108108- let max_content_length = 65535
109109- let max_padding_length = 255
110110- let header_length = 8
111111-112112- (** Management record variables *)
113113- let fcgi_max_conns = "FCGI_MAX_CONNS"
114114- let fcgi_max_reqs = "FCGI_MAX_REQS"
115115- let fcgi_mpxs_conns = "FCGI_MPXS_CONNS"
116116-end
117117-```
118118-119119-## Record Types
120120-121121-```ocaml
122122-(** FastCGI record header *)
123123-type record_header = {
124124- version : int;
125125- record_type : record_type;
126126- request_id : request_id;
127127- content_length : int;
128128- padding_length : int;
129129-}
130130-131131-(** Begin request record body *)
132132-type begin_request_body = {
133133- role : role;
134134- flags : connection_flags;
135135-}
136136-137137-(** End request record body *)
138138-type end_request_body = {
139139- app_status : app_status;
140140- protocol_status : protocol_status;
141141-}
142142-143143-(** Complete FastCGI record *)
144144-type record = {
145145- header : record_header;
146146- content : bytes;
147147- padding : bytes option;
148148-}
149149-150150-(** Name-value pair for parameters *)
151151-type name_value_pair = {
152152- name : string;
153153- value : string;
154154-}
155155-```
156156-157157-## Request/Response Model
158158-159159-```ocaml
160160-(** Request context containing all information for a FastCGI request *)
161161-type 'a request = {
162162- request_id : request_id;
163163- role : role;
164164- flags : connection_flags;
165165- params : (string * string) list;
166166- stdin : 'a Eio.Flow.source;
167167- data : 'a Eio.Flow.source option; (** Only for Filter role *)
168168-}
169169-170170-(** Response builder for constructing FastCGI responses *)
171171-type 'a response = {
172172- stdout : 'a Eio.Flow.sink;
173173- stderr : 'a Eio.Flow.sink;
174174-}
175175-176176-(** Complete response with status *)
177177-type response_result = {
178178- app_status : app_status;
179179- protocol_status : protocol_status;
180180-}
181181-```
182182-183183-## Application Interface
184184-185185-```ocaml
186186-(** Application handler signature *)
187187-module Handler = struct
188188- (** Responder handler: process HTTP request and generate response *)
189189- type 'a responder = 'a request -> 'a response -> response_result
190190-191191- (** Authorizer handler: make authorization decision *)
192192- type 'a authorizer = 'a request -> 'a response -> response_result
193193-194194- (** Filter handler: process data stream with filtering *)
195195- type 'a filter = 'a request -> 'a response -> response_result
196196-197197- (** Generic handler that can handle any role *)
198198- type 'a handler =
199199- | Responder of 'a responder
200200- | Authorizer of 'a authorizer
201201- | Filter of 'a filter
202202-end
203203-204204-(** Application configuration *)
205205-type 'a app_config = {
206206- max_connections : int; (** Maximum concurrent connections *)
207207- max_requests : int; (** Maximum concurrent requests *)
208208- multiplex_connections : bool; (** Support connection multiplexing *)
209209- handler : 'a Handler.handler; (** Application request handler *)
210210-}
211211-```
212212-213213-## Connection Management
214214-215215-```ocaml
216216-(** Connection manager for handling FastCGI protocol *)
217217-module Connection = struct
218218- (** Opaque connection type *)
219219- type 'a t
220220-221221- (** Create a connection from a network flow *)
222222- val create : sw:Eio.Switch.t -> 'a Eio.Flow.two_way -> 'a t
223223-224224- (** Accept and process a single request on the connection *)
225225- val process_request : 'a t -> 'a Handler.handler -> response_result Eio.Promise.t
226226-227227- (** Get connection statistics *)
228228- val stats : 'a t -> {
229229- active_requests : int;
230230- total_requests : int;
231231- bytes_sent : int;
232232- bytes_received : int;
233233- }
234234-235235- (** Close the connection gracefully *)
236236- val close : 'a t -> unit
237237-end
238238-239239-(** FastCGI server for accepting and managing connections *)
240240-module Server = struct
241241- (** Server configuration *)
242242- type 'a config = {
243243- app : 'a app_config;
244244- listen_address : [
245245- | `Unix of string (** Unix domain socket path *)
246246- | `Tcp of Eio.Net.Ipaddr.t * int (** TCP address and port *)
247247- ];
248248- backlog : int; (** Listen backlog size *)
249249- max_connections : int; (** Maximum concurrent connections *)
250250- }
251251-252252- (** Run a FastCGI server *)
253253- val run :
254254- sw:Eio.Switch.t ->
255255- net:'a Eio.Net.t ->
256256- 'a config ->
257257- unit
258258-259259- (** Run server with default configuration *)
260260- val run_default :
261261- sw:Eio.Switch.t ->
262262- net:'a Eio.Net.t ->
263263- handler:'a Handler.handler ->
264264- listen_address:[`Unix of string | `Tcp of Eio.Net.Ipaddr.t * int] ->
265265- unit
266266-end
267267-```
268268-269269-## Role Implementations
270270-271271-### Responder
272272-273273-```ocaml
274274-(** Responder role implementation *)
275275-module Responder = struct
276276- (** CGI-style environment variables *)
277277- type cgi_env = (string * string) list
278278-279279- (** HTTP request information *)
280280- type http_request = {
281281- method_ : string; (** HTTP method (GET, POST, etc.) *)
282282- uri : string; (** Request URI *)
283283- query_string : string; (** Query string parameters *)
284284- content_type : string option; (** Content-Type header *)
285285- content_length : int option; (** Content-Length header *)
286286- headers : (string * string) list; (** Additional HTTP headers *)
287287- body : 'a Eio.Flow.source; (** Request body stream *)
288288- }
289289-290290- (** HTTP response builder *)
291291- type 'a http_response = {
292292- write_status : int -> unit; (** Set HTTP status code *)
293293- write_header : string -> string -> unit; (** Add response header *)
294294- write_body : string -> unit; (** Write response body *)
295295- write_body_chunk : bytes -> unit; (** Write body chunk *)
296296- finish : unit -> unit; (** Complete response *)
297297- }
298298-299299- (** Convert FastCGI request to HTTP request *)
300300- val request_of_fastcgi : 'a request -> http_request
301301-302302- (** Create HTTP response writer from FastCGI response *)
303303- val response_of_fastcgi : 'a response -> 'a http_response
304304-305305- (** Convenience handler wrapper for HTTP-style handlers *)
306306- val http_handler :
307307- (http_request -> 'a http_response -> unit) ->
308308- 'a Handler.responder
309309-end
310310-```
311311-312312-### Authorizer
313313-314314-```ocaml
315315-(** Authorizer role implementation *)
316316-module Authorizer = struct
317317- (** Authorization result *)
318318- type auth_result =
319319- | Authorized of (string * string) list (** Authorized with variable bindings *)
320320- | Unauthorized of { (** Unauthorized with error response *)
321321- status : int;
322322- headers : (string * string) list;
323323- body : string;
324324- }
325325-326326- (** Authorization request information *)
327327- type auth_request = {
328328- method_ : string;
329329- uri : string;
330330- remote_addr : string option;
331331- remote_user : string option;
332332- auth_type : string option;
333333- headers : (string * string) list;
334334- }
335335-336336- (** Convert FastCGI request to authorization request *)
337337- val request_of_fastcgi : 'a request -> auth_request
338338-339339- (** Convert authorization result to FastCGI response *)
340340- val response_of_result : auth_result -> 'a response -> response_result
341341-342342- (** Convenience handler wrapper for authorization handlers *)
343343- val auth_handler :
344344- (auth_request -> auth_result) ->
345345- 'a Handler.authorizer
346346-end
347347-```
348348-349349-### Filter
350350-351351-```ocaml
352352-(** Filter role implementation *)
353353-module Filter = struct
354354- (** Filter request with data stream *)
355355- type 'a filter_request = {
356356- request : 'a request; (** Base FastCGI request *)
357357- data_stream : 'a Eio.Flow.source; (** File data to filter *)
358358- data_last_modified : float option; (** File modification time *)
359359- data_length : int option; (** Expected data length *)
360360- }
361361-362362- (** Convert FastCGI request to filter request *)
363363- val request_of_fastcgi : 'a request -> 'a filter_request
364364-365365- (** Convenience handler wrapper for filter handlers *)
366366- val filter_handler :
367367- ('a filter_request -> 'a response -> unit) ->
368368- 'a Handler.filter
369369-end
370370-```
371371-372372-## Error Handling
373373-374374-```ocaml
375375-(** FastCGI specific errors *)
376376-module Error = struct
377377- type t =
378378- | Protocol_error of string (** Protocol violation *)
379379- | Invalid_record of string (** Malformed record *)
380380- | Unsupported_version of int (** Unsupported protocol version *)
381381- | Unknown_record_type of int (** Unknown record type *)
382382- | Request_id_conflict of request_id (** Duplicate request ID *)
383383- | Connection_closed (** Connection unexpectedly closed *)
384384- | Application_error of string (** Application-specific error *)
385385-386386- exception Fastcgi_error of t
387387-388388- (** Convert error to string description *)
389389- val to_string : t -> string
390390-391391- (** Raise a FastCGI error *)
392392- val raise : t -> 'a
393393-end
394394-```
395395-396396-## Resource Management
397397-398398-```ocaml
399399-(** Resource management utilities *)
400400-module Resource = struct
401401- (** Request context with automatic cleanup *)
402402- type 'a request_context = {
403403- request : 'a request;
404404- response : 'a response;
405405- switch : Eio.Switch.t; (** Switch for request-scoped resources *)
406406- }
407407-408408- (** Create a request context with automatic resource management *)
409409- val with_request_context :
410410- sw:Eio.Switch.t ->
411411- 'a request ->
412412- ('a request_context -> 'b) ->
413413- 'b
414414-415415- (** Buffer management for efficient I/O *)
416416- module Buffer = struct
417417- type t
418418-419419- val create : size:int -> t
420420- val read_into : t -> 'a Eio.Flow.source -> int
421421- val write_from : t -> 'a Eio.Flow.sink -> int -> unit
422422- val clear : t -> unit
423423- end
424424-end
425425-```
426426-427427-## Examples
428428-429429-### Basic Responder Application
430430-431431-```ocaml
432432-open Eio.Std
433433-434434-let hello_handler request response =
435435- let http_req = Responder.request_of_fastcgi request in
436436- let http_resp = Responder.response_of_fastcgi response in
437437-438438- http_resp.write_status 200;
439439- http_resp.write_header "Content-Type" "text/html";
440440- http_resp.write_body "<h1>Hello, FastCGI!</h1>";
441441- http_resp.finish ();
442442-443443- { app_status = 0; protocol_status = Request_complete }
444444-445445-let run_server env =
446446- let net = Eio.Stdenv.net env in
447447- let config = {
448448- app = {
449449- max_connections = 10;
450450- max_requests = 50;
451451- multiplex_connections = true;
452452- handler = Handler.Responder hello_handler;
453453- };
454454- listen_address = `Tcp (Eio.Net.Ipaddr.V4.loopback, 9000);
455455- backlog = 5;
456456- max_connections = 10;
457457- } in
458458-459459- Switch.run @@ fun sw ->
460460- Server.run ~sw ~net config
461461-462462-let () = Eio_main.run run_server
463463-```
464464-465465-### File-serving Responder
466466-467467-```ocaml
468468-let file_server_handler ~cwd request response =
469469- let http_req = Responder.request_of_fastcgi request in
470470- let http_resp = Responder.response_of_fastcgi response in
471471-472472- let path = Eio.Path.(cwd / String.drop_prefix http_req.uri 1) in
473473-474474- match Eio.Path.load path with
475475- | content ->
476476- http_resp.write_status 200;
477477- http_resp.write_header "Content-Type" "text/html";
478478- http_resp.write_body content;
479479- http_resp.finish ()
480480- | exception (Eio.Io (Eio.Fs.E (Not_found _), _)) ->
481481- http_resp.write_status 404;
482482- http_resp.write_header "Content-Type" "text/plain";
483483- http_resp.write_body "File not found";
484484- http_resp.finish ()
485485- | exception ex ->
486486- http_resp.write_status 500;
487487- http_resp.write_header "Content-Type" "text/plain";
488488- http_resp.write_body ("Server error: " ^ Printexc.to_string ex);
489489- http_resp.finish ();
490490-491491- { app_status = 0; protocol_status = Request_complete }
492492-493493-let run_file_server env =
494494- let net = Eio.Stdenv.net env in
495495- let cwd = Eio.Stdenv.cwd env in
496496- let handler = file_server_handler ~cwd in
497497-498498- Switch.run @@ fun sw ->
499499- Server.run_default ~sw ~net ~handler:(Handler.Responder handler)
500500- ~listen_address:(`Tcp (Eio.Net.Ipaddr.V4.loopback, 9000))
501501-```
502502-503503-### Authorization Application
504504-505505-```ocaml
506506-let auth_handler auth_req =
507507- match auth_req.remote_user with
508508- | Some user when String.starts_with user "admin_" ->
509509- Authorizer.Authorized [("AUTH_USER", user); ("AUTH_LEVEL", "admin")]
510510- | Some user ->
511511- Authorizer.Authorized [("AUTH_USER", user); ("AUTH_LEVEL", "user")]
512512- | None ->
513513- Authorizer.Unauthorized {
514514- status = 401;
515515- headers = [("WWW-Authenticate", "Basic realm=\"FastCGI\"")];
516516- body = "Authentication required";
517517- }
518518-519519-let run_auth_server env =
520520- let net = Eio.Stdenv.net env in
521521- let handler = Authorizer.auth_handler auth_handler in
522522-523523- Switch.run @@ fun sw ->
524524- Server.run_default ~sw ~net ~handler:(Handler.Authorizer handler)
525525- ~listen_address:(`Unix "/tmp/auth.sock")
526526-```
527527-528528-### Filter Application
529529-530530-```ocaml
531531-let markdown_filter filter_req response =
532532- let data = Eio.Flow.read_all filter_req.data_stream in
533533- let html = Markdown.to_html data in (* Assuming markdown library *)
534534-535535- Eio.Flow.copy_string "Content-Type: text/html\r\n\r\n" response.stdout;
536536- Eio.Flow.copy_string html response.stdout;
537537-538538- { app_status = 0; protocol_status = Request_complete }
539539-540540-let run_filter_server env =
541541- let net = Eio.Stdenv.net env in
542542- let handler = Filter.filter_handler markdown_filter in
543543-544544- Switch.run @@ fun sw ->
545545- Server.run_default ~sw ~net ~handler:(Handler.Filter handler)
546546- ~listen_address:(`Tcp (Eio.Net.Ipaddr.V4.loopback, 9001))
547547-```
548548-549549-### Connection Management Example
550550-551551-```ocaml
552552-let custom_connection_handler env =
553553- let net = Eio.Stdenv.net env in
554554-555555- Switch.run @@ fun sw ->
556556- let server_socket = Eio.Net.listen net ~sw ~reuse_addr:true ~backlog:5
557557- (`Tcp (Eio.Net.Ipaddr.V4.loopback, 9000)) in
558558-559559- let handler = Handler.Responder hello_handler in
560560-561561- Eio.Net.run_server server_socket (fun flow _addr ->
562562- Switch.run @@ fun conn_sw ->
563563- let conn = Connection.create ~sw:conn_sw flow in
564564-565565- let rec process_loop () =
566566- match Connection.process_request conn handler with
567567- | response -> process_loop ()
568568- | exception End_of_file -> ()
569569- | exception ex ->
570570- Eio.traceln "Connection error: %s" (Printexc.to_string ex)
571571- in
572572- process_loop ()
573573- )
574574-```
575575-576576-## Implementation Notes
577577-578578-### Wire Protocol Details
579579-580580-The implementation must handle:
581581-582582-1. **Binary record format**: 8-byte headers with version, type, request ID, content length, and padding
583583-2. **Name-value pair encoding**: Variable-length encoding for parameter names and values
584584-3. **Stream management**: Proper handling of stdin, stdout, stderr, and data streams
585585-4. **Connection multiplexing**: Support multiple concurrent requests over single connection
586586-5. **Error propagation**: Convert protocol errors to appropriate OCaml exceptions
587587-588588-### Performance Considerations
589589-590590-1. **Buffer management**: Reuse buffers to minimize allocations
591591-2. **Streaming I/O**: Process requests in streaming fashion for large payloads
592592-3. **Connection pooling**: Support keep-alive connections for efficiency
593593-4. **Concurrent processing**: Use Eio fibers for handling multiple requests
594594-595595-### Security Considerations
596596-597597-1. **Input validation**: Validate all protocol fields and reject malformed records
598598-2. **Resource limits**: Enforce limits on request size, connection count, etc.
599599-3. **Capability restriction**: Use Eio's capability system to limit access
600600-4. **Error information**: Avoid leaking sensitive information in error messages
601601-602602-### Testing Strategy
603603-604604-The implementation should include:
605605-606606-1. **Protocol conformance tests**: Verify correct implementation of FastCGI protocol
607607-2. **Interoperability tests**: Test with real web servers (nginx, Apache)
608608-3. **Performance tests**: Measure throughput and latency under load
609609-4. **Error handling tests**: Verify graceful handling of error conditions
610610-5. **Mock testing**: Use Eio_mock for deterministic unit tests
611611-612612-This specification provides a comprehensive foundation for implementing a robust, efficient, and type-safe FastCGI library for OCaml using the Eio effects system.