| 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. |
| 4 | module 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 | |
| 11 | fn C.vschannel_h2_connect(tls_ctx &C.TlsContext, iport int, host &u16) int |
| 12 | fn C.vschannel_write(tls_ctx &C.TlsContext, buf &char, len int) int |
| 13 | fn C.vschannel_read(tls_ctx &C.TlsContext, buf &char, cap int) int |
| 14 | fn 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. |
| 20 | struct VSchannelH2Transport { |
| 21 | mut: |
| 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. |
| 28 | fn (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. |
| 40 | fn (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. |
| 57 | fn 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 | |