FastCGI implementation in OCaml

reset

-43
examples/basic_filter.ml
···
-
(* Basic filter that uppercases text content *)
-
-
open Fastcgi
-
-
let uppercase_filter_logic filter_req response =
-
let input = filter_req.Filter.data_stream in
-
let output = response.stdout in
-
-
(* Simple text transformation: read input and write uppercase output *)
-
let buffer = Cstruct.create 4096 in
-
let rec process () =
-
try
-
let n = Eio.Flow.single_read input buffer in
-
if n > 0 then begin
-
let text = Cstruct.to_string ~len:n buffer in
-
let uppercase_text = String.uppercase_ascii text in
-
Eio.Flow.copy_string uppercase_text output;
-
process ()
-
end
-
with
-
| End_of_file -> ()
-
in
-
process ()
-
-
let filter_handler request response =
-
let filter_req = Filter.request_of_fastcgi request in
-
-
(* Write HTTP headers *)
-
Eio.Flow.copy_string "Status: 200 OK\r\n" response.stdout;
-
Eio.Flow.copy_string "Content-Type: text/plain\r\n" response.stdout;
-
Eio.Flow.copy_string "\r\n" response.stdout;
-
-
(* Process the data stream *)
-
uppercase_filter_logic filter_req response;
-
-
{ app_status = 0; protocol_status = Request_complete }
-
-
let () = Eio_main.run @@ fun env ->
-
let net = Eio.Stdenv.net env in
-
Eio.Switch.run @@ fun sw ->
-
Server.run_default ~sw ~net
-
~handler:(Handler.Filter filter_handler)
-
~listen_address:(`Tcp ("127.0.0.1", 9003))
-4
examples/dune
···
-
(executables
-
(public_names hello_responder echo_responder simple_authorizer basic_filter)
-
(names hello_responder echo_responder simple_authorizer basic_filter)
-
(libraries fastcgi eio_main cstruct))
-40
examples/echo_responder.ml
···
-
(* Echo server that shows request information *)
-
-
open Fastcgi
-
-
let echo_handler request response =
-
let http_req = Responder.request_of_fastcgi request in
-
let http_resp = Responder.response_of_fastcgi response in
-
-
http_resp.write_status 200;
-
http_resp.write_header "Content-Type" "text/html";
-
-
http_resp.write_body "<h1>FastCGI Echo Server</h1>";
-
http_resp.write_body ("<p><strong>Method:</strong> " ^ http_req.method_ ^ "</p>");
-
http_resp.write_body ("<p><strong>URI:</strong> " ^ http_req.uri ^ "</p>");
-
http_resp.write_body ("<p><strong>Query String:</strong> " ^ http_req.query_string ^ "</p>");
-
-
(match http_req.content_type with
-
| Some ct -> http_resp.write_body ("<p><strong>Content-Type:</strong> " ^ ct ^ "</p>")
-
| None -> ());
-
-
(match http_req.content_length with
-
| Some cl -> http_resp.write_body ("<p><strong>Content-Length:</strong> " ^ string_of_int cl ^ "</p>")
-
| None -> ());
-
-
http_resp.write_body "<h2>Headers:</h2><ul>";
-
List.iter (fun (name, value) ->
-
http_resp.write_body ("<li><strong>" ^ name ^ ":</strong> " ^ value ^ "</li>")
-
) http_req.headers;
-
http_resp.write_body "</ul>";
-
-
http_resp.finish ();
-
-
{ app_status = 0; protocol_status = Request_complete }
-
-
let () = Eio_main.run @@ fun env ->
-
let net = Eio.Stdenv.net env in
-
Eio.Switch.run @@ fun sw ->
-
Server.run_default ~sw ~net
-
~handler:(Handler.Responder echo_handler)
-
~listen_address:(`Tcp ("127.0.0.1", 9001))
-22
examples/hello_responder.ml
···
-
(* Simple Hello World FastCGI Responder application *)
-
-
open Fastcgi
-
-
let hello_handler request response =
-
let _http_req = Responder.request_of_fastcgi request in
-
let http_resp = Responder.response_of_fastcgi response in
-
-
http_resp.write_status 200;
-
http_resp.write_header "Content-Type" "text/html";
-
http_resp.write_body "<h1>Hello, FastCGI!</h1>";
-
http_resp.write_body "<p>This is a simple FastCGI responder application.</p>";
-
http_resp.finish ();
-
-
{ app_status = 0; protocol_status = Request_complete }
-
-
let () = Eio_main.run @@ fun env ->
-
let net = Eio.Stdenv.net env in
-
Eio.Switch.run @@ fun sw ->
-
Server.run_default ~sw ~net
-
~handler:(Handler.Responder hello_handler)
-
~listen_address:(`Tcp ("127.0.0.1", 9000))
-29
examples/simple_authorizer.ml
···
-
(* Simple authorization server *)
-
-
open Fastcgi
-
-
let auth_logic auth_req =
-
(* Simple authorization: allow only GET requests to /public/* paths *)
-
match auth_req.Authorizer.method_, auth_req.uri with
-
| "GET", uri when String.starts_with ~prefix:"/public/" uri ->
-
Authorizer.Authorized [("USER_AUTHORIZED", "true"); ("ACCESS_LEVEL", "public")]
-
| "GET", "/login" ->
-
Authorizer.Authorized [("LOGIN_PAGE", "true")]
-
| _ ->
-
Authorizer.Unauthorized {
-
status = 403;
-
headers = [("Content-Type", "text/html")];
-
body = "<h1>403 Forbidden</h1><p>Access denied. Only GET requests to /public/* are allowed.</p>";
-
}
-
-
let auth_handler request response =
-
let auth_req = Authorizer.request_of_fastcgi request in
-
let result = auth_logic auth_req in
-
Authorizer.response_of_result result response
-
-
let () = Eio_main.run @@ fun env ->
-
let net = Eio.Stdenv.net env in
-
Eio.Switch.run @@ fun sw ->
-
Server.run_default ~sw ~net
-
~handler:(Handler.Authorizer auth_handler)
-
~listen_address:(`Tcp ("127.0.0.1", 9002))
+3 -10
lib/dune
···
(name fastcgi)
(libraries eio)
(modules
-
fastcgi_types
fastcgi
-
fastcgi_protocol
-
fastcgi_responder
-
fastcgi_authorizer
-
fastcgi_filter)
+
)
(modules_without_implementation
-
fastcgi_types
fastcgi
-
fastcgi_protocol
-
fastcgi_responder
-
fastcgi_authorizer
-
fastcgi_filter))
+
)
+
)
-389
lib/fastcgi.mli
···
-
(** FastCGI protocol implementation for OCaml using Eio.
-
-
This library provides a type-safe, high-performance implementation of the
-
FastCGI protocol using OCaml's Eio effects-based IO library. It supports
-
all three FastCGI roles: Responder, Authorizer, and Filter.
-
-
FastCGI is an open extension to CGI that provides high performance for all
-
Internet applications without the penalties of Web server APIs. Unlike
-
traditional CGI programs that are started for each request, FastCGI
-
applications are long-lived processes that can handle multiple requests
-
over persistent connections.
-
-
The library follows Eio conventions for structured concurrency,
-
capability-based security, and resource management. All network I/O is
-
performed using Eio's effects system, enabling high-performance concurrent
-
request processing.
-
-
{2 Key Features}
-
-
- {b Type Safety}: Leverages OCaml's type system to prevent protocol errors
-
- {b High Performance}: Connection multiplexing and keep-alive for efficiency
-
- {b Structured Concurrency}: Automatic resource cleanup using Eio switches
-
- {b All FastCGI Roles}: Complete support for Responder, Authorizer, and Filter
-
- {b Standards Compliant}: Full implementation of FastCGI Protocol 1.0
-
-
{2 Usage Example}
-
-
{[
-
let hello_handler request response =
-
let http_req = Responder.request_of_fastcgi request in
-
let http_resp = Responder.response_of_fastcgi response in
-
-
http_resp.write_status 200;
-
http_resp.write_header "Content-Type" "text/html";
-
http_resp.write_body "<h1>Hello, FastCGI!</h1>";
-
http_resp.finish ();
-
-
{ app_status = 0; protocol_status = Request_complete }
-
-
let () = Eio_main.run @@ fun env ->
-
let net = Eio.Stdenv.net env in
-
Switch.run @@ fun sw ->
-
Server.run_default ~sw ~net
-
~handler:(Handler.Responder hello_handler)
-
~listen_address:(`Tcp (Eio.Net.Sockaddr.stream, 9000))
-
]}
-
-
{2 References}
-
-
- {{:https://github.com/ocaml-multicore/eio} Eio Documentation} *)
-
-
(** {1 Core Types} *)
-
-
(** Re-export core types from Fastcgi_types.
-
-
This includes all the fundamental FastCGI protocol types such as
-
[record_type], [role], [request], [response], and protocol constants.
-
This includes all the fundamental FastCGI protocol types. *)
-
include module type of Fastcgi_types
-
-
(** {1 Protocol Implementation} *)
-
-
(** Wire protocol parsing and serialization. *)
-
module Protocol = Fastcgi_protocol
-
-
(** {1 Role-Specific Implementations} *)
-
-
(** Responder role implementation. *)
-
module Responder = Fastcgi_responder
-
-
(** Authorizer role implementation. *)
-
module Authorizer = Fastcgi_authorizer
-
-
(** Filter role implementation. *)
-
module Filter = Fastcgi_filter
-
-
(** {1 Application Interface} *)
-
-
(** Application handler signatures.
-
-
This module defines the core handler types that applications implement
-
to process FastCGI requests. Each handler type corresponds to one of
-
the three FastCGI roles: Responder, Authorizer, and Filter. *)
-
module Handler : sig
-
(** Responder handler: process HTTP request and generate response.
-
-
This is the most common handler type, equivalent to traditional CGI.
-
A Responder FastCGI application has the same purpose as a CGI/1.1 program:
-
it receives all the information associated with an HTTP request and generates
-
an HTTP response.
-
-
@param request Complete FastCGI request with parameters and input streams
-
@param response Output streams for writing response data
-
@return Response result with application and protocol status *)
-
type 'a responder = 'a request -> 'a response -> response_result
-
-
(** Authorizer handler: make authorization decision.
-
-
An Authorizer FastCGI application receives all the information associated
-
with an HTTP request and generates an authorized/unauthorized decision.
-
In case of an authorized decision, the Authorizer can also associate
-
name-value pairs with the HTTP request.
-
-
@param request FastCGI request with authentication context
-
@param response Output streams for authorization response
-
@return Response result indicating authorization decision *)
-
type 'a authorizer = 'a request -> 'a response -> response_result
-
-
(** Filter handler: process data stream with filtering.
-
-
A Filter FastCGI application receives all the information associated
-
with an HTTP request, plus an extra stream of data from a file stored
-
on the Web server, and generates a 'filtered' version of the data stream
-
as an HTTP response.
-
-
@param request FastCGI request including both HTTP context and file data
-
@param response Output streams for filtered response
-
@return Response result with filtered content status *)
-
type 'a filter = 'a request -> 'a response -> response_result
-
-
(** Generic handler that can handle any role.
-
-
This variant type allows applications to support multiple roles or
-
to be configured at runtime for different roles. The web server
-
specifies the desired role in each Begin_request record. *)
-
type 'a handler =
-
| Responder of 'a responder (** Handle HTTP requests and responses *)
-
| Authorizer of 'a authorizer (** Handle authorization decisions *)
-
| Filter of 'a filter (** Handle data filtering *)
-
end
-
-
(** Application configuration.
-
-
This type encapsulates all the settings needed to configure a FastCGI
-
application's behavior. It includes both resource limits and the
-
application logic (handler function).
-
-
The configuration values affect how the application responds to
-
FCGI_GET_VALUES management queries from the web server. *)
-
type 'a app_config = {
-
max_connections : int; (** Maximum concurrent transport connections
-
this application will accept. Corresponds to
-
FCGI_MAX_CONNS management variable. *)
-
max_requests : int; (** Maximum concurrent requests this application
-
will accept across all connections. Corresponds
-
to FCGI_MAX_REQS management variable. *)
-
multiplex_connections : bool; (** Whether this application can multiplex
-
connections (handle concurrent requests over
-
each connection). Corresponds to FCGI_MPXS_CONNS
-
management variable. *)
-
handler : 'a Handler.handler; (** Application request handler that implements
-
the core application logic. *)
-
}
-
-
(** {1 Connection Management} *)
-
-
(** Connection manager for handling FastCGI protocol.
-
-
This module provides low-level connection management for FastCGI applications.
-
It handles the binary protocol details, record parsing/serialization, and
-
request multiplexing over individual transport connections.
-
-
After a FastCGI process accepts a connection on its listening socket, the
-
process executes a simple protocol to receive and send data. The protocol
-
serves two purposes: First, it multiplexes a single transport connection
-
between several independent FastCGI requests. Second, within each request
-
the protocol provides several independent data streams in each direction. *)
-
module Connection : sig
-
(** Opaque connection type.
-
-
Represents a single transport connection from a web server. The connection
-
handles protocol parsing, request demultiplexing, and stream management.
-
Each connection can handle multiple concurrent requests if the application
-
supports multiplexing. *)
-
type 'a t
-
-
(** Connection statistics.
-
-
Provides runtime metrics about connection usage for monitoring and
-
debugging purposes. These statistics can help tune application
-
performance and detect potential issues. *)
-
type stats = {
-
active_requests : int; (** Number of requests currently being processed
-
on this connection. *)
-
total_requests : int; (** Total number of requests processed since
-
connection establishment. *)
-
bytes_sent : int; (** Total bytes sent to the web server (responses). *)
-
bytes_received : int; (** Total bytes received from the web server (requests). *)
-
}
-
-
(** Create a connection from a network flow.
-
-
Initializes a new FastCGI connection over an established transport.
-
The connection will parse incoming records and manage request state
-
according to the FastCGI protocol.
-
-
The connection handles a simple protocol to receive and send data,
-
including record headers, content data, and proper multiplexing.
-
-
@param sw Switch for managing connection lifetime and automatic cleanup
-
@param flow Two-way network flow for bidirectional communication
-
@return New connection ready to process FastCGI requests
-
@raise Invalid_argument if the flow is already closed *)
-
val create : sw:Eio.Switch.t -> 'a Eio.Flow.two_way -> 'a t
-
-
(** Accept and process a single request on the connection.
-
-
Waits for the next FastCGI request to arrive on this connection,
-
then processes it using the provided handler. This function handles
-
all protocol details including record parsing, stream management,
-
and response generation.
-
-
The function is concurrent-safe and can be called multiple times
-
if the application supports request multiplexing.
-
-
@param conn Connection to process request on
-
@param handler Application handler for the request
-
@return Promise that resolves to the response result when processing completes
-
@raise Fastcgi.Error.Fastcgi_error on protocol violations *)
-
val process_request : 'a t -> 'a Handler.handler -> response_result Eio.Promise.t
-
-
(** Get connection statistics.
-
-
Returns current runtime statistics for this connection. This information
-
can be used for monitoring, load balancing decisions, and performance
-
tuning.
-
-
@param conn Connection to query
-
@return Current statistics snapshot *)
-
val stats : 'a t -> stats
-
-
(** Close the connection gracefully.
-
-
Closes the transport connection after completing any active requests.
-
The Web server controls the lifetime of transport connections and can
-
close a connection when no requests are active.
-
-
@param conn Connection to close *)
-
val close : 'a t -> unit
-
end
-
-
(** FastCGI server for accepting and managing connections.
-
-
This module provides a high-level server that listens for incoming
-
connections and manages them according to FastCGI protocol requirements.
-
It handles connection acceptance, lifecycle management, and graceful
-
shutdown.
-
-
The server supports both Unix domain sockets and TCP sockets, as
-
commonly used by web servers like nginx, Apache, and lighttpd. *)
-
module Server : sig
-
(** Server configuration.
-
-
Complete configuration for a FastCGI server, including application
-
settings, network binding, and resource limits. *)
-
type 'a config = {
-
app : 'a app_config; (** Application configuration including handlers
-
and capability limits. *)
-
listen_address : [
-
| `Unix of string (** Unix domain socket path. Common for local
-
communication with web servers. *)
-
| `Tcp of string * int (** TCP socket address (host) and port.
-
Used for remote FastCGI servers. *)
-
];
-
backlog : int; (** Listen socket backlog size. Controls how
-
many pending connections can be queued. *)
-
max_connections : int; (** Maximum concurrent connections to accept.
-
Should align with app.max_connections. *)
-
}
-
-
(** Run a FastCGI server.
-
-
Starts a FastCGI server with the specified configuration. The server
-
will listen for connections, accept them within the configured limits,
-
and process requests using the application handler.
-
-
The server implements the standard FastCGI application lifecycle:
-
a FastCGI application calls accept() on the socket referred to by file
-
descriptor FCGI_LISTENSOCK_FILENO to accept a new transport connection.
-
-
This function blocks until the switch is cancelled or an error occurs.
-
-
@param sw Switch for managing server lifetime and automatic cleanup
-
@param net Network capability for creating listening sockets
-
@param config Complete server configuration
-
@raise Sys_error if unable to bind to the specified address *)
-
val run :
-
sw:Eio.Switch.t ->
-
net:[< `Generic | `Unix ] Eio.Net.ty Eio.Resource.t ->
-
_ config ->
-
unit
-
-
(** Run server with default configuration.
-
-
Convenience function to start a FastCGI server with sensible defaults.
-
Useful for simple applications that don't need custom configuration.
-
-
Default settings:
-
- max_connections: 10
-
- max_requests: 50
-
- multiplex_connections: true
-
- backlog: 5
-
-
@param sw Switch for managing server lifetime
-
@param net Network capability for creating sockets
-
@param handler Application handler function
-
@param listen_address Address to listen on (Unix socket or TCP)
-
@raise Sys_error if unable to bind to the specified address *)
-
val run_default :
-
sw:Eio.Switch.t ->
-
net:[< `Generic | `Unix ] Eio.Net.ty Eio.Resource.t ->
-
handler:_ Handler.handler ->
-
listen_address:[`Unix of string | `Tcp of string * int] ->
-
unit
-
end
-
-
(** {1 Error Handling} *)
-
-
(** FastCGI specific errors. *)
-
module Error : sig
-
(** Error types. *)
-
type t =
-
| Protocol_error of string (** Protocol violation *)
-
| Invalid_record of string (** Malformed record *)
-
| Unsupported_version of int (** Unsupported protocol version *)
-
| Unknown_record_type of int (** Unknown record type *)
-
| Request_id_conflict of request_id (** Duplicate request ID *)
-
| Connection_closed (** Connection unexpectedly closed *)
-
| Application_error of string (** Application-specific error *)
-
-
(** FastCGI exception. *)
-
exception Fastcgi_error of t
-
-
(** Convert error to string description. *)
-
val to_string : t -> string
-
-
(** Raise a FastCGI error. *)
-
val raise : t -> 'a
-
end
-
-
(** {1 Resource Management} *)
-
-
(** Resource management utilities. *)
-
module Resource : sig
-
(** Request context with automatic cleanup. *)
-
type 'a request_context = {
-
request : 'a request;
-
response : 'a response;
-
switch : Eio.Switch.t; (** Switch for request-scoped resources *)
-
}
-
-
(** Create a request context with automatic resource management.
-
-
@param sw Parent switch
-
@param request FastCGI request
-
@param f Function to execute with request context
-
@return Result of function execution *)
-
val with_request_context :
-
sw:Eio.Switch.t ->
-
'a request ->
-
('a request_context -> 'b) ->
-
'b
-
-
(** Buffer management for efficient I/O. *)
-
module Buffer : sig
-
(** Buffer type. *)
-
type t
-
-
(** Create a buffer with specified size. *)
-
val create : size:int -> t
-
-
(** Read data into buffer from source.
-
-
@param buffer Buffer to read into
-
@param source Source to read from
-
@return Number of bytes read *)
-
val read_into : t -> 'a Eio.Flow.source -> int
-
-
(** Write data from buffer to sink.
-
-
@param buffer Buffer to write from
-
@param sink Sink to write to
-
@param length Number of bytes to write *)
-
val write_from : t -> 'a Eio.Flow.sink -> int -> unit
-
-
(** Clear buffer contents. *)
-
val clear : t -> unit
-
end
-
end
-229
lib/fastcgi_authorizer.mli
···
-
(** Authorizer role implementation for FastCGI.
-
-
The Authorizer role performs access control decisions for web requests.
-
An Authorizer FastCGI application receives all the information associated
-
with an HTTP request and generates an authorized/unauthorized decision.
-
-
In case of an authorized decision, the Authorizer can associate name-value
-
pairs with the HTTP request for use by subsequent processing stages. When
-
giving an unauthorized decision, the Authorizer sends a complete response
-
to the HTTP client with appropriate error information.
-
-
This role is commonly used for:
-
- Authentication and authorization checks
-
- Access control based on IP addresses, user roles, or request attributes
-
- Rate limiting and quota enforcement
-
- Custom security policies
-
-
The web server may proceed with additional access checks after a successful
-
authorization, including requests to other Authorizers, depending on its
-
configuration. *)
-
-
(** {1 Authorization Types} *)
-
-
(** Authorization result.
-
-
Represents the outcome of an authorization decision. The Authorizer can
-
either grant access (possibly with additional variables) or deny access
-
with a complete error response. *)
-
type auth_result =
-
| Authorized of (string * string) list (** Request is authorized. The list contains
-
variable bindings that will be added to
-
the request environment for subsequent
-
processing stages. *)
-
| Unauthorized of {
-
status : int; (** HTTP status code for the error response
-
(typically 401, 403, or 429). *)
-
headers : (string * string) list; (** HTTP headers for the error response.
-
May include WWW-Authenticate, etc. *)
-
body : string; (** Error response body content sent to the client. *)
-
} (** Request is denied. The web server will send this error
-
response directly to the client without further processing. *)
-
-
(** Authorization request information.
-
-
Contains the relevant information from an HTTP request needed to make
-
authorization decisions. This is extracted from the CGI environment
-
variables provided by the web server. *)
-
type auth_request = {
-
method_ : string; (** HTTP method (GET, POST, etc.). *)
-
uri : string; (** Request URI being accessed. *)
-
remote_addr : string option; (** Client IP address, if available.
-
Useful for IP-based access control. *)
-
remote_user : string option; (** Authenticated username, if any.
-
Set by prior authentication stages. *)
-
auth_type : string option; (** Authentication method used (Basic, Bearer, etc.).
-
Indicates how the user was authenticated. *)
-
headers : (string * string) list; (** HTTP headers from the request.
-
May contain authorization headers, user agents, etc. *)
-
}
-
-
(** {1 Conversion Functions} *)
-
-
(** Convert FastCGI request to authorization request.
-
-
Extracts authorization-relevant information from FastCGI
-
parameters and creates an auth request object.
-
-
@param request FastCGI request
-
@return Authorization request *)
-
val request_of_fastcgi : 'a Fastcgi_types.request -> auth_request
-
-
(** Convert authorization result to FastCGI response.
-
-
Writes the authorization decision to the FastCGI response
-
streams in the correct format.
-
-
@param result Authorization decision
-
@param response FastCGI response
-
@return Response result *)
-
val response_of_result : auth_result -> 'a Fastcgi_types.response -> Fastcgi_types.response_result
-
-
(** {1 Handler Utilities} *)
-
-
(** Authorizer handler type. *)
-
type 'a authorizer_handler = 'a Fastcgi_types.request -> 'a Fastcgi_types.response -> Fastcgi_types.response_result
-
-
(** Convenience handler wrapper for authorization handlers.
-
-
Converts an authorization handler function into a FastCGI handler.
-
This allows writing handlers that work with auth request/result
-
types instead of raw FastCGI types.
-
-
@param handler Authorization handler function
-
@return FastCGI authorizer handler *)
-
val auth_handler :
-
(auth_request -> auth_result) ->
-
'a authorizer_handler
-
-
(** {1 Common Authorization Patterns} *)
-
-
(** Create a simple allow/deny authorizer.
-
-
@param predicate Function that returns true for authorized requests
-
@return Authorization result *)
-
val simple_authorizer : (auth_request -> bool) -> (auth_request -> auth_result)
-
-
(** IP-based authorization.
-
-
@param allowed_ips List of allowed IP addresses/ranges
-
@param request Authorization request
-
@return Authorization result *)
-
val ip_authorizer : string list -> auth_request -> auth_result
-
-
(** User-based authorization.
-
-
@param allowed_users List of allowed usernames
-
@param request Authorization request
-
@return Authorization result *)
-
val user_authorizer : string list -> auth_request -> auth_result
-
-
(** Role-based authorization.
-
-
@param get_user_roles Function to get roles for a user
-
@param required_roles Roles required for access
-
@param request Authorization request
-
@return Authorization result *)
-
val role_authorizer :
-
get_user_roles:(string -> string list) ->
-
required_roles:string list ->
-
auth_request ->
-
auth_result
-
-
(** Time-based authorization.
-
-
@param allowed_hours List of allowed hours (0-23)
-
@param request Authorization request
-
@return Authorization result *)
-
val time_authorizer : int list -> auth_request -> auth_result
-
-
(** {1 Authentication Integration} *)
-
-
(** Basic authentication handler.
-
-
@param realm Authentication realm
-
@param verify_credentials Function to verify username/password
-
@param request Authorization request
-
@return Authorization result *)
-
val basic_auth :
-
realm:string ->
-
verify_credentials:(string -> string -> bool) ->
-
auth_request ->
-
auth_result
-
-
(** Bearer token authentication.
-
-
@param verify_token Function to verify and decode token
-
@param request Authorization request
-
@return Authorization result *)
-
val bearer_auth :
-
verify_token:(string -> (string * (string * string) list) option) ->
-
auth_request ->
-
auth_result
-
-
(** API key authentication.
-
-
@param header_name Header containing API key
-
@param verify_key Function to verify API key
-
@param request Authorization request
-
@return Authorization result *)
-
val api_key_auth :
-
header_name:string ->
-
verify_key:(string -> (string * (string * string) list) option) ->
-
auth_request ->
-
auth_result
-
-
(** {1 Response Helpers} *)
-
-
(** Create a standard 401 Unauthorized response.
-
-
@param realm Authentication realm
-
@param message Error message
-
@return Unauthorized result *)
-
val unauthorized_response :
-
realm:string ->
-
message:string ->
-
auth_result
-
-
(** Create a standard 403 Forbidden response.
-
-
@param message Error message
-
@return Unauthorized result *)
-
val forbidden_response :
-
message:string ->
-
auth_result
-
-
(** Create a custom error response.
-
-
@param status HTTP status code
-
@param headers Additional headers
-
@param body Response body
-
@return Unauthorized result *)
-
val error_response :
-
status:int ->
-
headers:(string * string) list ->
-
body:string ->
-
auth_result
-
-
(** {1 Variable Binding} *)
-
-
(** Create authorized response with variables.
-
-
Variables are passed to subsequent FastCGI applications
-
as environment variables with "Variable-" prefix.
-
-
@param variables Name-value pairs to bind
-
@return Authorized result *)
-
val authorized_with_variables : (string * string) list -> auth_result
-
-
(** Add authentication information as variables.
-
-
@param user Authenticated username
-
@param auth_type Authentication method
-
@param additional Additional variables
-
@return Variable bindings *)
-
val auth_variables :
-
user:string ->
-
auth_type:string ->
-
additional:(string * string) list ->
-
(string * string) list
-329
lib/fastcgi_filter.mli
···
-
(** Filter role implementation for FastCGI.
-
-
The Filter role processes data streams with filtering capabilities.
-
A Filter FastCGI application receives all the information associated with
-
an HTTP request, plus an extra stream of data from a file stored on the
-
web server, and generates a "filtered" version of the data stream as an
-
HTTP response.
-
-
This role is useful for:
-
- Dynamic content transformation (e.g., server-side includes, templating)
-
- Format conversion (e.g., Markdown to HTML, image resizing)
-
- Content compression and optimization
-
- Real-time content modification based on request context
-
-
A Filter is similar in functionality to a Responder that takes a data file
-
as a parameter. The key difference is that with a Filter, both the data file
-
and the Filter itself can be access controlled using the web server's access
-
control mechanisms, while a Responder that takes the name of a data file as
-
a parameter must perform its own access control checks on the data file.
-
-
The web server presents the Filter with environment variables first, then
-
standard input (normally form POST data), and finally the data file input
-
that needs to be processed. *)
-
-
(** {1 Filter Types} *)
-
-
(** Filter request with data stream.
-
-
Represents a complete Filter request including both the HTTP request
-
context and the data stream that needs to be processed. *)
-
type 'a filter_request = {
-
request : 'a Fastcgi_types.request; (** Base FastCGI request containing HTTP context,
-
parameters, and form data. *)
-
data_stream : 'a Eio.Flow.source; (** File data stream to be filtered.
-
This is the content that needs processing. *)
-
data_last_modified : float option; (** File modification time as seconds since epoch.
-
Useful for caching and conditional requests. *)
-
data_length : int option; (** Expected length of the data stream in bytes.
-
May be None if length is unknown. *)
-
}
-
-
(** Filter metadata about the data being processed. *)
-
type filter_metadata = {
-
filename : string option; (** Original filename *)
-
mime_type : string option; (** MIME type of data *)
-
last_modified : float option; (** Last modification time *)
-
size : int option; (** Data size in bytes *)
-
etag : string option; (** Entity tag for caching *)
-
}
-
-
(** Filter context with additional processing information. *)
-
type 'a filter_context = {
-
filter_request : 'a filter_request; (** Request and data *)
-
metadata : filter_metadata; (** File metadata *)
-
cache_control : cache_policy; (** Caching policy *)
-
}
-
-
(** Cache policy for filtered content. *)
-
and cache_policy =
-
| No_cache (** No caching *)
-
| Cache_for of int (** Cache for N seconds *)
-
| Cache_until of float (** Cache until timestamp *)
-
| Conditional_cache of (filter_metadata -> bool) (** Conditional caching *)
-
-
(** {1 Conversion Functions} *)
-
-
(** Convert FastCGI request to filter request.
-
-
Extracts filter-specific information from FastCGI streams
-
and creates a filter request object.
-
-
@param request FastCGI request
-
@return Filter request with data stream *)
-
val request_of_fastcgi : 'a Fastcgi_types.request -> 'a filter_request
-
-
(** Create filter context with metadata.
-
-
@param filter_request Filter request
-
@param metadata File metadata
-
@param cache_control Caching policy
-
@return Filter context *)
-
val context_of_request :
-
'a filter_request ->
-
metadata:filter_metadata ->
-
cache_control:cache_policy ->
-
'a filter_context
-
-
(** {1 Handler Utilities} *)
-
-
(** Filter handler type. *)
-
type 'a filter_handler = 'a Fastcgi_types.request -> 'a Fastcgi_types.response -> Fastcgi_types.response_result
-
-
(** Convenience handler wrapper for filter handlers.
-
-
Converts a filter handler function into a FastCGI handler.
-
This allows writing handlers that work with filter request/response
-
types instead of raw FastCGI types.
-
-
@param handler Filter handler function
-
@return FastCGI filter handler *)
-
-
val filter_handler :
-
('a filter_request -> 'a Fastcgi_types.response -> unit) ->
-
'a filter_handler
-
-
(** Context-aware filter handler.
-
-
Similar to filter_handler but provides additional context
-
including metadata and caching information.
-
-
@param handler Context-aware filter handler
-
@return FastCGI filter handler *)
-
val context_filter_handler :
-
('a filter_context -> 'a Fastcgi_types.response -> unit) ->
-
'a filter_handler
-
-
(** {1 Common Filter Operations} *)
-
-
(** Text processing filters. *)
-
module Text : sig
-
(** Convert text encoding.
-
-
@param from_encoding Source encoding
-
@param to_encoding Target encoding
-
@param input Text stream
-
@param output Filtered stream *)
-
val convert_encoding :
-
from_encoding:string ->
-
to_encoding:string ->
-
input:'a Eio.Flow.source ->
-
output:'a Eio.Flow.sink ->
-
unit
-
-
(** Find and replace text patterns.
-
-
@param pattern Regular expression pattern
-
@param replacement Replacement text
-
@param input Text stream
-
@param output Filtered stream *)
-
val find_replace :
-
pattern:string ->
-
replacement:string ->
-
input:'a Eio.Flow.source ->
-
output:'a Eio.Flow.sink ->
-
unit
-
-
(** Template substitution.
-
-
@param variables Variable substitutions
-
@param input Template stream
-
@param output Processed stream *)
-
val template_substitute :
-
variables:(string * string) list ->
-
input:'a Eio.Flow.source ->
-
output:'a Eio.Flow.sink ->
-
unit
-
-
(** Markdown to HTML conversion.
-
-
@param options Markdown processing options
-
@param input Markdown stream
-
@param output HTML stream *)
-
val markdown_to_html :
-
options:string list ->
-
input:'a Eio.Flow.source ->
-
output:'a Eio.Flow.sink ->
-
unit
-
end
-
-
(** Image processing filters. *)
-
module Image : sig
-
(** Image resize operation.
-
-
@param width Target width
-
@param height Target height
-
@param input Image stream
-
@param output Resized stream *)
-
val resize :
-
width:int ->
-
height:int ->
-
input:'a Eio.Flow.source ->
-
output:'a Eio.Flow.sink ->
-
unit
-
-
(** Image format conversion.
-
-
@param format Target format (jpeg, png, etc.)
-
@param quality Quality setting (for lossy formats)
-
@param input Image stream
-
@param output Converted stream *)
-
val convert_format :
-
format:string ->
-
quality:int option ->
-
input:'a Eio.Flow.source ->
-
output:'a Eio.Flow.sink ->
-
unit
-
-
(** Image compression.
-
-
@param level Compression level
-
@param input Image stream
-
@param output Compressed stream *)
-
val compress :
-
level:int ->
-
input:'a Eio.Flow.source ->
-
output:'a Eio.Flow.sink ->
-
unit
-
end
-
-
(** Data compression filters. *)
-
module Compression : sig
-
(** Gzip compression.
-
-
@param level Compression level (1-9)
-
@param input Data stream
-
@param output Compressed stream *)
-
val gzip :
-
level:int ->
-
input:'a Eio.Flow.source ->
-
output:'a Eio.Flow.sink ->
-
unit
-
-
(** Brotli compression.
-
-
@param level Compression level
-
@param input Data stream
-
@param output Compressed stream *)
-
val brotli :
-
level:int ->
-
input:'a Eio.Flow.source ->
-
output:'a Eio.Flow.sink ->
-
unit
-
-
(** Auto-detect and compress.
-
-
@param preferred_formats Preferred compression formats
-
@param accept_encoding Client Accept-Encoding header
-
@param input Data stream
-
@param output Compressed stream *)
-
val auto_compress :
-
preferred_formats:string list ->
-
accept_encoding:string option ->
-
input:'a Eio.Flow.source ->
-
output:'a Eio.Flow.sink ->
-
string option (** Returns content-encoding used *)
-
end
-
-
(** {1 Caching and Optimization} *)
-
-
(** Check if content should be served from cache.
-
-
@param metadata File metadata
-
@param if_modified_since Client If-Modified-Since header
-
@param if_none_match Client If-None-Match header
-
@return True if content is not modified *)
-
val check_not_modified :
-
metadata:filter_metadata ->
-
if_modified_since:string option ->
-
if_none_match:string option ->
-
bool
-
-
(** Generate appropriate cache headers.
-
-
@param metadata File metadata
-
@param cache_policy Caching policy
-
@return List of cache-related headers *)
-
val generate_cache_headers :
-
metadata:filter_metadata ->
-
cache_policy:cache_policy ->
-
(string * string) list
-
-
(** {1 Streaming Operations} *)
-
-
(** Stream processor for chunk-by-chunk filtering. *)
-
type 'a stream_processor = {
-
process_chunk : bytes -> bytes option; (** Process a data chunk *)
-
finalize : unit -> bytes option; (** Finalize and get remaining data *)
-
reset : unit -> unit; (** Reset processor state *)
-
}
-
-
(** Create a streaming filter.
-
-
@param processor Stream processor
-
@param input Source stream
-
@param output Target stream *)
-
val streaming_filter :
-
processor:'a stream_processor ->
-
input:'a Eio.Flow.source ->
-
output:'a Eio.Flow.sink ->
-
unit
-
-
(** Chain multiple filters together.
-
-
@param filters List of filter functions
-
@param input Source stream
-
@param output Target stream *)
-
val chain_filters :
-
filters:(('a Eio.Flow.source -> 'a Eio.Flow.sink -> unit) list) ->
-
input:'a Eio.Flow.source ->
-
output:'a Eio.Flow.sink ->
-
unit
-
-
(** {1 Error Handling} *)
-
-
(** Filter-specific errors. *)
-
type filter_error =
-
| Data_corruption of string (** Data corruption detected *)
-
| Unsupported_format of string (** Unsupported file format *)
-
| Processing_failed of string (** Filter processing failed *)
-
| Resource_exhausted of string (** Out of memory/disk space *)
-
-
(** Filter exception. *)
-
exception Filter_error of filter_error
-
-
(** Convert filter error to string. *)
-
val filter_error_to_string : filter_error -> string
-
-
(** Safe filter wrapper that handles errors gracefully.
-
-
@param fallback Fallback function if filter fails
-
@param filter Primary filter function
-
@param input Source stream
-
@param output Target stream *)
-
val safe_filter :
-
fallback:('a Eio.Flow.source -> 'a Eio.Flow.sink -> unit) ->
-
filter:('a Eio.Flow.source -> 'a Eio.Flow.sink -> unit) ->
-
input:'a Eio.Flow.source ->
-
output:'a Eio.Flow.sink ->
-
unit
-414
lib/fastcgi_protocol.mli
···
-
(** FastCGI wire protocol parsing and serialization.
-
-
This module handles the low-level FastCGI protocol details including
-
record parsing, name-value pair encoding, and binary data serialization
-
using Eio's Buf_read and Buf_write modules.
-
-
The FastCGI protocol uses a binary record format where all data is
-
transmitted as 8-byte fixed headers followed by variable-length content
-
and optional padding. Multi-byte integers use network byte order for
-
platform independence.
-
-
The protocol supports several key features:
-
- Request multiplexing over single connections
-
- Variable-length encoding for efficient name-value pairs
-
- Stream-based data transmission with proper termination
-
- Alignment padding for optimal performance
-
-
This module provides the low-level primitives for encoding and decoding
-
these protocol elements, allowing higher-level modules to focus on
-
application logic rather than binary protocol details. *)
-
-
(** Protocol parsing error.
-
-
Raised when invalid or malformed protocol data is encountered during
-
parsing operations. This includes version mismatches, invalid record
-
structures, malformed length encodings, and other protocol violations. *)
-
exception Protocol_error of string
-
-
(** {1 Protocol Parsing}
-
-
These functions parse incoming FastCGI records and protocol elements
-
from binary data streams. They validate protocol compliance and convert
-
binary data into OCaml types for processing. *)
-
-
(** Parse a FastCGI record header from a buffer.
-
-
Reads exactly 8 bytes from the buffer to construct a record header.
-
The header contains version, record type, request ID, content length,
-
and padding length fields using network byte order.
-
-
The function validates that the version is supported (currently only
-
version 1) and that the record type is recognized.
-
-
@param buf Input buffer positioned at the start of a record header
-
@return Parsed record header with validated fields
-
@raise Protocol_error if version is unsupported or data is malformed *)
-
val parse_record_header : Eio.Buf_read.t -> Fastcgi_types.record_header
-
-
(** Parse a complete FastCGI record from a buffer.
-
-
Reads a record header followed by the specified amount of content data
-
and padding. This is the primary function for reading FastCGI records
-
from network streams.
-
-
The function ensures that exactly the number of content and padding
-
bytes specified in the header are read, maintaining proper stream
-
alignment for subsequent records.
-
-
@param buf Input buffer positioned at the start of a complete record
-
@return Complete parsed record with header, content, and optional padding
-
@raise Protocol_error if the record structure is invalid *)
-
val parse_record : Eio.Buf_read.t -> Fastcgi_types.record
-
-
(** Parse begin request body from record content.
-
-
Extracts role and connection flags from the 8-byte Begin_request record
-
body. The role indicates whether the application should act as a
-
Responder, Authorizer, or Filter. The flags control connection lifetime
-
management.
-
-
@param content Record content bytes (must be exactly 8 bytes)
-
@return Begin request body with role and flags
-
@raise Protocol_error if content length is wrong or role is unknown *)
-
val parse_begin_request : bytes -> Fastcgi_types.begin_request_body
-
-
(** Parse end request body from record content.
-
-
Extracts application status and protocol status from the 8-byte
-
End_request record body. The application status is similar to a
-
program exit code, while protocol status indicates completion
-
or rejection reasons.
-
-
@param content Record content bytes (must be exactly 8 bytes)
-
@return End request body with status codes
-
@raise Protocol_error if content length is wrong or status is invalid *)
-
val parse_end_request : bytes -> Fastcgi_types.end_request_body
-
-
(** Parse name-value pairs from record content.
-
-
Decodes the compact binary encoding used for parameter transmission.
-
Names and values can be up to 127 bytes (1-byte length) or longer
-
(4-byte length with high bit set). This encoding allows efficient
-
transmission of CGI environment variables and other parameters.
-
-
@param content Record content bytes containing encoded name-value pairs
-
@return List of decoded name-value pairs
-
@raise Protocol_error if the encoding is malformed *)
-
val parse_name_value_pairs : bytes -> Fastcgi_types.name_value_pair list
-
-
(** {1 Protocol Serialization}
-
-
These functions serialize OCaml types into the binary FastCGI protocol
-
format for transmission to web servers. All multi-byte integers are
-
written in network byte order for platform independence. *)
-
-
(** Write a FastCGI record header to a buffer.
-
-
Serializes a record header into exactly 8 bytes using network byte order.
-
The header format is: version (1 byte), type (1 byte), request ID (2 bytes),
-
content length (2 bytes), padding length (1 byte), reserved (1 byte).
-
-
@param buf Output buffer to write the header to
-
@param header Record header to serialize *)
-
val write_record_header : Eio.Buf_write.t -> Fastcgi_types.record_header -> unit
-
-
(** Write a complete FastCGI record to a buffer.
-
-
Serializes a complete record including header, content data, and padding.
-
This is the primary function for sending FastCGI records over network
-
connections. The function ensures proper alignment by writing any
-
specified padding bytes.
-
-
@param buf Output buffer to write the complete record to
-
@param record Complete record with header, content, and optional padding *)
-
val write_record : Eio.Buf_write.t -> Fastcgi_types.record -> unit
-
-
(** Write begin request body to bytes.
-
-
Serializes role and connection flags into the 8-byte Begin_request record
-
body format. The first 2 bytes contain the role, the next byte contains
-
flags, and the remaining 5 bytes are reserved (set to zero).
-
-
@param body Begin request body with role and flags
-
@return 8-byte serialized record body *)
-
val write_begin_request : Fastcgi_types.begin_request_body -> bytes
-
-
(** Write end request body to bytes.
-
-
Serializes application and protocol status into the 8-byte End_request
-
record body format. The first 4 bytes contain the application status,
-
the next byte contains the protocol status, and the remaining 3 bytes
-
are reserved.
-
-
@param body End request body with status codes
-
@return 8-byte serialized record body *)
-
val write_end_request : Fastcgi_types.end_request_body -> bytes
-
-
(** Write name-value pairs to bytes.
-
-
Serializes name-value pairs using the compact binary encoding where
-
each pair is encoded as: name length, value length, name data, value data.
-
Lengths up to 127 bytes use 1-byte encoding; longer lengths use 4-byte
-
encoding with the high bit set.
-
-
@param pairs List of name-value pairs to encode
-
@return Serialized bytes containing all encoded pairs *)
-
val write_name_value_pairs : Fastcgi_types.name_value_pair list -> bytes
-
-
(** {1 Type Conversions} *)
-
-
(** Convert record type to integer. *)
-
val record_type_to_int : Fastcgi_types.record_type -> int
-
-
(** Convert integer to record type.
-
-
@param i Integer value
-
@return Record type
-
@raise Protocol_error if unknown *)
-
val record_type_of_int : int -> Fastcgi_types.record_type
-
-
(** Convert role to integer. *)
-
val role_to_int : Fastcgi_types.role -> int
-
-
(** Convert integer to role.
-
-
@param i Integer value
-
@return Role
-
@raise Protocol_error if unknown *)
-
val role_of_int : int -> Fastcgi_types.role
-
-
(** Convert protocol status to integer. *)
-
val protocol_status_to_int : Fastcgi_types.protocol_status -> int
-
-
(** Convert integer to protocol status.
-
-
@param i Integer value
-
@return Protocol status
-
@raise Protocol_error if unknown *)
-
val protocol_status_of_int : int -> Fastcgi_types.protocol_status
-
-
(** {1 Length Encoding}
-
-
FastCGI uses a variable-length encoding for efficient transmission of
-
length values in name-value pairs. This encoding minimizes overhead
-
for short strings while supporting arbitrarily long values. *)
-
-
(** Encode a length value using FastCGI variable-length encoding.
-
-
Short lengths (0-127) are encoded in a single byte with the high bit
-
clear. Longer lengths (128 and above) are encoded in 4 bytes with the
-
high bit of the first byte set to 1, and the remaining 31 bits containing
-
the actual length value.
-
-
This encoding provides optimal space efficiency for typical use cases
-
where most parameter names and values are relatively short.
-
-
@param length Length value to encode (must be non-negative)
-
@return 1-byte or 4-byte encoded length
-
@raise Invalid_argument if length is negative *)
-
val encode_length : int -> bytes
-
-
(** Decode a length value from FastCGI variable-length encoding.
-
-
Reads either 1 or 4 bytes from the buffer depending on the high bit
-
of the first byte. Returns both the decoded length and the number of
-
bytes consumed to allow proper buffer advancement.
-
-
@param buf Input buffer positioned at the start of an encoded length
-
@return Tuple of (decoded length, bytes consumed)
-
@raise Protocol_error if the encoding is invalid or buffer is too short *)
-
val decode_length : Eio.Buf_read.t -> int * int
-
-
(** {1 Record Construction}
-
-
These convenience functions create properly formatted FastCGI records
-
with correct headers and content. They handle the binary encoding details
-
and ensure protocol compliance. *)
-
-
(** Create a begin request record.
-
-
Constructs a Begin_request record to initiate a new FastCGI request.
-
This record type is sent by web servers to start request processing
-
and specifies the role the application should play.
-
-
The record contains the application role (Responder, Authorizer, or Filter)
-
and connection flags that control whether the connection should be kept
-
alive after the request completes.
-
-
@param request_id Unique request identifier for multiplexing
-
@param role Role the application should play for this request
-
@param flags Connection management flags
-
@return Complete Begin_request record ready for transmission *)
-
val make_begin_request :
-
request_id:Fastcgi_types.request_id ->
-
role:Fastcgi_types.role ->
-
flags:Fastcgi_types.connection_flags ->
-
Fastcgi_types.record
-
-
(** Create an end request record.
-
-
Constructs an End_request record to complete a FastCGI request.
-
This record is sent by applications to indicate request completion
-
and provide both application-level and protocol-level status information.
-
-
The application status is similar to a program exit code, where 0
-
indicates success and non-zero values indicate errors. The protocol
-
status indicates whether the request was completed normally or rejected
-
for protocol-related reasons.
-
-
@param request_id Request ID that is being completed
-
@param app_status Application exit status (0 for success)
-
@param protocol_status Protocol completion status
-
@return Complete End_request record ready for transmission *)
-
val make_end_request :
-
request_id:Fastcgi_types.request_id ->
-
app_status:Fastcgi_types.app_status ->
-
protocol_status:Fastcgi_types.protocol_status ->
-
Fastcgi_types.record
-
-
(** Create a params record.
-
-
@param request_id Request ID
-
@param params Parameter name-value pairs
-
@return Params record *)
-
val make_params_record :
-
request_id:Fastcgi_types.request_id ->
-
params:(string * string) list ->
-
Fastcgi_types.record
-
-
(** Create a stream record (stdin, stdout, stderr, data).
-
-
@param record_type Stream record type
-
@param request_id Request ID
-
@param data Stream data
-
@return Stream record *)
-
val make_stream_record :
-
record_type:Fastcgi_types.record_type ->
-
request_id:Fastcgi_types.request_id ->
-
data:bytes ->
-
Fastcgi_types.record
-
-
(** Create an empty stream record (marks end of stream).
-
-
@param record_type Stream record type
-
@param request_id Request ID
-
@return Empty stream record *)
-
val make_empty_stream_record :
-
record_type:Fastcgi_types.record_type ->
-
request_id:Fastcgi_types.request_id ->
-
Fastcgi_types.record
-
-
(** Create a get values record.
-
-
@param variables Variable names to query
-
@return Get values record *)
-
val make_get_values_record :
-
variables:string list ->
-
Fastcgi_types.record
-
-
(** Create a get values result record.
-
-
@param values Variable name-value pairs
-
@return Get values result record *)
-
val make_get_values_result_record :
-
values:(string * string) list ->
-
Fastcgi_types.record
-
-
(** Create an unknown type record.
-
-
@param unknown_type Unknown record type value
-
@return Unknown type record *)
-
val make_unknown_type_record :
-
unknown_type:int ->
-
Fastcgi_types.record
-
-
(** Create an abort request record.
-
-
@param request_id Request ID to abort
-
@return Abort request record *)
-
val make_abort_request_record :
-
request_id:Fastcgi_types.request_id ->
-
Fastcgi_types.record
-
-
(** {1 Validation}
-
-
These functions validate protocol elements for compliance with FastCGI
-
requirements. They help detect malformed data and ensure protocol
-
correctness during parsing and construction. *)
-
-
(** Validate a record header.
-
-
Checks that a record header contains valid values for all fields.
-
This includes verifying the protocol version, record type bounds,
-
content length limits, and padding length constraints.
-
-
@param header Record header to validate
-
@return True if the header is valid and protocol-compliant
-
@raise Protocol_error if any field contains invalid values *)
-
val validate_record_header : Fastcgi_types.record_header -> bool
-
-
(** Validate a complete record.
-
-
Performs comprehensive validation of a complete record including
-
header validation and content/padding length consistency checks.
-
Ensures the record can be safely transmitted or processed.
-
-
@param record Complete record to validate
-
@return True if the entire record is valid and well-formed
-
@raise Protocol_error if any part of the record is invalid *)
-
val validate_record : Fastcgi_types.record -> bool
-
-
(** Check if a record type is a management record.
-
-
Management records use request ID 0 and contain protocol-level information
-
such as capability queries (Get_values) and unknown type responses.
-
They are not associated with any specific request.
-
-
@param record_type Record type to classify
-
@return True if this is a management record type *)
-
val is_management_record : Fastcgi_types.record_type -> bool
-
-
(** Check if a record type is a stream record.
-
-
Stream records (Params, Stdin, Stdout, Stderr, Data) can be sent in
-
multiple parts and are terminated with an empty record of the same type.
-
This allows streaming of arbitrarily large data without buffering.
-
-
@param record_type Record type to classify
-
@return True if this is a stream record type *)
-
val is_stream_record : Fastcgi_types.record_type -> bool
-
-
(** {1 Protocol Constants}
-
-
Essential constants and utility functions for FastCGI protocol
-
implementation. These values are defined by the protocol specification. *)
-
-
(** Null request ID for management records.
-
-
Management records always use request ID 0 to indicate they are not
-
associated with any specific request. Applications must use non-zero
-
request IDs for all application records. *)
-
val null_request_id : Fastcgi_types.request_id
-
-
(** Maximum content length for a single record.
-
-
FastCGI records can contain at most 65535 bytes of content data.
-
Larger data must be split across multiple records of the same type. *)
-
val max_content_length : int
-
-
(** Maximum padding length for a record.
-
-
Records can have at most 255 bytes of padding for alignment purposes.
-
Padding bytes are ignored by the receiver. *)
-
val max_padding_length : int
-
-
(** Calculate optimal padding for 8-byte alignment.
-
-
Computes the number of padding bytes needed to align the total record
-
size to an 8-byte boundary. This provides optimal performance on most
-
architectures by ensuring proper memory alignment.
-
-
@param content_length Length of the record content data
-
@return Number of padding bytes needed (0-7) *)
-
val calculate_padding : int -> int
-222
lib/fastcgi_responder.mli
···
-
(** Responder role implementation for FastCGI.
-
-
The Responder role is the most common FastCGI role, equivalent to
-
traditional CGI applications. It handles HTTP requests and generates
-
HTTP responses.
-
-
A Responder FastCGI application has the same purpose as a CGI/1.1 program:
-
it receives all the information associated with an HTTP request and generates
-
an HTTP response. This includes CGI environment variables, request headers,
-
request body data, and produces HTTP response headers and body content.
-
-
The key difference from traditional CGI is that Responder applications are
-
long-lived processes that can handle multiple requests efficiently, avoiding
-
the overhead of process creation for each request.
-
-
This module provides high-level abstractions for working with HTTP requests
-
and responses, automatically handling the conversion between FastCGI protocol
-
elements and familiar HTTP concepts. *)
-
-
(** {1 HTTP Request/Response Types} *)
-
-
(** CGI-style environment variables.
-
-
A list of name-value pairs containing the CGI environment variables
-
passed from the web server. These typically include variables like
-
REQUEST_METHOD, REQUEST_URI, HTTP_HOST, CONTENT_TYPE, etc. *)
-
type cgi_env = (string * string) list
-
-
(** HTTP request information extracted from FastCGI parameters.
-
-
This type represents an HTTP request in a convenient form for application
-
processing. It extracts and parses the most commonly needed information
-
from the CGI environment variables provided by the web server. *)
-
type 'a http_request = {
-
method_ : string; (** HTTP method (GET, POST, PUT, DELETE, etc.).
-
Extracted from REQUEST_METHOD CGI variable. *)
-
uri : string; (** Request URI path component, without query string.
-
Extracted from REQUEST_URI or SCRIPT_NAME + PATH_INFO. *)
-
query_string : string; (** URL query string parameters (after the '?' in the URL).
-
Extracted from QUERY_STRING CGI variable. *)
-
content_type : string option; (** MIME type of the request body, if present.
-
Extracted from CONTENT_TYPE CGI variable. *)
-
content_length : int option; (** Length of the request body in bytes, if known.
-
Extracted from CONTENT_LENGTH CGI variable. *)
-
headers : (string * string) list; (** HTTP headers sent by the client. Extracted from
-
CGI variables with HTTP_ prefix. *)
-
body : 'a Eio.Flow.source; (** Request body data stream. For POST requests,
-
this contains form data or payload content. *)
-
}
-
-
(** HTTP response builder for constructing responses.
-
-
This type provides a streaming interface for generating HTTP responses.
-
It handles proper HTTP formatting including status line, headers, and
-
body content. The response is written incrementally to avoid buffering
-
large responses in memory. *)
-
type 'a http_response = {
-
write_status : int -> unit; (** Set the HTTP status code (200, 404, 500, etc.).
-
Must be called before writing headers or body. *)
-
write_header : string -> string -> unit; (** Add an HTTP response header.
-
Common headers include Content-Type, Set-Cookie, etc. *)
-
write_body : string -> unit; (** Write string content to the response body.
-
Handles encoding and streaming automatically. *)
-
write_body_chunk : bytes -> unit; (** Write binary data to the response body.
-
Useful for file downloads or binary content. *)
-
finish : unit -> unit; (** Complete the response and close the connection.
-
Must be called to ensure proper termination. *)
-
}
-
-
(** {1 Conversion Functions} *)
-
-
(** Convert FastCGI request to HTTP request.
-
-
Extracts HTTP-specific information from FastCGI parameters
-
and creates an HTTP request object.
-
-
@param request FastCGI request
-
@return HTTP request with extracted information *)
-
val request_of_fastcgi : 'a Fastcgi_types.request -> 'a http_request
-
-
(** Create HTTP response writer from FastCGI response.
-
-
Wraps FastCGI response streams to provide HTTP-style
-
response writing interface.
-
-
@param response FastCGI response
-
@return HTTP response writer *)
-
val response_of_fastcgi : 'a Fastcgi_types.response -> 'a http_response
-
-
(** {1 Handler Utilities} *)
-
-
(** Responder handler type. *)
-
type 'a responder_handler = 'a Fastcgi_types.request -> 'a Fastcgi_types.response -> Fastcgi_types.response_result
-
-
(** Convenience handler wrapper for HTTP-style handlers.
-
-
Converts an HTTP-style handler function into a FastCGI handler.
-
This allows writing handlers that work with HTTP request/response
-
objects instead of raw FastCGI types.
-
-
@param handler HTTP handler function
-
@return FastCGI responder handler *)
-
val http_handler :
-
('a http_request -> 'a http_response -> unit) ->
-
'a responder_handler
-
-
(** {1 Common HTTP Operations} *)
-
-
(** Send a simple text response.
-
-
@param response HTTP response writer
-
@param status HTTP status code
-
@param content_type MIME type
-
@param body Response body text *)
-
val send_text_response :
-
'a http_response ->
-
status:int ->
-
content_type:string ->
-
body:string ->
-
unit
-
-
(** Send a JSON response.
-
-
@param response HTTP response writer
-
@param status HTTP status code
-
@param json JSON string *)
-
val send_json_response :
-
'a http_response ->
-
status:int ->
-
json:string ->
-
unit
-
-
(** Send an HTML response.
-
-
@param response HTTP response writer
-
@param status HTTP status code
-
@param html HTML content *)
-
val send_html_response :
-
'a http_response ->
-
status:int ->
-
html:string ->
-
unit
-
-
(** Send an error response.
-
-
@param response HTTP response writer
-
@param status HTTP error status code
-
@param message Error message *)
-
val send_error_response :
-
'a http_response ->
-
status:int ->
-
message:string ->
-
unit
-
-
(** Send a redirect response.
-
-
@param response HTTP response writer
-
@param status Redirect status code (301, 302, etc.)
-
@param location Target URL *)
-
val send_redirect_response :
-
'a http_response ->
-
status:int ->
-
location:string ->
-
unit
-
-
(** {1 Request Parsing} *)
-
-
(** Parse query string into parameter map.
-
-
@param query_string URL-encoded query string
-
@return Association list of parameter name-value pairs *)
-
val parse_query_string : string -> (string * string) list
-
-
(** Parse form data from request body.
-
-
Supports both application/x-www-form-urlencoded and
-
multipart/form-data content types.
-
-
@param request HTTP request
-
@return Association list of form field name-value pairs *)
-
val parse_form_data : 'a http_request -> (string * string) list
-
-
(** Get request header value.
-
-
@param request HTTP request
-
@param name Header name (case-insensitive)
-
@return Header value if present *)
-
val get_header : 'a http_request -> string -> string option
-
-
(** Get request parameter from query string or form data.
-
-
@param request HTTP request
-
@param name Parameter name
-
@return Parameter value if present *)
-
val get_param : 'a http_request -> string -> string option
-
-
(** {1 File Operations} *)
-
-
(** File upload information. *)
-
type 'a file_upload = {
-
filename : string option; (** Original filename *)
-
content_type : string option; (** MIME type *)
-
size : int; (** File size in bytes *)
-
data : 'a Eio.Flow.source; (** File content stream *)
-
}
-
-
(** Parse file uploads from multipart form data.
-
-
@param request HTTP request with multipart content
-
@return Association list of field name to file upload *)
-
val parse_file_uploads : 'a http_request -> (string * 'a file_upload) list
-
-
(** Save uploaded file to filesystem.
-
-
@param fs Filesystem capability
-
@param path Destination path
-
@param upload File upload to save *)
-
val save_upload :
-
fs:Eio.Fs.dir_ty Eio.Path.t ->
-
path:string ->
-
upload:'a file_upload ->
-
unit
-444
lib/fastcgi_types.mli
···
-
(** Core FastCGI types and constants.
-
-
This module defines the fundamental types used throughout the
-
FastCGI protocol implementation.
-
-
FastCGI is an open extension to CGI that provides high performance for
-
all Internet applications without the penalties of Web server APIs.
-
Unlike traditional CGI programs that are started for each request,
-
FastCGI applications are long-lived processes that can handle multiple
-
requests over persistent connections. *)
-
-
(** {1 Core Types} *)
-
-
(** FastCGI protocol version.
-
-
The current protocol uses version 1. Future versions of the
-
protocol may increment this value. Applications should check the version
-
field in incoming records and reject unsupported versions.
-
-
Version 1 is represented by the value [1]. *)
-
type version = int
-
-
(** FastCGI record types.
-
-
All data transmitted over a FastCGI connection is packaged in records.
-
Each record has a type that determines how the record's content should
-
be interpreted. The protocol defines both management records (for
-
protocol-level communication) and application records (for request
-
processing).
-
-
Management records contain information that is not specific to any web
-
server request, such as capability queries and unknown type responses.
-
Application records contain information about a particular request,
-
identified by a non-zero request ID. *)
-
type record_type =
-
| Begin_request (** Starts a new request. Sent by the web server to begin
-
processing. Contains the role and connection flags. *)
-
| Abort_request (** Aborts an existing request. Sent by the web server
-
when an HTTP client closes its connection while the
-
request is still being processed. *)
-
| End_request (** Completes a request. Sent by the application to
-
terminate processing, either normally or due to
-
an error condition. *)
-
| Params (** Parameter name-value pairs. Used to transmit CGI-style
-
environment variables from the web server to the
-
application. Sent as a stream. *)
-
| Stdin (** Standard input data. Contains the request body data
-
that would normally be available on stdin in CGI.
-
Sent as a stream. *)
-
| Stdout (** Standard output data. Response data from the application
-
to the web server. This becomes the HTTP response.
-
Sent as a stream. *)
-
| Stderr (** Standard error data. Error messages from the application.
-
Used for logging and debugging. Sent as a stream. *)
-
| Data (** Additional data stream. Used only in the Filter role
-
to transmit file data that needs to be filtered.
-
Sent as a stream. *)
-
| Get_values (** Management record to query application variables.
-
Allows the web server to discover application
-
capabilities like max connections and multiplexing. *)
-
| Get_values_result (** Management record response to Get_values. Contains
-
the requested variable values. *)
-
| Unknown_type (** Management record for unknown type handling. Sent by
-
the application when it receives a record type it
-
doesn't understand. *)
-
-
(** FastCGI application roles.
-
-
A FastCGI application can play one of several well-defined roles.
-
The role determines what information the application receives and
-
what it's expected to produce.
-
-
A FastCGI application plays one of several well-defined roles. The most
-
familiar is the Responder role, in which the application receives all
-
the information associated with an HTTP request and generates an HTTP
-
response. *)
-
type role =
-
| Responder (** The most common role, equivalent to traditional CGI.
-
The application receives all information associated with
-
an HTTP request and generates an HTTP response. This
-
includes environment variables, request headers, and
-
request body data. *)
-
| Authorizer (** Performs access control decisions. The application
-
receives HTTP request information and generates an
-
authorized/unauthorized decision. In case of authorization,
-
it can associate additional variables with the request.
-
For unauthorized requests, it provides a complete HTTP
-
error response. *)
-
| Filter (** Processes data streams with filtering. The application
-
receives HTTP request information plus an additional
-
data stream from a file stored on the web server, and
-
generates a "filtered" version of the data as an HTTP
-
response. Both the file and the filter can be access
-
controlled. *)
-
-
(** Request ID for multiplexing multiple requests over a single connection.
-
-
FastCGI supports multiplexing, allowing multiple concurrent requests
-
to be processed over a single transport connection. Each request is
-
identified by a unique request ID within the scope of that connection.
-
-
The Web server re-uses FastCGI request IDs; the application keeps track
-
of the current state of each request ID on a given transport connection.
-
Request IDs should be small integers to allow efficient tracking using
-
arrays rather than hash tables.
-
-
The value [0] is reserved for management records and is called the
-
"null request ID". *)
-
type request_id = int
-
-
(** Application-level status code.
-
-
When an application completes a request, it provides an application
-
status code similar to the exit status of a traditional CGI program.
-
A value of [0] indicates success, while non-zero values indicate
-
various error conditions.
-
-
The application sets the appStatus component to the status code that
-
the CGI program would have returned via the exit system call. *)
-
type app_status = int
-
-
(** Protocol-level status codes.
-
-
In addition to application status, each request completion includes
-
a protocol-level status that indicates whether the request was
-
processed normally or rejected for protocol-related reasons.
-
-
These status codes allow the web server to understand why a request
-
was not processed and take appropriate action. *)
-
type protocol_status =
-
| Request_complete (** Normal end of request. The application successfully
-
processed the request and the appStatus field
-
indicates the application-level result. *)
-
| Cant_mpx_conn (** Rejecting a new request because the application
-
cannot multiplex connections. This happens when
-
a web server sends concurrent requests over one
-
connection to an application that processes only
-
one request at a time per connection. *)
-
| Overloaded (** Rejecting a new request because the application
-
is overloaded. This occurs when the application
-
runs out of some resource, such as database
-
connections or memory. *)
-
| Unknown_role (** Rejecting a new request because the requested
-
role is unknown to the application. This happens
-
when the web server specifies a role that the
-
application doesn't implement. *)
-
-
(** Connection flags for controlling connection behavior.
-
-
These flags are sent in Begin_request records to control how the
-
connection should be managed after the request completes. *)
-
type connection_flags = {
-
keep_conn : bool; (** Keep connection open after request completion.
-
-
If false, the application closes the connection after
-
responding to this request. If true, the application
-
does not close the connection after responding to this
-
request; the Web server retains responsibility for the
-
connection.
-
-
This flag enables connection reuse for better performance,
-
especially important for high-traffic applications. *)
-
}
-
-
(** {1 Record Types} *)
-
-
(** FastCGI record header.
-
-
Every FastCGI record begins with an 8-byte header that identifies
-
the record type, request ID, and content length. This fixed-length
-
prefix allows efficient parsing and proper demultiplexing of records.
-
-
A FastCGI record consists of a fixed-length prefix followed by a
-
variable number of content and padding bytes.
-
-
The header format is platform-independent and uses network byte order
-
for multi-byte integers. *)
-
type record_header = {
-
version : int; (** Protocol version. Must be [1] for this specification.
-
Future versions may increment this value. *)
-
record_type : record_type; (** Type of this record, determining how to interpret
-
the content data. *)
-
request_id : request_id; (** Request ID this record belongs to. Value [0] is
-
reserved for management records. *)
-
content_length : int; (** Number of bytes in the content data. Must be
-
between [0] and [65535]. *)
-
padding_length : int; (** Number of padding bytes following content.
-
Must be between [0] and [255]. Used for alignment. *)
-
}
-
-
(** Begin request record body.
-
-
This record marks the start of a new request and specifies the role
-
the application should play and connection management flags.
-
-
The Web server sends a FCGI_BEGIN_REQUEST record to start a request.
-
The record body contains the role and flags that control request
-
processing. *)
-
type begin_request_body = {
-
role : role; (** The role the web server expects the application
-
to play for this request. *)
-
flags : connection_flags; (** Flags controlling connection behavior after
-
request completion. *)
-
}
-
-
(** End request record body.
-
-
This record marks the completion of a request and provides both
-
application-level and protocol-level status information.
-
-
The application sends a FCGI_END_REQUEST record to terminate a request,
-
either because the application has processed the request or because the
-
application has rejected the request. *)
-
type end_request_body = {
-
app_status : app_status; (** Application-level status code, similar to
-
a CGI program's exit status. *)
-
protocol_status : protocol_status; (** Protocol-level status indicating normal
-
completion or rejection reason. *)
-
}
-
-
(** Complete FastCGI record.
-
-
A complete record consists of the header, content data, and optional
-
padding. The content interpretation depends on the record type.
-
-
Records support padding to allow senders to keep data aligned for more
-
efficient processing. Experience with the X window system protocols shows
-
the performance benefit of such alignment. *)
-
type record = {
-
header : record_header; (** Fixed 8-byte header with record metadata. *)
-
content : bytes; (** Variable-length content data. Length must match
-
header.content_length. *)
-
padding : bytes option; (** Optional padding data for alignment. Length must
-
match header.padding_length if present. *)
-
}
-
-
(** Name-value pair for parameters.
-
-
FastCGI uses a compact binary encoding for transmitting name-value pairs
-
such as CGI environment variables. This encoding supports both short
-
and long names/values efficiently.
-
-
FastCGI transmits a name-value pair as the length of the name, followed
-
by the length of the value, followed by the name, followed by the value.
-
Lengths of 127 bytes and less can be encoded in one byte, while longer
-
lengths are always encoded in four bytes. *)
-
type name_value_pair = {
-
name : string; (** Parameter name. For CGI compatibility, these are typically
-
uppercase environment variable names like "REQUEST_METHOD". *)
-
value : string; (** Parameter value. The value does not include a terminating
-
null byte in the FastCGI encoding. *)
-
}
-
-
(** {1 Request/Response Model} *)
-
-
(** Request context containing all information for a FastCGI request.
-
-
This high-level type aggregates all the information needed to process
-
a FastCGI request. It combines the protocol-level details (request ID,
-
role, flags) with the application data (parameters and input streams).
-
-
The request follows a two-level processing model: First, the protocol
-
multiplexes a single transport connection between several independent
-
FastCGI requests. Second, within each request the protocol provides
-
several independent data streams in each direction. *)
-
type 'a request = {
-
request_id : request_id; (** Unique identifier for this request within
-
the connection scope. Used for multiplexing. *)
-
role : role; (** The role this application should play for
-
this request (Responder, Authorizer, or Filter). *)
-
flags : connection_flags; (** Connection management flags from the web server. *)
-
params : (string * string) list; (** CGI-style environment variables transmitted
-
via FCGI_PARAMS records. These provide request
-
context like HTTP headers, server info, etc. *)
-
stdin : 'a Eio.Flow.source; (** Standard input stream, containing request body
-
data (equivalent to CGI stdin). For HTTP POST
-
requests, this contains the form data or payload. *)
-
data : 'a Eio.Flow.source option; (** Additional data stream, used only in Filter
-
role. Contains file data that needs to be
-
filtered. [None] for Responder and Authorizer. *)
-
}
-
-
(** Response builder for constructing FastCGI responses.
-
-
This type provides the output streams for sending response data back
-
to the web server. Following CGI conventions, it separates normal
-
output from error output.
-
-
Both stdout and stderr data pass over a single transport connection from
-
the application to the Web server, rather than requiring separate pipes
-
as with CGI/1.1. *)
-
type 'a response = {
-
stdout : 'a Eio.Flow.sink; (** Standard output stream for response data.
-
In Responder role, this contains the HTTP
-
response including headers and body. *)
-
stderr : 'a Eio.Flow.sink; (** Standard error stream for logging and debugging.
-
All role protocols use the FCGI_STDERR stream
-
just the way stderr is used in conventional
-
applications programming: to report application-level
-
errors in an intelligible way. *)
-
}
-
-
(** Complete response with status.
-
-
When an application finishes processing a request, it must provide
-
both application-level and protocol-level status information. This
-
allows the web server to understand the outcome and take appropriate
-
action.
-
-
This corresponds to the FCGI_END_REQUEST record sent by the application. *)
-
type response_result = {
-
app_status : app_status; (** Application exit status, equivalent to what
-
a CGI program would return via exit(). *)
-
protocol_status : protocol_status; (** Protocol-level completion status, indicating
-
normal completion or various rejection reasons. *)
-
}
-
-
(** {1 Protocol Constants} *)
-
-
(** Protocol version constant.
-
-
This constant represents FCGI_VERSION_1. This value should be used in
-
the version field of all record headers. *)
-
val version_1 : int
-
-
(** Standard file descriptor for FastCGI listening socket.
-
-
The Web server leaves a single file descriptor, FCGI_LISTENSOCK_FILENO,
-
open when the application begins execution. This descriptor refers to a
-
listening socket created by the Web server.
-
-
The value equals STDIN_FILENO (0). Applications can distinguish between
-
CGI and FastCGI invocation by calling getpeername() on this descriptor. *)
-
val listensock_fileno : int
-
-
(** {2 Record Type Constants}
-
-
These integer constants correspond to the record_type variants and are
-
used in the binary protocol encoding. These are the values transmitted
-
in the type field of record headers. *)
-
-
(** Value [1]. Starts a new request. *)
-
val fcgi_begin_request : int
-
-
(** Value [2]. Aborts an existing request. *)
-
val fcgi_abort_request : int
-
-
(** Value [3]. Completes a request. *)
-
val fcgi_end_request : int
-
-
(** Value [4]. Parameter name-value pairs. *)
-
val fcgi_params : int
-
-
(** Value [5]. Standard input data. *)
-
val fcgi_stdin : int
-
-
(** Value [6]. Standard output data. *)
-
val fcgi_stdout : int
-
-
(** Value [7]. Standard error data. *)
-
val fcgi_stderr : int
-
-
(** Value [8]. Additional data stream (Filter). *)
-
val fcgi_data : int
-
-
(** Value [9]. Query application variables. *)
-
val fcgi_get_values : int
-
-
(** Value [10]. Response to Get_values. *)
-
val fcgi_get_values_result : int
-
-
(** Value [11]. Unknown record type response. *)
-
val fcgi_unknown_type : int
-
-
(** {2 Role Constants}
-
-
These integer constants correspond to the role variants and are used
-
in Begin_request record bodies. These identify the role the web server
-
expects the application to play. *)
-
-
(** Value [1]. Handle HTTP requests and generate responses. *)
-
val fcgi_responder : int
-
-
(** Value [2]. Perform authorization decisions. *)
-
val fcgi_authorizer : int
-
-
(** Value [3]. Process data streams with filtering. *)
-
val fcgi_filter : int
-
-
(** {2 Flag Constants}
-
-
These constants are used in the flags field of Begin_request records
-
to control connection behavior. *)
-
-
(** Value [1]. Keep connection open after request.
-
If zero, the application closes the connection after
-
responding to this request. If not zero, the application
-
does not close the connection after responding to this
-
request. *)
-
val fcgi_keep_conn : int
-
-
(** {2 Protocol Limits}
-
-
These constants define the maximum sizes for various protocol elements,
-
ensuring compatibility and preventing buffer overflows. *)
-
-
(** Value [65535]. Maximum bytes in record content.
-
Between 0 and 65535 bytes of data, interpreted
-
according to the record type. *)
-
val max_content_length : int
-
-
(** Value [255]. Maximum bytes in record padding.
-
Between 0 and 255 bytes of data, which are ignored. *)
-
val max_padding_length : int
-
-
(** Value [8]. Fixed size of record headers.
-
Number of bytes in a FCGI_Header. Future versions
-
of the protocol will not reduce this number. *)
-
val header_length : int
-
-
(** {2 Management Record Variables}
-
-
These string constants identify the standard management variables that
-
can be queried using FCGI_GET_VALUES records. They allow the web server
-
to discover application capabilities.
-
-
The initial set provides information to help the server perform application
-
and connection management. *)
-
-
(** Variable name "FCGI_MAX_CONNS". The maximum number
-
of concurrent transport connections this application
-
will accept, e.g. "1" or "10". *)
-
val fcgi_max_conns : string
-
-
(** Variable name "FCGI_MAX_REQS". The maximum number
-
of concurrent requests this application will accept,
-
e.g. "1" or "50". *)
-
val fcgi_max_reqs : string
-
-
(** Variable name "FCGI_MPXS_CONNS". "0" if this
-
application does not multiplex connections (i.e.
-
handle concurrent requests over each connection),
-
"1" otherwise. *)
-
val fcgi_mpxs_conns : string
-612
spec/OCAML.md
···
-
# OCaml FastCGI Specification
-
-
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.
-
-
## Table of Contents
-
-
1. [Overview](#overview)
-
2. [Core Types](#core-types)
-
3. [Protocol Constants](#protocol-constants)
-
4. [Record Types](#record-types)
-
5. [Request/Response Model](#requestresponse-model)
-
6. [Application Interface](#application-interface)
-
7. [Connection Management](#connection-management)
-
8. [Role Implementations](#role-implementations)
-
9. [Error Handling](#error-handling)
-
10. [Resource Management](#resource-management)
-
11. [Examples](#examples)
-
-
## Overview
-
-
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.
-
-
### Key Design Principles
-
-
- **Capability-based**: All external resources (network, filesystem) are explicitly passed as capabilities
-
- **Structured concurrency**: Use Eio switches for automatic resource cleanup
-
- **Type safety**: Leverage OCaml's type system to prevent protocol errors
-
- **Performance**: Support connection multiplexing and keep-alive for efficiency
-
- **Extensibility**: Support all three FastCGI roles with room for future extensions
-
-
## Core Types
-
-
```ocaml
-
(** FastCGI protocol version *)
-
type version = int
-
-
(** FastCGI record types *)
-
type record_type =
-
| Begin_request
-
| Abort_request
-
| End_request
-
| Params
-
| Stdin
-
| Stdout
-
| Stderr
-
| Data
-
| Get_values
-
| Get_values_result
-
| Unknown_type
-
-
(** FastCGI roles *)
-
type role =
-
| Responder (** Handle HTTP requests and generate responses *)
-
| Authorizer (** Perform authorization decisions *)
-
| Filter (** Process data streams with filtering *)
-
-
(** Request ID for multiplexing *)
-
type request_id = int
-
-
(** Application status code *)
-
type app_status = int
-
-
(** Protocol status codes *)
-
type protocol_status =
-
| Request_complete (** Normal completion *)
-
| Cant_mpx_conn (** Cannot multiplex connection *)
-
| Overloaded (** Application overloaded *)
-
| Unknown_role (** Unknown role requested *)
-
-
(** Connection flags *)
-
type connection_flags = {
-
keep_conn : bool; (** Keep connection open after request *)
-
}
-
```
-
-
## Protocol Constants
-
-
```ocaml
-
module Constants = struct
-
(** Protocol version *)
-
let version_1 = 1
-
-
(** Standard file descriptor for FastCGI listening socket *)
-
let listensock_fileno = 0
-
-
(** Record type constants *)
-
let fcgi_begin_request = 1
-
let fcgi_abort_request = 2
-
let fcgi_end_request = 3
-
let fcgi_params = 4
-
let fcgi_stdin = 5
-
let fcgi_stdout = 6
-
let fcgi_stderr = 7
-
let fcgi_data = 8
-
let fcgi_get_values = 9
-
let fcgi_get_values_result = 10
-
let fcgi_unknown_type = 11
-
-
(** Role constants *)
-
let fcgi_responder = 1
-
let fcgi_authorizer = 2
-
let fcgi_filter = 3
-
-
(** Flag constants *)
-
let fcgi_keep_conn = 1
-
-
(** Maximum sizes *)
-
let max_content_length = 65535
-
let max_padding_length = 255
-
let header_length = 8
-
-
(** Management record variables *)
-
let fcgi_max_conns = "FCGI_MAX_CONNS"
-
let fcgi_max_reqs = "FCGI_MAX_REQS"
-
let fcgi_mpxs_conns = "FCGI_MPXS_CONNS"
-
end
-
```
-
-
## Record Types
-
-
```ocaml
-
(** FastCGI record header *)
-
type record_header = {
-
version : int;
-
record_type : record_type;
-
request_id : request_id;
-
content_length : int;
-
padding_length : int;
-
}
-
-
(** Begin request record body *)
-
type begin_request_body = {
-
role : role;
-
flags : connection_flags;
-
}
-
-
(** End request record body *)
-
type end_request_body = {
-
app_status : app_status;
-
protocol_status : protocol_status;
-
}
-
-
(** Complete FastCGI record *)
-
type record = {
-
header : record_header;
-
content : bytes;
-
padding : bytes option;
-
}
-
-
(** Name-value pair for parameters *)
-
type name_value_pair = {
-
name : string;
-
value : string;
-
}
-
```
-
-
## Request/Response Model
-
-
```ocaml
-
(** Request context containing all information for a FastCGI request *)
-
type 'a request = {
-
request_id : request_id;
-
role : role;
-
flags : connection_flags;
-
params : (string * string) list;
-
stdin : 'a Eio.Flow.source;
-
data : 'a Eio.Flow.source option; (** Only for Filter role *)
-
}
-
-
(** Response builder for constructing FastCGI responses *)
-
type 'a response = {
-
stdout : 'a Eio.Flow.sink;
-
stderr : 'a Eio.Flow.sink;
-
}
-
-
(** Complete response with status *)
-
type response_result = {
-
app_status : app_status;
-
protocol_status : protocol_status;
-
}
-
```
-
-
## Application Interface
-
-
```ocaml
-
(** Application handler signature *)
-
module Handler = struct
-
(** Responder handler: process HTTP request and generate response *)
-
type 'a responder = 'a request -> 'a response -> response_result
-
-
(** Authorizer handler: make authorization decision *)
-
type 'a authorizer = 'a request -> 'a response -> response_result
-
-
(** Filter handler: process data stream with filtering *)
-
type 'a filter = 'a request -> 'a response -> response_result
-
-
(** Generic handler that can handle any role *)
-
type 'a handler =
-
| Responder of 'a responder
-
| Authorizer of 'a authorizer
-
| Filter of 'a filter
-
end
-
-
(** Application configuration *)
-
type 'a app_config = {
-
max_connections : int; (** Maximum concurrent connections *)
-
max_requests : int; (** Maximum concurrent requests *)
-
multiplex_connections : bool; (** Support connection multiplexing *)
-
handler : 'a Handler.handler; (** Application request handler *)
-
}
-
```
-
-
## Connection Management
-
-
```ocaml
-
(** Connection manager for handling FastCGI protocol *)
-
module Connection = struct
-
(** Opaque connection type *)
-
type 'a t
-
-
(** Create a connection from a network flow *)
-
val create : sw:Eio.Switch.t -> 'a Eio.Flow.two_way -> 'a t
-
-
(** Accept and process a single request on the connection *)
-
val process_request : 'a t -> 'a Handler.handler -> response_result Eio.Promise.t
-
-
(** Get connection statistics *)
-
val stats : 'a t -> {
-
active_requests : int;
-
total_requests : int;
-
bytes_sent : int;
-
bytes_received : int;
-
}
-
-
(** Close the connection gracefully *)
-
val close : 'a t -> unit
-
end
-
-
(** FastCGI server for accepting and managing connections *)
-
module Server = struct
-
(** Server configuration *)
-
type 'a config = {
-
app : 'a app_config;
-
listen_address : [
-
| `Unix of string (** Unix domain socket path *)
-
| `Tcp of Eio.Net.Ipaddr.t * int (** TCP address and port *)
-
];
-
backlog : int; (** Listen backlog size *)
-
max_connections : int; (** Maximum concurrent connections *)
-
}
-
-
(** Run a FastCGI server *)
-
val run :
-
sw:Eio.Switch.t ->
-
net:'a Eio.Net.t ->
-
'a config ->
-
unit
-
-
(** Run server with default configuration *)
-
val run_default :
-
sw:Eio.Switch.t ->
-
net:'a Eio.Net.t ->
-
handler:'a Handler.handler ->
-
listen_address:[`Unix of string | `Tcp of Eio.Net.Ipaddr.t * int] ->
-
unit
-
end
-
```
-
-
## Role Implementations
-
-
### Responder
-
-
```ocaml
-
(** Responder role implementation *)
-
module Responder = struct
-
(** CGI-style environment variables *)
-
type cgi_env = (string * string) list
-
-
(** HTTP request information *)
-
type http_request = {
-
method_ : string; (** HTTP method (GET, POST, etc.) *)
-
uri : string; (** Request URI *)
-
query_string : string; (** Query string parameters *)
-
content_type : string option; (** Content-Type header *)
-
content_length : int option; (** Content-Length header *)
-
headers : (string * string) list; (** Additional HTTP headers *)
-
body : 'a Eio.Flow.source; (** Request body stream *)
-
}
-
-
(** HTTP response builder *)
-
type 'a http_response = {
-
write_status : int -> unit; (** Set HTTP status code *)
-
write_header : string -> string -> unit; (** Add response header *)
-
write_body : string -> unit; (** Write response body *)
-
write_body_chunk : bytes -> unit; (** Write body chunk *)
-
finish : unit -> unit; (** Complete response *)
-
}
-
-
(** Convert FastCGI request to HTTP request *)
-
val request_of_fastcgi : 'a request -> http_request
-
-
(** Create HTTP response writer from FastCGI response *)
-
val response_of_fastcgi : 'a response -> 'a http_response
-
-
(** Convenience handler wrapper for HTTP-style handlers *)
-
val http_handler :
-
(http_request -> 'a http_response -> unit) ->
-
'a Handler.responder
-
end
-
```
-
-
### Authorizer
-
-
```ocaml
-
(** Authorizer role implementation *)
-
module Authorizer = struct
-
(** Authorization result *)
-
type auth_result =
-
| Authorized of (string * string) list (** Authorized with variable bindings *)
-
| Unauthorized of { (** Unauthorized with error response *)
-
status : int;
-
headers : (string * string) list;
-
body : string;
-
}
-
-
(** Authorization request information *)
-
type auth_request = {
-
method_ : string;
-
uri : string;
-
remote_addr : string option;
-
remote_user : string option;
-
auth_type : string option;
-
headers : (string * string) list;
-
}
-
-
(** Convert FastCGI request to authorization request *)
-
val request_of_fastcgi : 'a request -> auth_request
-
-
(** Convert authorization result to FastCGI response *)
-
val response_of_result : auth_result -> 'a response -> response_result
-
-
(** Convenience handler wrapper for authorization handlers *)
-
val auth_handler :
-
(auth_request -> auth_result) ->
-
'a Handler.authorizer
-
end
-
```
-
-
### Filter
-
-
```ocaml
-
(** Filter role implementation *)
-
module Filter = struct
-
(** Filter request with data stream *)
-
type 'a filter_request = {
-
request : 'a request; (** Base FastCGI request *)
-
data_stream : 'a Eio.Flow.source; (** File data to filter *)
-
data_last_modified : float option; (** File modification time *)
-
data_length : int option; (** Expected data length *)
-
}
-
-
(** Convert FastCGI request to filter request *)
-
val request_of_fastcgi : 'a request -> 'a filter_request
-
-
(** Convenience handler wrapper for filter handlers *)
-
val filter_handler :
-
('a filter_request -> 'a response -> unit) ->
-
'a Handler.filter
-
end
-
```
-
-
## Error Handling
-
-
```ocaml
-
(** FastCGI specific errors *)
-
module Error = struct
-
type t =
-
| Protocol_error of string (** Protocol violation *)
-
| Invalid_record of string (** Malformed record *)
-
| Unsupported_version of int (** Unsupported protocol version *)
-
| Unknown_record_type of int (** Unknown record type *)
-
| Request_id_conflict of request_id (** Duplicate request ID *)
-
| Connection_closed (** Connection unexpectedly closed *)
-
| Application_error of string (** Application-specific error *)
-
-
exception Fastcgi_error of t
-
-
(** Convert error to string description *)
-
val to_string : t -> string
-
-
(** Raise a FastCGI error *)
-
val raise : t -> 'a
-
end
-
```
-
-
## Resource Management
-
-
```ocaml
-
(** Resource management utilities *)
-
module Resource = struct
-
(** Request context with automatic cleanup *)
-
type 'a request_context = {
-
request : 'a request;
-
response : 'a response;
-
switch : Eio.Switch.t; (** Switch for request-scoped resources *)
-
}
-
-
(** Create a request context with automatic resource management *)
-
val with_request_context :
-
sw:Eio.Switch.t ->
-
'a request ->
-
('a request_context -> 'b) ->
-
'b
-
-
(** Buffer management for efficient I/O *)
-
module Buffer = struct
-
type t
-
-
val create : size:int -> t
-
val read_into : t -> 'a Eio.Flow.source -> int
-
val write_from : t -> 'a Eio.Flow.sink -> int -> unit
-
val clear : t -> unit
-
end
-
end
-
```
-
-
## Examples
-
-
### Basic Responder Application
-
-
```ocaml
-
open Eio.Std
-
-
let hello_handler request response =
-
let http_req = Responder.request_of_fastcgi request in
-
let http_resp = Responder.response_of_fastcgi response in
-
-
http_resp.write_status 200;
-
http_resp.write_header "Content-Type" "text/html";
-
http_resp.write_body "<h1>Hello, FastCGI!</h1>";
-
http_resp.finish ();
-
-
{ app_status = 0; protocol_status = Request_complete }
-
-
let run_server env =
-
let net = Eio.Stdenv.net env in
-
let config = {
-
app = {
-
max_connections = 10;
-
max_requests = 50;
-
multiplex_connections = true;
-
handler = Handler.Responder hello_handler;
-
};
-
listen_address = `Tcp (Eio.Net.Ipaddr.V4.loopback, 9000);
-
backlog = 5;
-
max_connections = 10;
-
} in
-
-
Switch.run @@ fun sw ->
-
Server.run ~sw ~net config
-
-
let () = Eio_main.run run_server
-
```
-
-
### File-serving Responder
-
-
```ocaml
-
let file_server_handler ~cwd request response =
-
let http_req = Responder.request_of_fastcgi request in
-
let http_resp = Responder.response_of_fastcgi response in
-
-
let path = Eio.Path.(cwd / String.drop_prefix http_req.uri 1) in
-
-
match Eio.Path.load path with
-
| content ->
-
http_resp.write_status 200;
-
http_resp.write_header "Content-Type" "text/html";
-
http_resp.write_body content;
-
http_resp.finish ()
-
| exception (Eio.Io (Eio.Fs.E (Not_found _), _)) ->
-
http_resp.write_status 404;
-
http_resp.write_header "Content-Type" "text/plain";
-
http_resp.write_body "File not found";
-
http_resp.finish ()
-
| exception ex ->
-
http_resp.write_status 500;
-
http_resp.write_header "Content-Type" "text/plain";
-
http_resp.write_body ("Server error: " ^ Printexc.to_string ex);
-
http_resp.finish ();
-
-
{ app_status = 0; protocol_status = Request_complete }
-
-
let run_file_server env =
-
let net = Eio.Stdenv.net env in
-
let cwd = Eio.Stdenv.cwd env in
-
let handler = file_server_handler ~cwd in
-
-
Switch.run @@ fun sw ->
-
Server.run_default ~sw ~net ~handler:(Handler.Responder handler)
-
~listen_address:(`Tcp (Eio.Net.Ipaddr.V4.loopback, 9000))
-
```
-
-
### Authorization Application
-
-
```ocaml
-
let auth_handler auth_req =
-
match auth_req.remote_user with
-
| Some user when String.starts_with user "admin_" ->
-
Authorizer.Authorized [("AUTH_USER", user); ("AUTH_LEVEL", "admin")]
-
| Some user ->
-
Authorizer.Authorized [("AUTH_USER", user); ("AUTH_LEVEL", "user")]
-
| None ->
-
Authorizer.Unauthorized {
-
status = 401;
-
headers = [("WWW-Authenticate", "Basic realm=\"FastCGI\"")];
-
body = "Authentication required";
-
}
-
-
let run_auth_server env =
-
let net = Eio.Stdenv.net env in
-
let handler = Authorizer.auth_handler auth_handler in
-
-
Switch.run @@ fun sw ->
-
Server.run_default ~sw ~net ~handler:(Handler.Authorizer handler)
-
~listen_address:(`Unix "/tmp/auth.sock")
-
```
-
-
### Filter Application
-
-
```ocaml
-
let markdown_filter filter_req response =
-
let data = Eio.Flow.read_all filter_req.data_stream in
-
let html = Markdown.to_html data in (* Assuming markdown library *)
-
-
Eio.Flow.copy_string "Content-Type: text/html\r\n\r\n" response.stdout;
-
Eio.Flow.copy_string html response.stdout;
-
-
{ app_status = 0; protocol_status = Request_complete }
-
-
let run_filter_server env =
-
let net = Eio.Stdenv.net env in
-
let handler = Filter.filter_handler markdown_filter in
-
-
Switch.run @@ fun sw ->
-
Server.run_default ~sw ~net ~handler:(Handler.Filter handler)
-
~listen_address:(`Tcp (Eio.Net.Ipaddr.V4.loopback, 9001))
-
```
-
-
### Connection Management Example
-
-
```ocaml
-
let custom_connection_handler env =
-
let net = Eio.Stdenv.net env in
-
-
Switch.run @@ fun sw ->
-
let server_socket = Eio.Net.listen net ~sw ~reuse_addr:true ~backlog:5
-
(`Tcp (Eio.Net.Ipaddr.V4.loopback, 9000)) in
-
-
let handler = Handler.Responder hello_handler in
-
-
Eio.Net.run_server server_socket (fun flow _addr ->
-
Switch.run @@ fun conn_sw ->
-
let conn = Connection.create ~sw:conn_sw flow in
-
-
let rec process_loop () =
-
match Connection.process_request conn handler with
-
| response -> process_loop ()
-
| exception End_of_file -> ()
-
| exception ex ->
-
Eio.traceln "Connection error: %s" (Printexc.to_string ex)
-
in
-
process_loop ()
-
)
-
```
-
-
## Implementation Notes
-
-
### Wire Protocol Details
-
-
The implementation must handle:
-
-
1. **Binary record format**: 8-byte headers with version, type, request ID, content length, and padding
-
2. **Name-value pair encoding**: Variable-length encoding for parameter names and values
-
3. **Stream management**: Proper handling of stdin, stdout, stderr, and data streams
-
4. **Connection multiplexing**: Support multiple concurrent requests over single connection
-
5. **Error propagation**: Convert protocol errors to appropriate OCaml exceptions
-
-
### Performance Considerations
-
-
1. **Buffer management**: Reuse buffers to minimize allocations
-
2. **Streaming I/O**: Process requests in streaming fashion for large payloads
-
3. **Connection pooling**: Support keep-alive connections for efficiency
-
4. **Concurrent processing**: Use Eio fibers for handling multiple requests
-
-
### Security Considerations
-
-
1. **Input validation**: Validate all protocol fields and reject malformed records
-
2. **Resource limits**: Enforce limits on request size, connection count, etc.
-
3. **Capability restriction**: Use Eio's capability system to limit access
-
4. **Error information**: Avoid leaking sensitive information in error messages
-
-
### Testing Strategy
-
-
The implementation should include:
-
-
1. **Protocol conformance tests**: Verify correct implementation of FastCGI protocol
-
2. **Interoperability tests**: Test with real web servers (nginx, Apache)
-
3. **Performance tests**: Measure throughput and latency under load
-
4. **Error handling tests**: Verify graceful handling of error conditions
-
5. **Mock testing**: Use Eio_mock for deterministic unit tests
-
-
This specification provides a comprehensive foundation for implementing a robust, efficient, and type-safe FastCGI library for OCaml using the Eio effects system.