FastCGI implementation in OCaml

more

+1
.gitignore
···
+
_build
+25
dune-project
···
+
(lang dune 3.0)
+
+
(name fastcgi)
+
+
(generate_opam_files true)
+
+
(source
+
(github ocaml/ocaml-fastcgi))
+
+
(authors "OCaml FastCGI Library Authors")
+
+
(maintainers "OCaml FastCGI Library Authors")
+
+
(license ISC)
+
+
(documentation https://ocaml.github.io/ocaml-fastcgi/)
+
+
(package
+
(name fastcgi)
+
(depends
+
ocaml
+
dune
+
eio)
+
(synopsis "FastCGI protocol implementation for OCaml using Eio")
+
(description "A type-safe implementation of the FastCGI protocol for OCaml using the Eio effects-based IO library. Supports all three FastCGI roles: Responder, Authorizer, and Filter."))
+32
fastcgi.opam
···
+
# This file is generated by dune, edit dune-project instead
+
opam-version: "2.0"
+
synopsis: "FastCGI protocol implementation for OCaml using Eio"
+
description:
+
"A type-safe implementation of the FastCGI protocol for OCaml using the Eio effects-based IO library. Supports all three FastCGI roles: Responder, Authorizer, and Filter."
+
maintainer: ["OCaml FastCGI Library Authors"]
+
authors: ["OCaml FastCGI Library Authors"]
+
license: "ISC"
+
homepage: "https://github.com/ocaml/ocaml-fastcgi"
+
doc: "https://ocaml.github.io/ocaml-fastcgi/"
+
bug-reports: "https://github.com/ocaml/ocaml-fastcgi/issues"
+
depends: [
+
"ocaml"
+
"dune" {>= "3.0"}
+
"eio"
+
"odoc" {with-doc}
+
]
+
build: [
+
["dune" "subst"] {dev}
+
[
+
"dune"
+
"build"
+
"-p"
+
name
+
"-j"
+
jobs
+
"@install"
+
"@runtest" {with-test}
+
"@doc" {with-doc}
+
]
+
]
+
dev-repo: "git+https://github.com/ocaml/ocaml-fastcgi.git"
+18
lib/dune
···
+
(library
+
(public_name fastcgi)
+
(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))
+373
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 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 Eio.Net.Sockaddr.stream * int (** TCP socket address 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:'a Eio.Net.t ->
+
'a 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:'a Eio.Net.t ->
+
handler:'a Handler.handler ->
+
listen_address:[`Unix of string | `Tcp of Eio.Net.Sockaddr.stream * 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