FastCGI implementation in OCaml
1 2type version = int 3 4type record = 5 | Begin_request 6 | Abort_request 7 | End_request 8 | Params 9 | Stdin 10 | Stdout 11 | Stderr 12 | Data 13 | Get_values 14 | Get_values_result 15 | Unknown_type 16 17let record_to_int = function 18 | Begin_request -> 1 19 | Abort_request -> 2 20 | End_request -> 3 21 | Params -> 4 22 | Stdin -> 5 23 | Stdout -> 6 24 | Stderr -> 7 25 | Data -> 8 26 | Get_values -> 9 27 | Get_values_result -> 10 28 | Unknown_type -> 11 29 30let record_of_int = function 31 | 1 -> Begin_request 32 | 2 -> Abort_request 33 | 3 -> End_request 34 | 4 -> Params 35 | 5 -> Stdin 36 | 6 -> Stdout 37 | 7 -> Stderr 38 | 8 -> Data 39 | 9 -> Get_values 40 | 10 -> Get_values_result 41 | 11 -> Unknown_type 42 | n -> invalid_arg (Printf.sprintf "Unknown FastCGI record type: %d" n) 43 44let pp_record ppf = function 45 | Begin_request -> Format.pp_print_string ppf "Begin_request" 46 | Abort_request -> Format.pp_print_string ppf "Abort_request" 47 | End_request -> Format.pp_print_string ppf "End_request" 48 | Params -> Format.pp_print_string ppf "Params" 49 | Stdin -> Format.pp_print_string ppf "Stdin" 50 | Stdout -> Format.pp_print_string ppf "Stdout" 51 | Stderr -> Format.pp_print_string ppf "Stderr" 52 | Data -> Format.pp_print_string ppf "Data" 53 | Get_values -> Format.pp_print_string ppf "Get_values" 54 | Get_values_result -> Format.pp_print_string ppf "Get_values_result" 55 | Unknown_type -> Format.pp_print_string ppf "Unknown_type" 56 57type request_id = int 58 59type t = { 60 version : version; 61 record_type : record; 62 request_id : request_id; 63 content : string; 64} 65 66let pp ?(max_content_len=100) ppf record = 67 let truncated_content = 68 let content = record.content in 69 let len = String.length content in 70 if len <= max_content_len then content 71 else String.sub content 0 max_content_len ^ "..." ^ Printf.sprintf " (%d more bytes)" (len - max_content_len) 72 in 73 Format.fprintf ppf 74 "@[<2>{ version = %d;@ record_type = %a;@ request_id = %d;@ content = %S }@]" 75 record.version 76 pp_record record.record_type 77 record.request_id 78 truncated_content 79 80(* FastCGI constants *) 81let fcgi_version_1 = 1 82let fcgi_header_len = 8 83 84let read buf_read = 85 (* Read the 8-byte header *) 86 let header = Eio.Buf_read.take fcgi_header_len buf_read in 87 88 (* Parse header fields *) 89 let version = Char.code header.[0] in 90 let record_type_int = Char.code header.[1] in 91 let request_id = (Char.code header.[2] lsl 8) lor (Char.code header.[3]) in 92 let content_length = (Char.code header.[4] lsl 8) lor (Char.code header.[5]) in 93 let padding_length = Char.code header.[6] in 94 let _reserved = Char.code header.[7] in 95 96 (* Validate version *) 97 if version <> fcgi_version_1 then 98 invalid_arg (Printf.sprintf "Unsupported FastCGI version: %d" version); 99 100 (* Convert record type *) 101 let record_type = record_of_int record_type_int in 102 103 (* Read content *) 104 let content = 105 if content_length = 0 then 106 "" 107 else 108 Eio.Buf_read.take content_length buf_read 109 in 110 111 (* Skip padding *) 112 if padding_length > 0 then 113 ignore (Eio.Buf_read.take padding_length buf_read); 114 115 { version; record_type; request_id; content } 116 117let write buf_write record = 118 let content_length = String.length record.content in 119 120 (* Calculate padding for 8-byte alignment *) 121 let padding_length = (8 - (content_length land 7)) land 7 in 122 123 (* Create and write header *) 124 let header = Bytes.create fcgi_header_len in 125 Bytes.set_uint8 header 0 record.version; 126 Bytes.set_uint8 header 1 (record_to_int record.record_type); 127 Bytes.set_uint16_be header 2 record.request_id; 128 Bytes.set_uint16_be header 4 content_length; 129 Bytes.set_uint8 header 6 padding_length; 130 Bytes.set_uint8 header 7 0; (* reserved *) 131 132 Eio.Buf_write.string buf_write (Bytes.to_string header); 133 134 (* Write content *) 135 if content_length > 0 then 136 Eio.Buf_write.string buf_write record.content; 137 138 (* Write padding *) 139 if padding_length > 0 then 140 Eio.Buf_write.string buf_write (String.make padding_length '\000') 141 142let create ~version ~record ~request_id ~content = 143 { version; record_type = record; request_id; content } 144 145module KV = struct 146 type t = (string * string) list 147 148 let empty = [] 149 150 let add key value kvs = (key, value) :: kvs 151 152 let remove key kvs = List.filter (fun (k, _) -> k <> key) kvs 153 154 let find key kvs = 155 try List.assoc key kvs 156 with Not_found -> raise Not_found 157 158 let find_opt key kvs = 159 try Some (List.assoc key kvs) 160 with Not_found -> None 161 162 let to_seq kvs = List.to_seq kvs 163 164 let of_seq seq = List.of_seq seq 165 166 let encode_length len = 167 if len <= 127 then 168 String.make 1 (Char.chr len) 169 else 170 let b = Bytes.create 4 in 171 Bytes.set_int32_be b 0 (Int32.logor (Int32.of_int len) 0x80000000l); 172 Bytes.to_string b 173 174 let decode_length buf pos = 175 if pos >= String.length buf then 176 failwith "Unexpected end of buffer while reading length"; 177 let first_byte = Char.code buf.[pos] in 178 if first_byte land 0x80 = 0 then 179 (* Single byte length *) 180 (first_byte, pos + 1) 181 else ( 182 (* Four byte length *) 183 if pos + 4 > String.length buf then 184 failwith "Unexpected end of buffer while reading 4-byte length"; 185 let len = 186 ((first_byte land 0x7f) lsl 24) lor 187 ((Char.code buf.[pos + 1]) lsl 16) lor 188 ((Char.code buf.[pos + 2]) lsl 8) lor 189 (Char.code buf.[pos + 3]) 190 in 191 (len, pos + 4) 192 ) 193 194 let encode kvs = 195 let buf = Buffer.create 256 in 196 List.iter (fun (key, value) -> 197 let key_len = String.length key in 198 let value_len = String.length value in 199 Buffer.add_string buf (encode_length key_len); 200 Buffer.add_string buf (encode_length value_len); 201 Buffer.add_string buf key; 202 Buffer.add_string buf value 203 ) kvs; 204 Buffer.contents buf 205 206 let decode content = 207 let len = String.length content in 208 let rec loop pos acc = 209 if pos >= len then 210 List.rev acc 211 else 212 (* Read name length *) 213 let name_len, pos = decode_length content pos in 214 (* Read value length *) 215 let value_len, pos = decode_length content pos in 216 217 (* Check bounds *) 218 if pos + name_len + value_len > len then 219 failwith "Unexpected end of buffer while reading key-value data"; 220 221 (* Extract name and value *) 222 let name = String.sub content pos name_len in 223 let value = String.sub content (pos + name_len) value_len in 224 225 loop (pos + name_len + value_len) ((name, value) :: acc) 226 in 227 loop 0 [] 228 229 let read buf_read = 230 (* For reading from a stream, we need to determine how much data to read. 231 Since we're in a record context, we should read all available data 232 until we hit the end of the record content. *) 233 let content = Eio.Buf_read.take_all buf_read in 234 decode content 235 236 let write buf_write kvs = 237 let encoded = encode kvs in 238 Eio.Buf_write.string buf_write encoded 239 240 let pp ppf kvs = 241 Format.fprintf ppf "@[<2>[@["; 242 let first = ref true in 243 List.iter (fun (key, value) -> 244 if not !first then Format.fprintf ppf ";@ "; 245 first := false; 246 Format.fprintf ppf "(%S, %S)" key value 247 ) kvs; 248 Format.fprintf ppf "@]]@]" 249end