Skip to main content

headless_lms_utils/
http.rs

1use std::time::Duration;
2
3use headless_lms_base::config::bool_env_false_by_default;
4use once_cell::sync::Lazy;
5
6/// Total deadline for a single request/response exchange, for callers that do not set their own.
7/// Never apply it to a streaming request: reqwest counts it until the response body has finished,
8/// so it would cut off a long but healthy stream.
9pub const NON_STREAMING_REQUEST_TIMEOUT: Duration = Duration::from_secs(120);
10
11/// How long a streaming request may take to produce response headers. Applied by the caller with
12/// `tokio::time::timeout` around `send()`, which resolves on headers, so the body is left untimed.
13pub const STREAM_RESPONSE_HEADERS_TIMEOUT: Duration = Duration::from_secs(120);
14
15/// How long a streaming response body may go without producing a chunk before it counts as stalled.
16/// Applied per chunk by the caller, unlike `ClientBuilder::read_timeout`; see `REQWEST_STREAMING_CLIENT`.
17pub const STREAM_IDLE_TIMEOUT: Duration = Duration::from_secs(120);
18
19/// How long establishing the connection may take, before anything is sent.
20pub const CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
21
22// Reads env vars directly instead of caching in ApplicationConfiguration, which isn't static.
23fn in_http_test_mode() -> bool {
24    bool_env_false_by_default("USE_MOCK_AZURE_CONFIGURATION")
25        || bool_env_false_by_default("TEST_MODE")
26}
27
28/// The settings both clients share, timeouts other than the connect one left to the caller.
29fn base_client_builder() -> reqwest::ClientBuilder {
30    reqwest::Client::builder()
31        .use_rustls_tls()
32        .https_only(!in_http_test_mode())
33        .connect_timeout(CONNECT_TIMEOUT)
34}
35
36pub static REQWEST_CLIENT: Lazy<reqwest::Client> = Lazy::new(|| {
37    if in_http_test_mode() {
38        warn!("Test environment. REQWEST_CLIENT is allowed to make http requests");
39    }
40
41    base_client_builder()
42        // Default deadline only: a per-request `.timeout()` takes precedence over it. Deliberately
43        // not `read_timeout`, whose sleep is armed once when the request is created and polled
44        // before the response future, making it a flat ceiling on the wait for headers that no
45        // per-request timeout can raise.
46        .timeout(NON_STREAMING_REQUEST_TIMEOUT)
47        .build()
48        .expect("Failed to build Client")
49});
50
51/// For responses whose body is consumed as a stream. Carries no total or read timeout, because both
52/// would cut off a healthy but slow stream; callers must instead bound the header wait with
53/// [`STREAM_RESPONSE_HEADERS_TIMEOUT`] and each chunk with [`STREAM_IDLE_TIMEOUT`].
54pub static REQWEST_STREAMING_CLIENT: Lazy<reqwest::Client> = Lazy::new(|| {
55    base_client_builder()
56        .build()
57        .expect("Failed to build streaming Client")
58});