v / cmd / tools / vschannel_16kb_httpbin_probe.v
167 lines · 156 sloc · 4.06 KB · e2e5cf8db56f3562c7baa735061690be936bdf3e
Raw
1import flag
2import net.http
3import os
4import time
5
6const probe_url = 'https://httpbin.org/post'
7const vschannel_16kb_boundary = 16 * 1024
8const default_sizes_csv = '12000,15000,17000,24000,32768'
9
10fn main() {
11 if os.args.len >= 2 && os.args[1] == '--worker' {
12 run_worker_mode()
13 return
14 }
15 run_parent_mode()
16}
17
18fn run_worker_mode() {
19 if os.args.len < 3 {
20 eprintln('missing payload size for --worker')
21 exit(2)
22 }
23 size := os.args[2].int()
24 if size <= 0 {
25 eprintln('invalid payload size `${os.args[2]}`')
26 exit(2)
27 }
28 payload := '{"size":${size},"payload":"' + 'x'.repeat(size) + '"}'
29 mut headers := http.new_header()
30 headers.add(.content_type, 'application/json')
31 mut req := http.Request{
32 method: .post
33 url: probe_url
34 header: headers
35 data: payload
36 read_timeout: 60 * time.second
37 write_timeout: 30 * time.second
38 }
39 resp := req.do() or {
40 eprintln('request failed at size=${size}: ${err}')
41 exit(1)
42 }
43 if resp.status_code != 200 {
44 eprintln('unexpected status at size=${size}: ${resp.status_code}')
45 exit(1)
46 }
47 if !resp.body.contains('httpbin.org/post') {
48 eprintln('unexpected response body at size=${size}')
49 exit(1)
50 }
51 println('OK size=${size} payload_bytes=${payload.len}')
52}
53
54fn run_parent_mode() {
55 mut fp := flag.new_flag_parser(os.args)
56 fp.application('vschannel_16kb_httpbin_probe')
57 fp.version('0.0.1')
58 fp.description('Probe net.http HTTPS POST behavior around 16KB payloads using https://httpbin.org/post.')
59 fp.skip_executable()
60
61 expect_mode := fp.string('expect', `e`, 'after',
62 'Expected behavior mode: before|after|none. before expects >16KB failures, after expects all succeed.')
63 sizes_csv := fp.string('sizes', `s`, default_sizes_csv,
64 'Comma separated payload sizes in bytes.')
65 verbose := fp.bool('verbose', `v`, false, 'Print child process output for all sizes.')
66 show_help := fp.bool('help', `h`, false, 'Show this help screen.')
67 free_args := fp.finalize() or {
68 eprintln('flag parse failed: ${err}')
69 exit(2)
70 }
71 if free_args.len > 0 {
72 eprintln('unexpected positional args: ${free_args}')
73 exit(2)
74 }
75 if show_help {
76 println(fp.usage())
77 return
78 }
79
80 mode := expect_mode.to_lower()
81 if mode !in ['before', 'after', 'none'] {
82 eprintln('invalid --expect `${expect_mode}` (allowed: before|after|none)')
83 exit(2)
84 }
85 sizes := parse_sizes_csv(sizes_csv) or {
86 eprintln(err)
87 exit(2)
88 }
89
90 self_exe := os.executable()
91 println('probe url: ${probe_url}')
92 println('expect mode: ${mode}')
93 println('sizes: ${sizes}')
94
95 mut mismatches := 0
96 for size in sizes {
97 // Launch worker directly without going through a shell, and capture stdout/stderr separately.
98 mut p := os.new_process(self_exe)
99 p.set_args(['--worker', size.str()])
100 p.set_redirect_stdio()
101 p.run()
102 p.wait()
103 exit_code := p.code
104 stdout := p.stdout_read().trim_space()
105 stderr := p.stderr_read().trim_space()
106 p.close()
107 success := exit_code == 0
108 expected_success := expected_success_for_mode(mode, size)
109 status := if success { 'OK' } else { 'FAIL' }
110 expect_text := if expected_success { 'expect=OK' } else { 'expect=FAIL' }
111 println('${status:4} size=${size:6} exit=${exit_code:3} ${expect_text}')
112 if verbose || !success {
113 if stdout.len > 0 {
114 println(stdout)
115 }
116 if stderr.len > 0 {
117 eprintln(stderr)
118 }
119 }
120 if mode != 'none' && success != expected_success {
121 mismatches++
122 }
123 }
124
125 if mismatches > 0 {
126 eprintln('mismatch count: ${mismatches}')
127 exit(1)
128 }
129 println('probe completed without mismatches')
130}
131
132fn parse_sizes_csv(raw string) ![]int {
133 mut sizes := []int{}
134 for part in raw.split(',') {
135 item := part.trim_space()
136 if item.len == 0 {
137 continue
138 }
139 size := item.int()
140 if size <= 0 {
141 return error('invalid size entry `${item}` in --sizes')
142 }
143 sizes << size
144 }
145 if sizes.len == 0 {
146 return error('no sizes provided in --sizes')
147 }
148 return sizes
149}
150
151fn expected_success_for_mode(mode string, size int) bool {
152 return match mode {
153 'before' {
154 size <= vschannel_16kb_boundary
155 }
156 'after' {
157 true
158 }
159 'none' {
160 true
161 }
162 else {
163 eprintln('unexpected mode `${mode}` in expected_success_for_mode')
164 false
165 }
166 }
167}
168