| 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 | import net.ssl |
| 7 | import strings |
| 8 | |
| 9 | fn (req &Request) ssl_do(port int, method Method, host_name string, path string, data string, header Header) !Response { |
| 10 | $if windows && !no_vschannel ? { |
| 11 | return vschannel_ssl_do(req, port, method, host_name, path, data, header) |
| 12 | } |
| 13 | return net_ssl_do(req, port, method, host_name, path, data, header) |
| 14 | } |
| 15 | |
| 16 | fn net_ssl_do(req &Request, port int, method Method, host_name string, path string, data string, header Header) !Response { |
| 17 | mut retries := 0 |
| 18 | req_headers := req.build_request_headers_with(method, host_name, port, path, data, header) |
| 19 | $if trace_http_request ? { |
| 20 | eprint('> ') |
| 21 | eprint(req_headers) |
| 22 | eprintln('') |
| 23 | } |
| 24 | for { |
| 25 | mut ssl_conn := ssl.new_ssl_conn( |
| 26 | verify: req.verify |
| 27 | cert: req.cert |
| 28 | cert_key: req.cert_key |
| 29 | validate: req.validate |
| 30 | in_memory_verification: req.in_memory_verification |
| 31 | )! |
| 32 | ssl_conn.dial(host_name, port) or { |
| 33 | retries++ |
| 34 | if is_no_need_retry_error(err.code()) || retries >= req.max_retries { |
| 35 | return err |
| 36 | } |
| 37 | continue |
| 38 | } |
| 39 | // Propagate the request's read timeout into the SSL backend. |
| 40 | // Without this, mbedtls keeps its init-time default and openssl falls back to no |
| 41 | // timeout at all on a stalled socket — see issue surfaced by macOS arm64 + tcc CI hangs. |
| 42 | if req.read_timeout > 0 { |
| 43 | ssl_conn.set_read_timeout(req.read_timeout) |
| 44 | } |
| 45 | return req.do_request(req_headers, mut ssl_conn)! |
| 46 | } |
| 47 | return error('http.net_ssl_do: exhausted retries') |
| 48 | } |
| 49 | |
| 50 | fn read_from_ssl_connection_cb(con voidptr, buf &u8, bufsize int) !int { |
| 51 | mut ssl_conn := unsafe { &ssl.SSLConn(con) } |
| 52 | return ssl_conn.socket_read_into_ptr(buf, bufsize) |
| 53 | } |
| 54 | |
| 55 | fn (req &Request) do_request(req_headers string, mut ssl_conn ssl.SSLConn) !Response { |
| 56 | defer { |
| 57 | ssl_conn.shutdown() or {} |
| 58 | } |
| 59 | ssl_conn.write_string(req_headers) or { return err } |
| 60 | mut content := strings.new_builder(4096) |
| 61 | response_info := req.receive_all_data_from_cb_in_builder(mut content, voidptr(ssl_conn), |
| 62 | read_from_ssl_connection_cb)! |
| 63 | response_text := content.str() |
| 64 | $if trace_http_response ? { |
| 65 | eprint('< ') |
| 66 | eprint(response_text) |
| 67 | eprintln('') |
| 68 | } |
| 69 | if req.on_finish != unsafe { nil } { |
| 70 | req.on_finish(req, u64(response_text.len))! |
| 71 | } |
| 72 | return parse_received_response(response_text, response_info) |
| 73 | } |
| 74 | |