v / vlib / net / http / vschannel_h2_windows.c.v
88 lines · 79 sloc · 3.3 KB · 5d739b1b1ac7ec2625d864cbb17d758c6c92f8bc
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// HTTP/2 over the Windows SChannel backend. This wires the streaming transport
7// primitives in thirdparty/vschannel/vschannel.c into the backend-agnostic
8// HTTP/2 client (h2_conn.v / h2_client.v), mirroring what net_ssl_do does with
9// net.ssl on the other platforms. See vlang/v#27383.
10
11fn C.vschannel_h2_connect(tls_ctx &C.TlsContext, iport int, host &u16) int
12fn C.vschannel_write(tls_ctx &C.TlsContext, buf &char, len int) int
13fn C.vschannel_read(tls_ctx &C.TlsContext, buf &char, cap int) int
14fn C.vschannel_h2_close(tls_ctx &C.TlsContext)
15
16// VSchannelH2Transport adapts the SChannel streaming C API (vschannel_read /
17// vschannel_write over an open TLS connection) to the H2Transport interface
18// that H2Conn drives. It borrows a &C.TlsContext owned by the caller, which
19// must outlive the transport.
20struct VSchannelH2Transport {
21mut:
22 ctx &C.TlsContext = unsafe { nil }
23}
24
25// read fills `buf` with up to buf.len decrypted application bytes, returning the
26// number read (0 at end of stream). H2Conn treats a 0/closed read as the
27// connection closing, matching net.ssl semantics.
28fn (mut t VSchannelH2Transport) read(mut buf []u8) !int {
29 if buf.len == 0 {
30 return 0
31 }
32 n := C.vschannel_read(t.ctx, &char(buf.data), buf.len)
33 if n < 0 {
34 return error('http: vschannel_read failed')
35 }
36 return n
37}
38
39// write encrypts and sends all of `buf`, returning the number of bytes consumed.
40fn (mut t VSchannelH2Transport) write(buf []u8) !int {
41 if buf.len == 0 {
42 return 0
43 }
44 n := C.vschannel_write(t.ctx, &char(buf.data), buf.len)
45 if n < 0 {
46 return error('http: vschannel_write failed')
47 }
48 return n
49}
50
51// vschannel_h2_do opens an SChannel TLS connection advertising ALPN
52// `h2`/`http/1.1`. If the server selects `h2`, it runs the request over HTTP/2.
53// Otherwise it speaks HTTP/1.1 over the *same* open connection (so a server
54// that does not do HTTP/2 — or ALPN at all — costs no extra handshake, and
55// single-connection servers are not broken by a probe-then-reconnect). Any
56// error once HTTP/2 is in use propagates as-is.
57fn vschannel_h2_do(req &Request, port int, method Method, host_name string, path string, data string, header Header) !Response {
58 mut ctx := C.new_tls_context()
59 C.vschannel_use_tls12_client_protocol()
60 C.vschannel_init(&ctx, C.BOOL(if req.validate { 1 } else { 0 }))
61 wire := alpn_wire(['h2', 'http/1.1'])
62 C.vschannel_set_alpn(&ctx, &char(wire.data), wire.len)
63
64 if C.vschannel_h2_connect(&ctx, port, host_name.to_wide()) != 0 {
65 err_code := C.vschannel_last_error(&ctx)
66 C.vschannel_cleanup(&ctx)
67 if err_code != 0 {
68 return vschannel_request_error(err_code)
69 }
70 return error('http: vschannel connect failed')
71 }
72
73 mut abuf := []u8{len: 16}
74 an := C.vschannel_get_alpn(&ctx, &char(abuf.data), abuf.len)
75 if an > 0 && abuf[..an].bytestr() == 'h2' {
76 defer {
77 C.vschannel_h2_close(&ctx)
78 }
79 mut transport := &VSchannelH2Transport{
80 ctx: &ctx
81 }
82 mut conn := new_h2_conn(transport)
83 return req.h2_exchange(mut conn, method, host_name, port, path, data, header)!
84 }
85
86 // Server chose HTTP/1.1 (or no ALPN): reuse the open connection for h1.
87 return req.vschannel_h1_on_open(&ctx, method, host_name, port, path, data, header)!
88}
89