From b3b68e418dfd74757ac30afa807190de28b2c544 Mon Sep 17 00:00:00 2001 From: shove Date: Fri, 27 Oct 2023 17:27:54 +0800 Subject: [PATCH] net.http: add a retry mechanism to http.fetch(), when the socket inevitably errors (#19660) --- vlib/net/http/backend_nix.c.v | 12 ++++++++- vlib/net/http/http.v | 2 ++ vlib/net/http/request.v | 49 ++++++++++++++++++++++++++++++----- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/vlib/net/http/backend_nix.c.v b/vlib/net/http/backend_nix.c.v index 84e84f4b105cd4..50644dbebbf798 100644 --- a/vlib/net/http/backend_nix.c.v +++ b/vlib/net/http/backend_nix.c.v @@ -14,7 +14,17 @@ fn (req &Request) ssl_do(port int, method Method, host_name string, path string) validate: req.validate in_memory_verification: req.in_memory_verification )! - ssl_conn.dial(host_name, port) or { return err } + mut retries := 0 + for { + ssl_conn.dial(host_name, port) or { + retries++ + if is_no_need_retry_error(err.code()) || retries >= req.max_retries { + return err + } + continue + } + break + } req_headers := req.build_request_headers(method, host_name, path) $if trace_http_request ? { diff --git a/vlib/net/http/http.v b/vlib/net/http/http.v index d4a870589335f4..c8cecf9931caf9 100644 --- a/vlib/net/http/http.v +++ b/vlib/net/http/http.v @@ -31,6 +31,7 @@ pub mut: cert_key string // the path to a key.pem file, containing private keys for the client certificate(s) in_memory_verification bool // if true, verify, cert, and cert_key are read from memory, not from a file allow_redirect bool = true // whether to allow redirect + max_retries int = 5 // maximum number of retries required when an underlying socket error occurs // callbacks to allow custom reporting code to run, while the request is running on_redirect RequestRedirectFn = unsafe { nil } on_progress RequestProgressFn = unsafe { nil } @@ -164,6 +165,7 @@ pub fn fetch(config FetchConfig) !Response { cert_key: config.cert_key in_memory_verification: config.in_memory_verification allow_redirect: config.allow_redirect + max_retries: config.max_retries on_progress: config.on_progress on_redirect: config.on_redirect on_finish: config.on_finish diff --git a/vlib/net/http/request.v b/vlib/net/http/request.v index 1b24519d29aff9..26adbf40565bd9 100644 --- a/vlib/net/http/request.v +++ b/vlib/net/http/request.v @@ -42,6 +42,7 @@ pub mut: cert_key string in_memory_verification bool // if true, verify, cert, and cert_key are read from memory, not from a file allow_redirect bool = true // whether to allow redirect + max_retries int = 5 // maximum number of retries required when an underlying socket error occurs // callbacks to allow custom reporting code to run, while the request is running on_redirect RequestRedirectFn = unsafe { nil } on_progress RequestProgressFn = unsafe { nil } @@ -120,15 +121,42 @@ fn (req &Request) method_and_url_to_response(method Method, url urllib.URL) !Res // println('fetch $method, $scheme, $host_name, $nport, $path ') if scheme == 'https' && req.proxy == unsafe { nil } { // println('ssl_do( $nport, $method, $host_name, $path )') - res := req.ssl_do(nport, method, host_name, path)! - return res + mut retries := 0 + for { + res := req.ssl_do(nport, method, host_name, path) or { + retries++ + if is_no_need_retry_error(err.code()) || retries >= req.max_retries { + return err + } + continue + } + return res + } } else if scheme == 'http' && req.proxy == unsafe { nil } { // println('http_do( $nport, $method, $host_name, $path )') - res := req.http_do('${host_name}:${nport}', method, path)! - return res + mut retries := 0 + for { + res := req.http_do('${host_name}:${nport}', method, path) or { + retries++ + if is_no_need_retry_error(err.code()) || retries >= req.max_retries { + return err + } + continue + } + return res + } } else if req.proxy != unsafe { nil } { - res := req.proxy.http_do(host_name, method, path, req)! - return res + mut retries := 0 + for { + res := req.proxy.http_do(host_name, method, path, req) or { + retries++ + if is_no_need_retry_error(err.code()) || retries >= req.max_retries { + return err + } + continue + } + return res + } } return error('http.request.method_and_url_to_response: unsupported scheme: "${scheme}"') } @@ -470,3 +498,12 @@ fn parse_disposition(line string) map[string]string { } return data } + +fn is_no_need_retry_error(err_code int) bool { + return err_code in [ + net.err_port_out_of_range.code(), + net.err_no_udp_remote.code(), + net.err_connect_timed_out.code(), + net.err_timed_out_code, + ] +}