FastCGI implementation in OCaml

Refactor to separate Flow operations from protocol parsing

Move all Eio.Flow-related functions from fastcgi_request to the
higher-level fastcgi module. This creates a cleaner separation where
fastcgi_request handles only protocol parsing/serialization using
Buf_read/Buf_write, while fastcgi provides the Flow-based interface
for connection and request handling.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

+1 -1
bin/fcgi_server.ml
···
@@ fun flow addr ->
Eio.traceln "Accepted connection from %a" Eio.Net.Sockaddr.pp addr;
(* Here you would handle the FastCGI protocol, but for simplicity, we just echo a string. *)
-
let req = Fastcgi.Request.read_request_from_flow ~sw flow in
+
let req = Fastcgi.read_request_from_flow ~sw flow in
match req with
| Error msg ->
Eio.traceln "Failed to read request: %s" msg;
-3
lib/dune
···
fastcgi_record
fastcgi_request
)
-
(modules_without_implementation
-
fastcgi
-
)
)
+102
lib/fastcgi.ml
···
+
module Record = Fastcgi_record
+
+
(** Request-level state machine and application interface *)
+
module Request = Fastcgi_request
+
+
(** Request handler function type *)
+
type handler = Request.t ->
+
stdout:Eio.Flow.sink_ty Eio.Resource.t ->
+
stderr:Eio.Flow.sink_ty Eio.Resource.t ->
+
Request.app_status
+
+
(** [read_request_from_flow ~sw flow] reads a complete FastCGI request from flow.
+
Processes BEGIN_REQUEST, PARAMS, STDIN, and DATA records until complete.
+
Returns the populated request context. *)
+
let read_request_from_flow ~sw:_ flow =
+
let buf_read = Eio.Buf_read.of_flow flow ~max_size:1000000 in
+
Request.read_request buf_read
+
+
(** [write_response ~sw request ~stdout ~stderr sink app_status] writes FastCGI response.
+
Reads from stdout and stderr flows, converts to FastCGI records, and writes to sink.
+
Automatically handles stream termination and END_REQUEST. *)
+
let write_response ~sw:_ request ~stdout ~stderr sink app_status =
+
(* Read stdout content *)
+
let stdout_buf = Buffer.create 4096 in
+
Eio.Flow.copy stdout (Eio.Flow.buffer_sink stdout_buf);
+
let stdout_content = Buffer.contents stdout_buf in
+
+
(* Read stderr content *)
+
let stderr_buf = Buffer.create 1024 in
+
Eio.Flow.copy stderr (Eio.Flow.buffer_sink stderr_buf);
+
let stderr_content = Buffer.contents stderr_buf in
+
+
(* Write response using Buf_write *)
+
Eio.Buf_write.with_flow sink (fun buf_write ->
+
Request.write_stdout_records buf_write request.Request.request_id stdout_content;
+
Request.write_stderr_records buf_write request.Request.request_id stderr_content;
+
Request.write_end_request buf_write request.Request.request_id app_status Request.Request_complete
+
)
+
+
(** [process_request ~sw request handler sink] processes complete request.
+
Calls handler with flows for stdout/stderr output, then writes response to sink. *)
+
let process_request ~sw request handler sink =
+
(* Create in-memory flows for stdout and stderr *)
+
let stdout_buf = Buffer.create 4096 in
+
let stderr_buf = Buffer.create 1024 in
+
let stdout_sink = Eio.Flow.buffer_sink stdout_buf in
+
let stderr_sink = Eio.Flow.buffer_sink stderr_buf in
+
+
(* Call handler *)
+
let app_status = handler request ~stdout:stdout_sink ~stderr:stderr_sink in
+
+
(* Convert buffers to sources and write response *)
+
let stdout_source = Eio.Flow.string_source (Buffer.contents stdout_buf) in
+
let stderr_source = Eio.Flow.string_source (Buffer.contents stderr_buf) in
+
+
write_response ~sw request ~stdout:stdout_source ~stderr:stderr_source sink app_status
+
+
(** [process_request_with_flows ~sw request ~stdout ~stderr sink app_status]
+
processes request using provided output flows. *)
+
let process_request_with_flows ~sw request ~stdout ~stderr sink app_status =
+
write_response ~sw request ~stdout ~stderr sink app_status
+
+
(** {1 Connection Management} *)
+
+
(** [handle_connection ~sw flow handler] handles complete FastCGI connection.
+
Reads requests from flow, processes them with handler, multiplexes responses.
+
Continues until connection is closed. *)
+
let handle_connection ~sw flow handler =
+
let rec loop () =
+
try
+
(* Read next request *)
+
match read_request_from_flow ~sw flow with
+
| Error msg ->
+
(* Log error and continue or close connection *)
+
Printf.eprintf "Error reading request: %s\n%!" msg
+
| Ok request ->
+
(* Process request *)
+
let response_buf = Buffer.create 4096 in
+
let response_sink = Eio.Flow.buffer_sink response_buf in
+
+
process_request ~sw request handler response_sink;
+
+
(* Write response to connection *)
+
let response_data = Buffer.contents response_buf in
+
Eio.Flow.copy (Eio.Flow.string_source response_data) flow;
+
+
(* Continue if keep_conn is true *)
+
if request.Request.keep_conn then
+
loop ()
+
with
+
| End_of_file -> () (* Connection closed *)
+
| exn ->
+
Printf.eprintf "Connection error: %s\n%!" (Printexc.to_string exn)
+
in
+
loop ()
+
+
(** [serve ~sw ~backlog ~port handler] creates FastCGI server.
+
Listens on port, accepts connections, handles each with handler. *)
+
let serve ~sw:_ ~backlog:_ ~port:_ _handler =
+
(* This would typically use Eio.Net to create a listening socket *)
+
(* For now, we'll provide a placeholder implementation *)
+
failwith "serve: Implementation requires Eio.Net integration"
+64 -1
lib/fastcgi.mli
···
This library provides a complete implementation of the FastCGI protocol
for building high-performance web applications in OCaml. *)
+
(** {1 Core Protocol Components} *)
(** Record-level protocol handling *)
module Record = Fastcgi_record
(** Request-level state machine and application interface *)
-
module Request = Fastcgi_request
+
module Request = Fastcgi_request
+
+
(** {1 High-level Request Processing} *)
+
+
(** Request handler function type *)
+
type handler = Request.t ->
+
stdout:Eio.Flow.sink_ty Eio.Resource.t ->
+
stderr:Eio.Flow.sink_ty Eio.Resource.t ->
+
Request.app_status
+
+
(** [read_request_from_flow ~sw flow] reads a complete FastCGI request from flow.
+
Processes BEGIN_REQUEST, PARAMS, STDIN, and DATA records until complete.
+
Returns the populated request context. *)
+
val read_request_from_flow : sw:Eio.Switch.t -> 'a Eio.Flow.source -> (Request.t, string) result
+
+
(** [write_response ~sw request ~stdout ~stderr sink app_status] writes FastCGI response.
+
Reads from stdout and stderr flows, converts to FastCGI records, and writes to sink.
+
Automatically handles stream termination and END_REQUEST. *)
+
val write_response :
+
sw:Eio.Switch.t ->
+
Request.t ->
+
stdout:'a Eio.Flow.source ->
+
stderr:'a Eio.Flow.source ->
+
'a Eio.Flow.sink ->
+
Request.app_status -> unit
+
+
(** [process_request ~sw request handler sink] processes complete request.
+
Calls handler with flows for stdout/stderr output, then writes response to sink. *)
+
val process_request :
+
sw:Eio.Switch.t ->
+
Request.t ->
+
handler ->
+
Eio.Flow.sink_ty Eio.Resource.t -> unit
+
+
(** [process_request_with_flows ~sw request ~stdout ~stderr sink app_status]
+
processes request using provided output flows. *)
+
val process_request_with_flows :
+
sw:Eio.Switch.t ->
+
Request.t ->
+
stdout:'a Eio.Flow.source ->
+
stderr:'a Eio.Flow.source ->
+
'a Eio.Flow.sink ->
+
Request.app_status -> unit
+
+
(** {1 Connection Management} *)
+
+
(** [handle_connection ~sw flow handler] handles complete FastCGI connection.
+
Reads requests from flow, processes them with handler, multiplexes responses.
+
Continues until connection is closed. *)
+
val handle_connection :
+
sw:Eio.Switch.t ->
+
Eio.Flow.two_way_ty Eio.Resource.t ->
+
handler ->
+
unit
+
+
(** [serve ~sw ~backlog ~port handler] creates FastCGI server.
+
Listens on port, accepts connections, handles each with handler. *)
+
val serve :
+
sw:Eio.Switch.t ->
+
backlog:int ->
+
port:int ->
+
handler ->
+
unit
+47 -130
lib/fastcgi_request.ml
···
String.length record.content = 0
-
let read_params_from_flow ~sw:_ buf_read =
+
let read_params buf_read =
Printf.eprintf "[DEBUG] read_params_from_flow: Starting\n%!";
let params = ref KV.empty in
let rec loop () =
···
in
loop ()
-
let read_stdin_from_flow ~sw:_ buf_read =
+
let read_stdin buf_read =
Printf.eprintf "[DEBUG] read_stdin_from_flow: Starting\n%!";
let data = Buffer.create 1024 in
let rec loop () =
···
loop ()
(** Read DATA stream for Filter role *)
-
let read_data_from_flow buf_read =
+
let read_data buf_read =
let data_buf = Buffer.create 1024 in
let rec read_data () =
try
···
read_data ()
(** Read request streams based on role *)
-
let read_request_streams ~sw request buf_read =
+
let read_request_streams request buf_read =
Printf.eprintf "[DEBUG] read_request_streams: Processing role=%s\n%!"
(Format.asprintf "%a" pp_role request.role);
match request.role with
···
Ok request
| Responder ->
Printf.eprintf "[DEBUG] read_request_streams: Responder role, reading STDIN\n%!";
-
let* stdin_data = read_stdin_from_flow ~sw buf_read in
+
let* stdin_data = read_stdin buf_read in
Printf.eprintf "[DEBUG] read_request_streams: Got STDIN data, %d bytes\n%!" (String.length stdin_data);
Ok { request with stdin_data }
| Filter ->
Printf.eprintf "[DEBUG] read_request_streams: Filter role, reading STDIN and DATA\n%!";
-
let* stdin_data = read_stdin_from_flow ~sw buf_read in
+
let* stdin_data = read_stdin buf_read in
Printf.eprintf "[DEBUG] read_request_streams: Got STDIN data, %d bytes\n%!" (String.length stdin_data);
let request = { request with stdin_data } in
-
let* data = read_data_from_flow buf_read in
+
let* data = read_data buf_read in
Printf.eprintf "[DEBUG] read_request_streams: Got DATA stream, %d bytes\n%!" (String.length data);
Ok { request with data_stream = Some data }
-
let read_request_from_flow ~sw flow =
-
Printf.eprintf "[DEBUG] read_request_from_flow: Starting\n%!";
-
let buf_read = Eio.Buf_read.of_flow flow ~max_size:1000000 in
+
let read_request buf_read =
+
Printf.eprintf "[DEBUG] read_request: Starting\n%!";
try
(* Read BEGIN_REQUEST *)
-
Printf.eprintf "[DEBUG] read_request_from_flow: Reading BEGIN_REQUEST record\n%!";
+
Printf.eprintf "[DEBUG] read_request: Reading BEGIN_REQUEST record\n%!";
let begin_record = Fastcgi_record.read buf_read in
-
Printf.eprintf "[DEBUG] read_request_from_flow: Got BEGIN_REQUEST record: %s\n%!"
+
Printf.eprintf "[DEBUG] read_request: Got BEGIN_REQUEST record: %s\n%!"
(Format.asprintf "%a" (Fastcgi_record.pp ~max_content_len:50) begin_record);
let* request = create begin_record in
-
Printf.eprintf "[DEBUG] read_request_from_flow: Created request with role=%s, id=%d\n%!"
+
Printf.eprintf "[DEBUG] read_request: Created request with role=%s, id=%d\n%!"
(Format.asprintf "%a" pp_role request.role) request.request_id;
(* Read PARAMS stream *)
-
Printf.eprintf "[DEBUG] read_request_from_flow: Reading PARAMS stream\n%!";
-
let* params = read_params_from_flow ~sw buf_read in
-
Printf.eprintf "[DEBUG] read_request_from_flow: Got %d params\n%!" (Fastcgi_record.KV.cardinal params);
+
Printf.eprintf "[DEBUG] read_request: Reading PARAMS stream\n%!";
+
let* params = read_params buf_read in
+
Printf.eprintf "[DEBUG] read_request: Got %d params\n%!" (Fastcgi_record.KV.cardinal params);
let request = { request with params } in
(* Read remaining streams based on role *)
-
Printf.eprintf "[DEBUG] read_request_from_flow: Reading streams for role=%s\n%!"
+
Printf.eprintf "[DEBUG] read_request: Reading streams for role=%s\n%!"
(Format.asprintf "%a" pp_role request.role);
-
let result = read_request_streams ~sw request buf_read in
-
Printf.eprintf "[DEBUG] read_request_from_flow: Finished reading request\n%!";
+
let result = read_request_streams request buf_read in
+
Printf.eprintf "[DEBUG] read_request: Finished reading request\n%!";
result
with
| End_of_file ->
-
Printf.eprintf "[DEBUG] read_request_from_flow: Hit End_of_file\n%!";
+
Printf.eprintf "[DEBUG] read_request: Hit End_of_file\n%!";
Error "Unexpected end of stream"
| exn ->
-
Printf.eprintf "[DEBUG] read_request_from_flow: Exception: %s\n%!" (Printexc.to_string exn);
+
Printf.eprintf "[DEBUG] read_request: Exception: %s\n%!" (Printexc.to_string exn);
Error (Printf.sprintf "Error reading request: %s" (Printexc.to_string exn))
(** {1 Response Generation} *)
···
List.rev !records
-
let flow_to_stream_records ~sw:_ ~request_id ~record_type flow =
-
(* Read entire flow content *)
-
let buf = Buffer.create 4096 in
-
Eio.Flow.copy flow (Eio.Flow.buffer_sink buf);
-
let content = Buffer.contents buf in
-
string_to_stream_records ~request_id ~record_type content
-
-
let write_stream_records records sink =
-
(* Create a function to serialize a single record to a string *)
-
let serialize_record record =
-
let buf = Buffer.create 512 in
-
let buf_sink = Eio.Flow.buffer_sink buf in
-
Eio.Buf_write.with_flow buf_sink (fun buf_write ->
-
Fastcgi_record.write buf_write record
-
);
-
Buffer.contents buf
+
let write_stream_records buf_write request_id record_type content =
+
let max_chunk = 65535 in (* FastCGI max record content length *)
+
let len = String.length content in
+
+
let rec chunk_string pos =
+
if pos >= len then
+
() (* Empty terminator will be added separately *)
+
else
+
let chunk_len = min max_chunk (len - pos) in
+
let chunk = String.sub content pos chunk_len in
+
let record = Fastcgi_record.create ~version:1 ~record:record_type ~request_id ~content:chunk in
+
Fastcgi_record.write buf_write record;
+
chunk_string (pos + chunk_len)
in
-
(* Serialize all records and write to sink *)
-
List.iter (fun record ->
-
let serialized = serialize_record record in
-
Eio.Flow.copy_string serialized sink
-
) records
+
chunk_string 0;
+
+
(* Add stream terminator *)
+
let terminator = Fastcgi_record.create ~version:1 ~record:record_type ~request_id ~content:"" in
+
Fastcgi_record.write buf_write terminator
-
let make_end_request ~request_id ~app_status ~protocol_status =
+
let write_stdout_records buf_write request_id content =
+
write_stream_records buf_write request_id Stdout content
+
+
let write_stderr_records buf_write request_id content =
+
write_stream_records buf_write request_id Stderr content
+
+
let write_end_request buf_write request_id app_status protocol_status =
let content =
let buf = Bytes.create 8 in
Bytes.set_int32_be buf 0 (Int32.of_int app_status);
···
Bytes.set_uint8 buf 7 0; (* reserved *)
Bytes.to_string buf
in
-
Fastcgi_record.create ~version:1 ~record:End_request ~request_id ~content
-
-
let write_response ~sw request ~stdout ~stderr sink app_status =
-
(* Convert stdout flow to STDOUT records *)
-
let stdout_records = flow_to_stream_records ~sw ~request_id:request.request_id ~record_type:Stdout stdout in
-
-
(* Convert stderr flow to STDERR records *)
-
let stderr_records = flow_to_stream_records ~sw ~request_id:request.request_id ~record_type:Stderr stderr in
-
-
(* Create END_REQUEST record *)
-
let end_record = make_end_request ~request_id:request.request_id ~app_status ~protocol_status:Request_complete in
-
-
(* Write all records *)
-
let all_records = stdout_records @ stderr_records @ [end_record] in
-
write_stream_records all_records sink
-
-
let write_error_response request sink proto_status =
-
let end_record = make_end_request ~request_id:request.request_id ~app_status:1 ~protocol_status:proto_status in
-
write_stream_records [end_record] sink
-
-
let write_abort_response request sink =
-
let end_record = make_end_request ~request_id:request.request_id ~app_status:0 ~protocol_status:Request_complete in
-
write_stream_records [end_record] sink
-
-
(** {1 High-level Request Processing} *)
-
-
type handler = t ->
-
stdout:Eio.Flow.sink_ty Eio.Resource.t ->
-
stderr:Eio.Flow.sink_ty Eio.Resource.t ->
-
app_status
-
-
let process_request ~sw request handler sink =
-
(* Create in-memory flows for stdout and stderr *)
-
let stdout_buf = Buffer.create 4096 in
-
let stderr_buf = Buffer.create 1024 in
-
let stdout_sink = Eio.Flow.buffer_sink stdout_buf in
-
let stderr_sink = Eio.Flow.buffer_sink stderr_buf in
-
-
(* Call handler *)
-
let app_status = handler request ~stdout:stdout_sink ~stderr:stderr_sink in
-
-
(* Convert buffers to sources and write response *)
-
let stdout_source = Eio.Flow.string_source (Buffer.contents stdout_buf) in
-
let stderr_source = Eio.Flow.string_source (Buffer.contents stderr_buf) in
-
-
write_response ~sw request ~stdout:stdout_source ~stderr:stderr_source sink app_status
-
-
let process_request_with_flows ~sw request ~stdout ~stderr sink app_status =
-
write_response ~sw request ~stdout ~stderr sink app_status
-
-
(** {1 Connection Management} *)
-
-
let handle_connection ~sw flow handler =
-
let _buf_read = Eio.Buf_read.of_flow flow ~max_size:1000000 in
-
let _buf_write = Eio.Buf_write.create 4096 in
-
-
let rec loop () =
-
try
-
(* Read next request *)
-
match read_request_from_flow ~sw flow with
-
| Error msg ->
-
(* Log error and continue or close connection *)
-
Printf.eprintf "Error reading request: %s\n%!" msg
-
| Ok request ->
-
(* Process request *)
-
let response_buf = Buffer.create 4096 in
-
let response_sink = Eio.Flow.buffer_sink response_buf in
-
-
process_request ~sw request handler response_sink;
-
-
(* Write response to connection *)
-
let response_data = Buffer.contents response_buf in
-
Eio.Flow.copy (Eio.Flow.string_source response_data) flow;
-
-
(* Continue if keep_conn is true *)
-
if request.keep_conn then
-
loop ()
-
with
-
| End_of_file -> () (* Connection closed *)
-
| exn ->
-
Printf.eprintf "Connection error: %s\n%!" (Printexc.to_string exn)
-
in
-
loop ()
+
let record = Fastcgi_record.create ~version:1 ~record:End_request ~request_id ~content in
+
Fastcgi_record.write buf_write record
-
let serve ~sw:_ ~backlog:_ ~port:_ _handler =
-
(* This would typically use Eio.Net to create a listening socket *)
-
(* For now, we'll provide a placeholder implementation *)
-
failwith "serve: Implementation requires Eio.Net integration"
+21 -104
lib/fastcgi_request.mli
···
(** {1 Stream Processing} *)
-
(** [read_request_from_flow ~sw flow] reads a complete FastCGI request from flow.
-
Processes BEGIN_REQUEST, PARAMS, STDIN, and DATA records until complete.
-
Returns the populated request context. *)
-
val read_request_from_flow : sw:Eio.Switch.t -> 'a Eio.Flow.source -> (t, string) result
-
-
(** [read_params_from_flow ~sw buf_read] reads PARAMS stream from buf_read until empty record.
+
(** [read_params buf_read] reads PARAMS stream from buf_read until empty record.
Returns the accumulated parameters. *)
-
val read_params_from_flow : sw:Eio.Switch.t -> Eio.Buf_read.t -> (Fastcgi_record.KV.t, string) result
+
val read_params : Eio.Buf_read.t -> (Fastcgi_record.KV.t, string) result
-
(** [read_stdin_from_flow ~sw buf_read] reads STDIN stream from buf_read until empty record.
+
(** [read_stdin buf_read] reads STDIN stream from buf_read until empty record.
Returns the accumulated data. *)
-
val read_stdin_from_flow : sw:Eio.Switch.t -> Eio.Buf_read.t -> (string, string) result
+
val read_stdin : Eio.Buf_read.t -> (string, string) result
+
+
(** [read_data buf_read] reads DATA stream from buf_read until empty record.
+
Returns the accumulated data for Filter role. *)
+
val read_data : Eio.Buf_read.t -> (string, string) result
+
+
(** [read_request buf_read] reads a complete FastCGI request from buf_read.
+
Processes BEGIN_REQUEST, PARAMS, STDIN, and DATA records until complete.
+
Returns the populated request context. *)
+
val read_request : Eio.Buf_read.t -> (t, string) result
(** {1 Response Generation} *)
···
(** [pp_protocol_status ppf status] pretty-prints protocol status *)
val pp_protocol_status : Format.formatter -> protocol_status -> unit
-
(** [write_response ~sw request ~stdout ~stderr sink] writes FastCGI response.
-
Reads from stdout and stderr flows, converts to FastCGI records, and writes to sink.
-
Automatically handles stream termination and END_REQUEST. *)
-
val write_response :
-
sw:Eio.Switch.t ->
-
t ->
-
stdout:'a Eio.Flow.source ->
-
stderr:'a Eio.Flow.source ->
-
'a Eio.Flow.sink ->
-
app_status -> unit
+
(** [write_stdout_records buf_write request_id content] writes STDOUT stream records.
+
Splits content into chunks and writes with terminator. *)
+
val write_stdout_records : Eio.Buf_write.t -> Fastcgi_record.request_id -> string -> unit
-
(** [write_error_response request sink proto_status] writes error END_REQUEST record *)
-
val write_error_response : t -> 'a Eio.Flow.sink -> protocol_status -> unit
+
(** [write_stderr_records buf_write request_id content] writes STDERR stream records.
+
Splits content into chunks and writes with terminator. *)
+
val write_stderr_records : Eio.Buf_write.t -> Fastcgi_record.request_id -> string -> unit
-
(** [write_abort_response request sink] writes END_REQUEST for aborted request *)
-
val write_abort_response : t -> 'a Eio.Flow.sink -> unit
+
(** [write_end_request buf_write request_id app_status protocol_status] writes END_REQUEST record. *)
+
val write_end_request : Eio.Buf_write.t -> Fastcgi_record.request_id -> app_status -> protocol_status -> unit
-
(** {1 High-level Request Processing} *)
-
-
(** Request handler function type *)
-
type handler = t ->
-
stdout:Eio.Flow.sink_ty Eio.Resource.t ->
-
stderr:Eio.Flow.sink_ty Eio.Resource.t ->
-
app_status
-
-
(** [process_request ~sw request handler sink] processes complete request.
-
Calls handler with flows for stdout/stderr output, then writes response to sink. *)
-
val process_request :
-
sw:Eio.Switch.t ->
-
t ->
-
handler ->
-
Eio.Flow.sink_ty Eio.Resource.t -> unit
-
-
(** [process_request_with_flows ~sw request ~stdout ~stderr sink app_status]
-
processes request using provided output flows. *)
-
val process_request_with_flows :
-
sw:Eio.Switch.t ->
-
t ->
-
stdout:'a Eio.Flow.source ->
-
stderr:'a Eio.Flow.source ->
-
'a Eio.Flow.sink ->
-
app_status -> unit
-
-
(** {1 Connection Management} *)
-
-
(** [handle_connection ~sw flow handler] handles complete FastCGI connection.
-
Reads requests from flow, processes them with handler, multiplexes responses.
-
Continues until connection is closed. *)
-
val handle_connection :
-
sw:Eio.Switch.t ->
-
Eio.Flow.two_way_ty Eio.Resource.t ->
-
handler ->
-
unit
-
-
(** [serve ~sw ~backlog ~port handler] creates FastCGI server.
-
Listens on port, accepts connections, handles each with handler. *)
-
val serve :
-
sw:Eio.Switch.t ->
-
backlog:int ->
-
port:int ->
-
handler ->
-
unit
(** {1 Utilities} *)
···
record_type:Fastcgi_record.record ->
string -> Fastcgi_record.t list
-
(** [flow_to_stream_records ~sw ~request_id ~record_type flow] converts flow to stream records *)
-
val flow_to_stream_records :
-
sw:Eio.Switch.t ->
-
request_id:Fastcgi_record.request_id ->
-
record_type:Fastcgi_record.record ->
-
'a Eio.Flow.source -> Fastcgi_record.t list
-
(** [write_stream_records records sink] writes stream records to flow *)
-
val write_stream_records : Fastcgi_record.t list -> 'a Eio.Flow.sink -> unit
-
-
(** {1 Example Usage} *)
-
-
(** {2 Simple Handler}
-
{[
-
let my_handler request ~stdout ~stderr =
-
(* Write CGI headers *)
-
Eio.Flow.copy_string "Content-Type: text/html\r\n\r\n" stdout;
-
-
(* Generate response based on request.params *)
-
let method_ = Fastcgi_record.KV.find "REQUEST_METHOD" request.params in
-
Eio.Flow.copy_string ("<h1>Hello from " ^ method_ ^ "</h1>") stdout;
-
-
(* Optional error logging *)
-
if method_ = "POST" then
-
Eio.Flow.copy_string "Processing POST data\n" stderr;
-
-
0 (* Success status *)
-
]}
-
-
{2 Server Setup}
-
{[
-
let () =
-
Eio_main.run @@ fun env ->
-
Eio.Switch.run @@ fun sw ->
-
Fastcgi.Request.serve ~sw ~backlog:128 ~port:9000 my_handler
-
]}
-
*)