fix(http): set I/O timeout to 1 minute rather than whole request timeout

Before the fix HTTP client
had no connection timeout,
so it only had a chance
to test one IPv6 and one IPv4
address if the first addresses timed out.
Now it can test at least 4 addresses
of each family and more if some addresses
refuse connection rather than time out.
This commit is contained in:
link2xt
2024-08-25 15:01:34 +00:00
parent 137ee9334c
commit f912bc78e6
2 changed files with 24 additions and 1 deletions

View File

@@ -27,6 +27,13 @@ use tls::wrap_tls;
/// This constant should be more than the largest expected RTT.
pub(crate) const TIMEOUT: Duration = Duration::from_secs(60);
/// Transaction timeout, e.g. for a GET or POST request
/// together with all connection attempts.
///
/// This is the worst case time user has to wait on a very slow network
/// after clicking a button and before getting an error message.
pub(crate) const TRANSACTION_TIMEOUT: Duration = Duration::from_secs(300);
/// TTL for caches in seconds.
pub(crate) const CACHE_TTL: u64 = 30 * 24 * 60 * 60;

View File

@@ -136,8 +136,24 @@ pub(crate) async fn get_client(context: &Context, load_cache: bool) -> Result<re
let socks5_config = Socks5Config::from_database(&context.sql).await?;
let resolver = Arc::new(CustomResolver::new(context.clone(), load_cache));
// `reqwest` uses `hyper-util` crate internally which implements
// [Happy Eyeballs](https://datatracker.ietf.org/doc/html/rfc6555) algorithm.
// On a dual-stack host it starts IPv4 connection attempts in parallel
// to IPv6 connection attempts after 300 ms.
// In the worst case of all connection attempts
// timing out this allows to try four IPv6 and four IPv4
// addresses before request expires
// if request timeout is set to 5 minutes
// and connection timeout is set to 1 minute.
//
// We do not set write timeout because `reqwest`
// does not support it, but request timeout
// should prevent deadlocks if the server
// does not read the data.
let builder = reqwest::ClientBuilder::new()
.timeout(super::TIMEOUT)
.connect_timeout(super::TIMEOUT)
.read_timeout(super::TIMEOUT)
.timeout(super::TRANSACTION_TIMEOUT)
.add_root_certificate(LETSENCRYPT_ROOT.clone())
.dns_resolver(resolver);