v / vlib / net / http / h2_client.v
92 lines · 87 sloc · 3.11 KB · 94a763e85cd34a51ee9c9609c445d6f9d5490ad1
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module http
5
6// This file converts between net.http's Request/Response and the HTTP/2
7// client types in h2_conn.v. The actual transport wiring (ALPN negotiation on
8// the TLS socket) lives in backend.c.v; these helpers are pure and backend
9// agnostic, so they can be tested without a socket.
10
11// h2_hop_by_hop are header names that must not be forwarded on HTTP/2
12// (RFC 7540 Section 8.1.2.2), plus `host` (replaced by the :authority
13// pseudo-header) and `cookie` (handled specially below).
14const h2_hop_by_hop = ['connection', 'keep-alive', 'proxy-connection', 'transfer-encoding', 'upgrade',
15 'host', 'cookie']
16
17// h2_authority returns the :authority value for a host and port, omitting the
18// port for the default HTTPS port.
19fn h2_authority(host string, port int) string {
20 if port == 443 || port == 0 {
21 return host
22 }
23 return '${host}:${port}'
24}
25
26// to_h2_request builds an HTTP/2 request from this request. Header names are
27// lowercased, hop-by-hop headers are dropped, the Host header becomes the
28// :authority pseudo-header, and cookies are collapsed into a single field.
29fn (req &Request) to_h2_request(method Method, authority string, path string, data string, header Header) H2ClientRequest {
30 // An explicit Host header overrides the URL host, matching the HTTP/1.1
31 // path (used for virtual-host / host-override requests).
32 mut auth := authority
33 if host := header.get(.host) {
34 if host != '' {
35 auth = host
36 }
37 }
38 mut extra := []H2HeaderField{}
39 if !header.contains(.user_agent) && req.user_agent != '' {
40 extra << H2HeaderField{'user-agent', req.user_agent}
41 }
42 if data.len > 0 && !header.contains(.content_length) {
43 extra << H2HeaderField{'content-length', data.len.str()}
44 }
45 for key in header.keys() {
46 lkey := key.to_lower()
47 if lkey in h2_hop_by_hop {
48 continue
49 }
50 for val in header.custom_values(key) {
51 extra << H2HeaderField{lkey, val}
52 }
53 }
54 // Cookies: the request's own cookie map plus any Cookie header values,
55 // joined into one field (RFC 7540 Section 8.1.2.5 also allows splitting).
56 mut cookie_parts := []string{}
57 for k, v in req.cookies {
58 cookie_parts << '${k}=${v}'
59 }
60 for cv in header.values(.cookie) {
61 cookie_parts << cv
62 }
63 if cookie_parts.len > 0 {
64 extra << H2HeaderField{'cookie', cookie_parts.join('; ')}
65 }
66 return H2ClientRequest{
67 method: method.str()
68 scheme: 'https'
69 authority: auth
70 path: path
71 headers: extra
72 body: data.bytes()
73 }
74}
75
76// h2_response_to_http converts an HTTP/2 response into a net.http Response,
77// decoding any Content-Encoding the same way the HTTP/1.1 path does.
78fn h2_response_to_http(h2resp H2ClientResponse) Response {
79 mut h := new_header()
80 for f in h2resp.headers {
81 h.add_custom(f.name, f.value) or {}
82 }
83 body := decode_response_body(h2resp.body.bytestr(), h.get(.content_encoding) or { '' })
84 status := status_from_int(h2resp.status)
85 return Response{
86 http_version: '2.0'
87 status_code: h2resp.status
88 status_msg: status.str()
89 header: h
90 body: body
91 }
92}
93