v2 / vlib / veb / tests / chunked_multipart_upload_test.v
123 lines · 109 sloc · 3.33 KB · 344b9afcfe67902dd9660bd3b077f18464d1d114
Raw
1// vtest retry: 3
2// vtest build: !windows // fasthttp.Server.run is not implemented on windows yet
3import crypto.sha256
4import io
5import net
6import time
7import veb
8
9const chunked_multipart_port = 13005
10
11const chunked_multipart_boundary = '----VChunkedMultipartBoundary'
12const chunked_multipart_chunk_size = 4096
13
14const chunked_multipart_localserver = '127.0.0.1:${chunked_multipart_port}'
15
16pub struct ChunkedMultipartContext {
17 veb.Context
18}
19
20pub struct ChunkedMultipartApp {
21pub mut:
22 started chan bool
23}
24
25pub fn (mut app ChunkedMultipartApp) before_accept_loop() {
26 app.started <- true
27}
28
29@['/upload'; post]
30pub fn (mut app ChunkedMultipartApp) upload(mut ctx ChunkedMultipartContext) veb.Result {
31 if 'file' !in ctx.files || ctx.files['file'].len == 0 {
32 ctx.res.set_status(.bad_request)
33 return ctx.text('missing file')
34 }
35 file := ctx.files['file'][0]
36 return ctx.text(sha256.sum256(file.data.bytes()).hex())
37}
38
39fn testsuite_begin() {
40 mut app := &ChunkedMultipartApp{}
41 spawn veb.run_at[ChunkedMultipartApp, ChunkedMultipartContext](mut app,
42 port: chunked_multipart_port
43 family: .ip
44 timeout_in_seconds: 10
45 )
46 _ := <-app.started
47}
48
49fn test_large_chunked_multipart_form_upload() {
50 payload := make_chunked_multipart_payload(2 * 1024 * 1024)
51 expected_hash := sha256.sum256(payload).hex()
52 response := send_chunked_multipart_request(payload) or {
53 assert false, 'failed to send chunked multipart request: ${err}'
54 return
55 }
56 assert response.starts_with('HTTP/1.1 200 OK')
57 assert response.all_after('\r\n\r\n') == expected_hash
58}
59
60fn make_chunked_multipart_payload(size int) []u8 {
61 mut payload := []u8{len: size}
62 for i in 0 .. size {
63 payload[i] = u8(i % 256)
64 }
65 return payload
66}
67
68fn send_chunked_multipart_request(payload []u8) !string {
69 mut client := net.dial_tcp(chunked_multipart_localserver)!
70 defer {
71 client.close() or {}
72 }
73 client.set_read_timeout(10 * time.second)
74 client.set_write_timeout(10 * time.second)
75 headers := 'POST /upload HTTP/1.1\r\nHost: ${chunked_multipart_localserver}\r\nTransfer-Encoding: chunked\r\nContent-Type: multipart/form-data; boundary=${chunked_multipart_boundary}\r\nConnection: close\r\n\r\n'
76 client.write_string(headers)!
77 body := build_multipart_body(payload)
78 write_chunked_body(mut client, body)!
79 return read_response(mut client)
80}
81
82fn build_multipart_body(payload []u8) []u8 {
83 mut body := []u8{}
84 body << '--${chunked_multipart_boundary}\r\n'.bytes()
85 body << 'Content-Disposition: form-data; name="file"; filename="payload.bin"\r\n'.bytes()
86 body << 'Content-Type: application/octet-stream\r\n\r\n'.bytes()
87 body << payload
88 body << '\r\n--${chunked_multipart_boundary}--\r\n'.bytes()
89 return body
90}
91
92fn write_chunked_body(mut client net.TcpConn, body []u8) ! {
93 for start := 0; start < body.len; start += chunked_multipart_chunk_size {
94 end := if start + chunked_multipart_chunk_size < body.len {
95 start + chunked_multipart_chunk_size
96 } else {
97 body.len
98 }
99 chunk := body[start..end]
100 client.write_string('${chunk.len:x}\r\n')!
101 client.write(chunk)!
102 client.write_string('\r\n')!
103 }
104 client.write_string('0\r\n\r\n')!
105}
106
107fn read_response(mut client net.TcpConn) !string {
108 mut response := []u8{}
109 mut buf := []u8{len: 4096}
110 for {
111 n := client.read(mut buf) or {
112 if err is io.Eof {
113 break
114 }
115 return err
116 }
117 if n == 0 {
118 break
119 }
120 response << buf[..n]
121 }
122 return response.bytestr()
123}
124