···11+open Cmdliner
22+33+let run port =
44+ Eio_main.run @@ fun env ->
55+ Eio.Switch.run @@ fun sw ->
66+ let net = Eio.Stdenv.net env in
77+ let addr = `Tcp (Eio.Net.Ipaddr.V4.loopback, port) in
88+ let server_socket = Eio.Net.listen net ~backlog:10 ~reuse_addr:true ~sw addr in
99+ Eio.traceln "FastCGI server listening on port %d" port;
1010+ Eio.Net.run_server server_socket ~on_error:(fun ex -> Eio.traceln "Error: %s" (Printexc.to_string ex))
1111+ @@ fun flow addr ->
1212+ Eio.traceln "Accepted connection from %a" Eio.Net.Sockaddr.pp addr;
1313+ (* Here you would handle the FastCGI protocol, but for simplicity, we just echo a string. *)
1414+ let req = Fastcgi.Request.read_request_from_flow ~sw flow in
1515+ match req with
1616+ | Error msg ->
1717+ Eio.traceln "Failed to read request: %s" msg;
1818+ Eio.Flow.close flow
1919+ | Ok req ->
2020+ Eio.traceln "Received request: %a" Fastcgi.Request.pp req;
2121+ Eio.Flow.close flow
2222+2323+let port =
2424+ let doc = "Port to listen on" in
2525+ Arg.(value & opt int 9000 & info ["p"; "port"] ~docv:"PORT" ~doc)
2626+2727+let cmd =
2828+ let doc = "FastCGI server" in
2929+ let info = Cmd.info "fcgi-server" ~doc in
3030+ Cmd.v info Term.(const run $ port)
3131+3232+let () = exit (Cmd.eval cmd)
+3-1
dune-project
···2020 (depends
2121 ocaml
2222 dune
2323- eio)
2323+ eio
2424+ cmdliner
2525+ eio_main)
2426 (synopsis "FastCGI protocol implementation for OCaml using Eio")
2527 (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."))
···9898let fcgi_header_len = 8
9999100100let read buf_read =
101101+ Printf.eprintf "[DEBUG] Fastcgi_record.read: Starting to read record\n%!";
101102 (* Read the 8-byte header *)
103103+ Printf.eprintf "[DEBUG] Fastcgi_record.read: Reading %d-byte header\n%!" fcgi_header_len;
102104 let header = Eio.Buf_read.take fcgi_header_len buf_read in
103105104106 (* Parse header fields *)
···109111 let padding_length = Char.code header.[6] in
110112 let _reserved = Char.code header.[7] in
111113114114+ Printf.eprintf "[DEBUG] Fastcgi_record.read: Header parsed - version=%d, type=%d, id=%d, content_len=%d, padding=%d\n%!"
115115+ version record_type_int request_id content_length padding_length;
116116+112117 (* Validate version *)
113118 if version <> fcgi_version_1 then invalid_version version;
114119115120 (* Convert record type *)
116121 let record_type = record_of_int record_type_int in
122122+ Printf.eprintf "[DEBUG] Fastcgi_record.read: Record type = %s\n%!"
123123+ (Format.asprintf "%a" pp_record record_type);
117124118125 (* Read content *)
119126 let content =
120120- if content_length = 0 then
127127+ if content_length = 0 then (
128128+ Printf.eprintf "[DEBUG] Fastcgi_record.read: No content to read (length=0)\n%!";
121129 ""
122122- else
123123- Eio.Buf_read.take content_length buf_read
130130+ ) else (
131131+ Printf.eprintf "[DEBUG] Fastcgi_record.read: Reading %d bytes of content\n%!" content_length;
132132+ let c = Eio.Buf_read.take content_length buf_read in
133133+ Printf.eprintf "[DEBUG] Fastcgi_record.read: Successfully read %d bytes\n%!" (String.length c);
134134+ c
135135+ )
124136 in
125137126138 (* Skip padding *)
127127- if padding_length > 0 then
128128- ignore (Eio.Buf_read.take padding_length buf_read);
139139+ if padding_length > 0 then (
140140+ Printf.eprintf "[DEBUG] Fastcgi_record.read: Skipping %d bytes of padding\n%!" padding_length;
141141+ ignore (Eio.Buf_read.take padding_length buf_read)
142142+ );
129143130130- { version; record_type; request_id; content }
144144+ let record = { version; record_type; request_id; content } in
145145+ Printf.eprintf "[DEBUG] Fastcgi_record.read: Complete record = %s\n%!"
146146+ (Format.asprintf "%a" (pp ~max_content_len:50) record);
147147+ record
131148132149let write buf_write record =
133150 let content_length = String.length record.content in
···165182 let add key value kvs = (key, value) :: kvs
166183167184 let remove key kvs = List.filter (fun (k, _) -> k <> key) kvs
185185+186186+ let cardinal kvs = List.length kvs
168187169188 let find key kvs =
170189 try List.assoc key kvs
+3
lib/fastcgi_record.mli
···103103104104 (** [of_seq pairs] creates from a sequence of (key, value) tuples *)
105105 val of_seq : (string * string) Seq.t -> t
106106+107107+ (** [cardinal pairs] returns the number of key-value pairs *)
108108+ val cardinal : t -> int
106109107110 (** [read buf_read] reads key-value pairs from a buffer.
108111 Handles the FastCGI variable-length encoding where lengths ≤ 127 bytes
+67-19
lib/fastcgi_request.ml
···7979 String.length record.content = 0
808081818282-let read_params_from_flow ~sw:_ flow =
8383- let buf_read = Eio.Buf_read.of_flow flow ~max_size:1000000 in
8282+let read_params_from_flow ~sw:_ buf_read =
8383+ Printf.eprintf "[DEBUG] read_params_from_flow: Starting\n%!";
8484 let params = ref KV.empty in
8585 let rec loop () =
8686 try
8787+ Printf.eprintf "[DEBUG] read_params_from_flow: Reading next PARAMS record\n%!";
8788 let record = Fastcgi_record.read buf_read in
8989+ Printf.eprintf "[DEBUG] read_params_from_flow: Got record type=%s, content_length=%d\n%!"
9090+ (Format.asprintf "%a" pp_record record.record_type)
9191+ (String.length record.content);
8892 if record.record_type <> Params then
8993 Error (Printf.sprintf "Expected PARAMS record, got %s"
9094 (Format.asprintf "%a" pp_record record.record_type))
9191- else if is_stream_terminator record then
9595+ else if is_stream_terminator record then (
9696+ Printf.eprintf "[DEBUG] read_params_from_flow: Got stream terminator, returning %d params\n%!"
9797+ (Fastcgi_record.KV.cardinal !params);
9298 Ok !params
9393- else (
9999+ ) else (
94100 let record_params = KV.decode record.content in
101101+ Printf.eprintf "[DEBUG] read_params_from_flow: Decoded %d params from record\n%!"
102102+ (Fastcgi_record.KV.cardinal record_params);
95103 params := KV.to_seq record_params
96104 |> Seq.fold_left (fun acc (k, v) -> KV.add k v acc) !params;
97105 loop ()
98106 )
99107 with
100100- | End_of_file -> Error "Unexpected end of stream while reading PARAMS"
101101- | exn -> Error (Printf.sprintf "Error reading PARAMS: %s" (Printexc.to_string exn))
108108+ | End_of_file ->
109109+ Printf.eprintf "[DEBUG] read_params_from_flow: Hit End_of_file\n%!";
110110+ Error "Unexpected end of stream while reading PARAMS"
111111+ | exn ->
112112+ Printf.eprintf "[DEBUG] read_params_from_flow: Exception: %s\n%!" (Printexc.to_string exn);
113113+ Error (Printf.sprintf "Error reading PARAMS: %s" (Printexc.to_string exn))
102114 in
103115 loop ()
104116105105-let read_stdin_from_flow ~sw:_ flow =
106106- let buf_read = Eio.Buf_read.of_flow flow ~max_size:1000000 in
117117+let read_stdin_from_flow ~sw:_ buf_read =
118118+ Printf.eprintf "[DEBUG] read_stdin_from_flow: Starting\n%!";
107119 let data = Buffer.create 1024 in
108120 let rec loop () =
109121 try
122122+ Printf.eprintf "[DEBUG] read_stdin_from_flow: Reading next STDIN record\n%!";
110123 let record = Fastcgi_record.read buf_read in
124124+ Printf.eprintf "[DEBUG] read_stdin_from_flow: Got record type=%s, content_length=%d\n%!"
125125+ (Format.asprintf "%a" pp_record record.record_type)
126126+ (String.length record.content);
111127 if record.record_type <> Stdin then
112128 Error (Printf.sprintf "Expected STDIN record, got %s"
113129 (Format.asprintf "%a" pp_record record.record_type))
114114- else if is_stream_terminator record then
130130+ else if is_stream_terminator record then (
131131+ Printf.eprintf "[DEBUG] read_stdin_from_flow: Got stream terminator, total stdin=%d bytes\n%!"
132132+ (Buffer.length data);
115133 Ok (Buffer.contents data)
116116- else (
134134+ ) else (
117135 Buffer.add_string data record.content;
136136+ Printf.eprintf "[DEBUG] read_stdin_from_flow: Added %d bytes, total now %d\n%!"
137137+ (String.length record.content) (Buffer.length data);
118138 loop ()
119139 )
120140 with
121121- | End_of_file -> Error "Unexpected end of stream while reading STDIN"
122122- | exn -> Error (Printf.sprintf "Error reading STDIN: %s" (Printexc.to_string exn))
141141+ | End_of_file ->
142142+ Printf.eprintf "[DEBUG] read_stdin_from_flow: Hit End_of_file\n%!";
143143+ Error "Unexpected end of stream while reading STDIN"
144144+ | exn ->
145145+ Printf.eprintf "[DEBUG] read_stdin_from_flow: Exception: %s\n%!" (Printexc.to_string exn);
146146+ Error (Printf.sprintf "Error reading STDIN: %s" (Printexc.to_string exn))
123147 in
124148 loop ()
125149···144168 read_data ()
145169146170(** Read request streams based on role *)
147147-let read_request_streams ~sw request flow buf_read =
171171+let read_request_streams ~sw request buf_read =
172172+ Printf.eprintf "[DEBUG] read_request_streams: Processing role=%s\n%!"
173173+ (Format.asprintf "%a" pp_role request.role);
148174 match request.role with
149175 | Authorizer ->
176176+ Printf.eprintf "[DEBUG] read_request_streams: Authorizer role, no streams to read\n%!";
150177 Ok request
151178 | Responder ->
152152- let* stdin_data = read_stdin_from_flow ~sw flow in
179179+ Printf.eprintf "[DEBUG] read_request_streams: Responder role, reading STDIN\n%!";
180180+ let* stdin_data = read_stdin_from_flow ~sw buf_read in
181181+ Printf.eprintf "[DEBUG] read_request_streams: Got STDIN data, %d bytes\n%!" (String.length stdin_data);
153182 Ok { request with stdin_data }
154183 | Filter ->
155155- let* stdin_data = read_stdin_from_flow ~sw flow in
184184+ Printf.eprintf "[DEBUG] read_request_streams: Filter role, reading STDIN and DATA\n%!";
185185+ let* stdin_data = read_stdin_from_flow ~sw buf_read in
186186+ Printf.eprintf "[DEBUG] read_request_streams: Got STDIN data, %d bytes\n%!" (String.length stdin_data);
156187 let request = { request with stdin_data } in
157188 let* data = read_data_from_flow buf_read in
189189+ Printf.eprintf "[DEBUG] read_request_streams: Got DATA stream, %d bytes\n%!" (String.length data);
158190 Ok { request with data_stream = Some data }
159191160192let read_request_from_flow ~sw flow =
193193+ Printf.eprintf "[DEBUG] read_request_from_flow: Starting\n%!";
161194 let buf_read = Eio.Buf_read.of_flow flow ~max_size:1000000 in
162195 try
163196 (* Read BEGIN_REQUEST *)
197197+ Printf.eprintf "[DEBUG] read_request_from_flow: Reading BEGIN_REQUEST record\n%!";
164198 let begin_record = Fastcgi_record.read buf_read in
199199+ Printf.eprintf "[DEBUG] read_request_from_flow: Got BEGIN_REQUEST record: %s\n%!"
200200+ (Format.asprintf "%a" (Fastcgi_record.pp ~max_content_len:50) begin_record);
165201 let* request = create begin_record in
202202+ Printf.eprintf "[DEBUG] read_request_from_flow: Created request with role=%s, id=%d\n%!"
203203+ (Format.asprintf "%a" pp_role request.role) request.request_id;
166204 (* Read PARAMS stream *)
167167- let* params = read_params_from_flow ~sw flow in
205205+ Printf.eprintf "[DEBUG] read_request_from_flow: Reading PARAMS stream\n%!";
206206+ let* params = read_params_from_flow ~sw buf_read in
207207+ Printf.eprintf "[DEBUG] read_request_from_flow: Got %d params\n%!" (Fastcgi_record.KV.cardinal params);
168208 let request = { request with params } in
169209 (* Read remaining streams based on role *)
170170- read_request_streams ~sw request flow buf_read
210210+ Printf.eprintf "[DEBUG] read_request_from_flow: Reading streams for role=%s\n%!"
211211+ (Format.asprintf "%a" pp_role request.role);
212212+ let result = read_request_streams ~sw request buf_read in
213213+ Printf.eprintf "[DEBUG] read_request_from_flow: Finished reading request\n%!";
214214+ result
171215 with
172172- | End_of_file -> Error "Unexpected end of stream"
173173- | exn -> Error (Printf.sprintf "Error reading request: %s" (Printexc.to_string exn))
216216+ | End_of_file ->
217217+ Printf.eprintf "[DEBUG] read_request_from_flow: Hit End_of_file\n%!";
218218+ Error "Unexpected end of stream"
219219+ | exn ->
220220+ Printf.eprintf "[DEBUG] read_request_from_flow: Exception: %s\n%!" (Printexc.to_string exn);
221221+ Error (Printf.sprintf "Error reading request: %s" (Printexc.to_string exn))
174222175223(** {1 Response Generation} *)
176224
+4-4
lib/fastcgi_request.mli
···4343 Returns the populated request context. *)
4444val read_request_from_flow : sw:Eio.Switch.t -> 'a Eio.Flow.source -> (t, string) result
45454646-(** [read_params_from_flow ~sw flow] reads PARAMS stream from flow until empty record.
4646+(** [read_params_from_flow ~sw buf_read] reads PARAMS stream from buf_read until empty record.
4747 Returns the accumulated parameters. *)
4848-val read_params_from_flow : sw:Eio.Switch.t -> 'a Eio.Flow.source -> (Fastcgi_record.KV.t, string) result
4848+val read_params_from_flow : sw:Eio.Switch.t -> Eio.Buf_read.t -> (Fastcgi_record.KV.t, string) result
49495050-(** [read_stdin_from_flow ~sw flow] reads STDIN stream from flow until empty record.
5050+(** [read_stdin_from_flow ~sw buf_read] reads STDIN stream from buf_read until empty record.
5151 Returns the accumulated data. *)
5252-val read_stdin_from_flow : sw:Eio.Switch.t -> 'a Eio.Flow.source -> (string, string) result
5252+val read_stdin_from_flow : sw:Eio.Switch.t -> Eio.Buf_read.t -> (string, string) result
53535454(** {1 Response Generation} *)
5555