vxx2 / vlib / net / http / h2_client_test.v
123 lines · 112 sloc · 4.33 KB · 89607c731290ee2c148c11541f73c40b23b00481
Raw
1module http
2
3// Tests for the HTTP/2 <-> net.http conversion glue (h2_client.v). The
4// request/response conversions are pure and need no socket; the end-to-end
5// fetch test is opt-in and network-dependent.
6
7fn test_to_h2_request_pseudo_headers_and_body() {
8 req := Request{
9 user_agent: 'v.http'
10 }
11 h2req := req.to_h2_request(.post, 'example.com', '/p?q=1', 'hello', new_header())
12 assert h2req.method == 'POST'
13 assert h2req.scheme == 'https'
14 assert h2req.authority == 'example.com'
15 assert h2req.path == '/p?q=1'
16 assert h2req.body.bytestr() == 'hello'
17 // user-agent (from the request) and a synthesized content-length.
18 assert h2req.headers.any(it.name == 'user-agent' && it.value == 'v.http')
19 assert h2req.headers.any(it.name == 'content-length' && it.value == '5')
20}
21
22fn test_to_h2_request_lowercases_and_keeps_custom_headers() {
23 mut h := new_header()
24 h.add_custom('Accept', 'application/json') or {}
25 h.add(.content_type, 'text/plain')
26 req := Request{}
27 h2req := req.to_h2_request(.get, 'h.example', '/', '', h)
28 assert h2req.headers.any(it.name == 'accept' && it.value == 'application/json')
29 assert h2req.headers.any(it.name == 'content-type' && it.value == 'text/plain')
30}
31
32fn test_to_h2_request_strips_hop_by_hop_and_host() {
33 mut h := new_header()
34 h.add(.connection, 'keep-alive')
35 h.add(.host, 'example.com')
36 h.add_custom('Transfer-Encoding', 'chunked') or {}
37 req := Request{}
38 h2req := req.to_h2_request(.get, 'example.com', '/', '', h)
39 assert !h2req.headers.any(it.name == 'connection')
40 assert !h2req.headers.any(it.name == 'host')
41 assert !h2req.headers.any(it.name == 'transfer-encoding')
42}
43
44fn test_to_h2_request_te_only_trailers() {
45 // RFC 9113 §8.2.2: TE may be sent on an HTTP/2 request but only with the
46 // value 'trailers'; any other value must be dropped.
47 req := Request{}
48 mut h := new_header()
49 h.add_custom('TE', 'gzip') or {}
50 h2req := req.to_h2_request(.get, 'h.example', '/', '', h)
51 assert !h2req.headers.any(it.name == 'te'), 'a non-trailers TE must be dropped'
52
53 mut h2 := new_header()
54 h2.add_custom('TE', 'trailers') or {}
55 h2req2 := req.to_h2_request(.get, 'h.example', '/', '', h2)
56 te := h2req2.headers.filter(it.name == 'te')
57 assert te.len == 1 && te[0].value == 'trailers', 'te: trailers must be kept'
58}
59
60fn test_to_h2_request_collapses_cookies() {
61 mut h := new_header()
62 h.add(.cookie, 'a=1')
63 req := Request{
64 cookies: {
65 'sid': 'abc'
66 }
67 }
68 h2req := req.to_h2_request(.get, 'h.example', '/', '', h)
69 cookie := h2req.headers.filter(it.name == 'cookie')
70 assert cookie.len == 1
71 // Both the request cookie map and the Cookie header value are present.
72 assert cookie[0].value.contains('sid=abc')
73 assert cookie[0].value.contains('a=1')
74}
75
76fn test_h2_response_to_http() {
77 h2resp := H2ClientResponse{
78 status: 200
79 headers: [H2HeaderField{'content-type', 'text/plain'},
80 H2HeaderField{'x-foo', 'bar'}]
81 body: 'hi'.bytes()
82 }
83 resp := h2_response_to_http(h2resp)
84 assert resp.status_code == 200
85 assert resp.http_version == '2.0'
86 assert resp.version() == .v2_0
87 assert resp.body == 'hi'
88 assert (resp.header.get_custom('content-type') or { '' }) == 'text/plain'
89 assert (resp.header.get_custom('x-foo') or { '' }) == 'bar'
90}
91
92fn test_h2_authority_omits_default_port() {
93 assert h2_authority('example.com', 443) == 'example.com'
94 assert h2_authority('example.com', 0) == 'example.com'
95 assert h2_authority('example.com', 8443) == 'example.com:8443'
96}
97
98// End-to-end test against a real HTTP/2 server. Run with `-d network`,
99// e.g. `v -d network test vlib/net/http/h2_client_test.v`.
100fn test_http2_fetch_real_server() {
101 $if !network ? {
102 return
103 }
104 // HTTP/2 is negotiated by default for https requests. On Windows this runs
105 // over the SChannel backend's ALPN + HTTP/2 path (vlang/v#27383).
106 resp := get('https://www.google.com/')!
107 assert resp.version() == .v2_0
108 assert resp.status_code == 200
109 assert resp.body.len > 0
110 // Opting out forces HTTP/1.1 against the same server.
111 plain := fetch(url: 'https://www.google.com/', enable_http2: false)!
112 assert plain.version() == .v1_1
113}
114
115fn test_to_h2_request_authority_from_host_header() {
116 mut h := new_header()
117 h.add(.host, 'override.example:8443')
118 req := Request{}
119 // The URL host is origin.example, but an explicit Host header must win.
120 h2req := req.to_h2_request(.get, 'origin.example', '/', '', h)
121 assert h2req.authority == 'override.example:8443'
122 assert !h2req.headers.any(it.name == 'host')
123}
124