FastCGI implementation in OCaml
at main 5.0 kB view raw
1open Fastcgi 2 3let hex_dump ?(max_bytes=256) content = 4 let len = String.length content in 5 let display_len = min len max_bytes in 6 let bytes_per_line = 16 in 7 let lines_to_show = (display_len - 1) / bytes_per_line + 1 in 8 9 for i = 0 to lines_to_show - 1 do 10 let start = i * bytes_per_line in 11 let end_ = min (start + bytes_per_line) display_len in 12 Printf.printf " %04x: " start; 13 14 (* Print hex bytes *) 15 for j = start to end_ - 1 do 16 Printf.printf "%02x " (Char.code content.[j]); 17 if j = start + 7 then Printf.printf " " 18 done; 19 20 (* Pad remaining space *) 21 for _ = end_ to start + bytes_per_line - 1 do 22 Printf.printf " "; 23 if end_ <= start + 7 then Printf.printf " " 24 done; 25 26 Printf.printf " |"; 27 28 (* Print ASCII representation *) 29 for j = start to end_ - 1 do 30 let c = content.[j] in 31 if c >= ' ' && c <= '~' then 32 Printf.printf "%c" c 33 else 34 Printf.printf "." 35 done; 36 37 Printf.printf "|\n%!" 38 done; 39 40 if len > max_bytes then 41 Printf.printf " ... (%d more bytes truncated)\n%!" (len - max_bytes) 42 43 44let test_record_types () = 45 Printf.printf "Testing record type conversions...\n%!"; 46 47 let test_type rt expected_int = 48 assert (Record.record_to_int rt = expected_int); 49 assert (Record.record_of_int expected_int = rt) 50 in 51 52 test_type Begin_request 1; 53 test_type Abort_request 2; 54 test_type End_request 3; 55 test_type Params 4; 56 test_type Stdin 5; 57 test_type Stdout 6; 58 test_type Stderr 7; 59 test_type Data 8; 60 test_type Get_values 9; 61 test_type Get_values_result 10; 62 test_type Unknown_type 11; 63 64 (* Test invalid record type *) 65 (try 66 let _ = Record.record_of_int 99 in 67 assert false (* Should not reach here *) 68 with Invalid_argument _ -> ()); 69 70 Printf.printf "✓ Record type conversion test passed\n%!" 71 72let test_kv_encoding () = 73 Printf.printf "Testing key-value encoding...\n%!"; 74 75 let kv = Record.KV.empty 76 |> Record.KV.add "REQUEST_METHOD" "GET" 77 |> Record.KV.add "SERVER_NAME" "localhost" 78 |> Record.KV.add "SERVER_PORT" "80" 79 in 80 81 let encoded = Record.KV.encode kv in 82 let decoded = Record.KV.decode encoded in 83 84 (* Check that all original pairs are present *) 85 assert (Record.KV.find "REQUEST_METHOD" decoded = "GET"); 86 assert (Record.KV.find "SERVER_NAME" decoded = "localhost"); 87 assert (Record.KV.find "SERVER_PORT" decoded = "80"); 88 89 Printf.printf "✓ Key-value encoding test passed\n%!" 90 91let test_long_key_value () = 92 Printf.printf "Testing long key-value encoding...\n%!"; 93 94 (* Create a long key and value to test 4-byte length encoding *) 95 let long_key = String.make 200 'k' in 96 let long_value = String.make 300 'v' in 97 98 let kv = Record.KV.empty 99 |> Record.KV.add long_key long_value 100 |> Record.KV.add "short" "val" 101 in 102 103 let encoded = Record.KV.encode kv in 104 let decoded = Record.KV.decode encoded in 105 106 assert (Record.KV.find long_key decoded = long_value); 107 assert (Record.KV.find "short" decoded = "val"); 108 109 Printf.printf "✓ Long key-value encoding test passed\n%!" 110 111let test_with_binary_test_case ~fs filename expected_type expected_request_id = 112 Printf.printf "Testing with binary test case: %s...\n%!" filename; 113 114 let (raw_content, parsed) = 115 Eio.Path.with_open_in (fs, "test_cases/" ^ filename) @@ fun flow -> 116 let buf = Buffer.create 1024 in 117 Eio.Flow.copy flow (Eio.Flow.buffer_sink buf); 118 let raw_content = Buffer.contents buf in 119 let buf_read = Eio.Buf_read.of_string raw_content in 120 let parsed = Record.read buf_read in 121 (raw_content, parsed) 122 in 123 124 Printf.printf "\nRaw file contents (%d bytes):\n" (String.length raw_content); 125 hex_dump raw_content; 126 127 Printf.printf "\nParsed record:\n"; 128 Format.printf "%a\n" (fun ppf -> Record.pp ppf) parsed; 129 130 (* If this is a Params record, also show the decoded key-value pairs *) 131 if parsed.record_type = Params && String.length parsed.content > 0 then ( 132 let params = Record.KV.decode parsed.content in 133 Printf.printf "\nDecoded parameters:\n"; 134 Format.printf "%a\n" Record.KV.pp params 135 ); 136 137 assert (parsed.version = 1); 138 assert (parsed.record_type = expected_type); 139 assert (parsed.request_id = expected_request_id); 140 141 Printf.printf "✓ Binary test case %s passed\n%!" filename 142 143let run_tests ~fs = 144 Printf.printf "Running FastCGI Record tests...\n\n%!"; 145 146 test_record_types (); 147 test_kv_encoding (); 148 test_long_key_value (); 149 150 (* Test with some of our binary test cases *) 151 test_with_binary_test_case ~fs "begin_request_responder.bin" Begin_request 1; 152 test_with_binary_test_case ~fs "params_empty.bin" Params 1; 153 test_with_binary_test_case ~fs "end_request_success.bin" End_request 1; 154 test_with_binary_test_case ~fs "get_values.bin" Get_values 0; 155 test_with_binary_test_case ~fs "abort_request.bin" Abort_request 1; 156 157 Printf.printf "\n✅ All FastCGI Record tests passed!\n%!" 158 159let () = Eio_main.run @@ fun env -> 160 let fs = Eio.Stdenv.cwd env in 161 run_tests ~fs:(fst fs)