1use std::{
16 fmt::Debug,
17 mem,
18 sync::atomic::{AtomicU64, Ordering},
19 time::Duration,
20};
21
22use backon::{ExponentialBuilder, Retryable};
23use bytes::Bytes;
24use bytesize::ByteSize;
25use eyeball::SharedObservable;
26use http::header::CONTENT_LENGTH;
27#[cfg(not(target_family = "wasm"))]
28use reqwest::Certificate;
29use reqwest::tls;
30use ruma::api::{IncomingResponseExt as _, OutgoingRequest, error::FromHttpResponseError};
31use tracing::{debug, info, warn};
32
33use super::{DEFAULT_REQUEST_TIMEOUT, HttpClient, TransmissionProgress, response_to_http_response};
34use crate::{
35 HttpResult,
36 config::RequestConfig,
37 error::{HttpError, RetryKind},
38};
39
40impl HttpClient {
41 pub(super) async fn send_request<R>(
42 &self,
43 request: http::Request<Bytes>,
44 config: RequestConfig,
45 send_progress: SharedObservable<TransmissionProgress>,
46 ) -> HttpResult<R::IncomingResponse>
47 where
48 R: OutgoingRequest + Debug,
49 HttpError: From<FromHttpResponseError<R::EndpointError>>,
50 {
51 fn make_backoff(config: &RequestConfig) -> ExponentialBuilder {
54 let mut backoff = ExponentialBuilder::new()
57 .with_min_delay(Duration::from_millis(500))
58 .with_max_delay(Duration::from_secs(60))
59 .with_total_delay(Some(Duration::from_secs(15 * 60)))
60 .without_max_times();
61
62 if let Some(max_delay) = config.max_retry_time {
64 backoff = backoff.with_max_delay(max_delay)
65 }
66
67 if let Some(max_times) = config.retry_limit {
68 backoff = backoff.with_max_times(max_times.saturating_sub(1))
71 }
72
73 backoff
74 }
75 async fn send_request_inner(
76 http_client: &reqwest::Client,
77 request: &http::Request<Bytes>,
78 timeout: Option<Duration>,
79 retry_count: &AtomicU64,
80 send_progress: SharedObservable<TransmissionProgress>,
81 ) -> HttpResult<http::Response<Bytes>> {
82 let num_attempt = retry_count.fetch_add(1, Ordering::SeqCst);
83 debug!(num_attempt, "Sending request");
84 let before = ruma::time::Instant::now();
85
86 let response = execute_request(http_client, request, timeout, send_progress).await?;
87
88 let request_duration = ruma::time::Instant::now().saturating_duration_since(before);
89
90 let status_code = response.status();
91 let response_size = ByteSize(response.body().len().try_into().unwrap_or(u64::MAX));
92 tracing::Span::current()
93 .record("status", status_code.as_u16())
94 .record("response_size", response_size.display().si_short().to_string())
95 .record("request_duration", tracing::field::debug(request_duration));
96
97 for (header_name, header_value) in response.headers() {
100 let header_name = header_name.as_str().to_lowercase();
101
102 if header_name == "x-sentry-event-id" {
105 tracing::Span::current()
106 .record("sentry_event_id", header_value.to_str().unwrap_or("<???>"));
107 }
108 }
109
110 Ok(response)
111 }
112 fn adjust_backoff(
113 err: &HttpError,
114 backon_suggested_timeout: Option<Duration>,
115 has_retry_limit: bool,
116 ) -> Option<Duration> {
117 match err.retry_kind() {
118 RetryKind::Transient { retry_after } => {
119 if backon_suggested_timeout.is_some() {
127 retry_after.or(backon_suggested_timeout)
128 } else {
129 None
130 }
131 }
132 RetryKind::Permanent => None,
133 RetryKind::NetworkFailure => {
134 if has_retry_limit { backon_suggested_timeout } else { None }
138 }
139 }
140 }
141
142 let retry_count = AtomicU64::new(1);
143
144 let send_request = || {
145 let send_progress = send_progress.clone();
146 async {
147 let response = send_request_inner(
148 &self.inner,
149 &request,
150 config.timeout,
151 &retry_count,
152 send_progress,
153 )
154 .await?;
155 let (parts, body) = response.into_parts();
156 let response: http::Response<&[u8]> = http::Response::from_parts(parts, &body);
157 R::IncomingResponse::try_from_http_response(response).map_err(HttpError::from)
158 }
159 };
160
161 let has_retry_limit = config.retry_limit.is_some();
162
163 send_request
164 .retry(make_backoff(&config))
165 .adjust(|err, backon_suggested_timeout| {
166 adjust_backoff(err, backon_suggested_timeout, has_retry_limit)
167 })
168 .await
169 }
170}
171
172#[cfg(not(target_family = "wasm"))]
173#[derive(Clone, Debug)]
174pub(crate) struct HttpSettings {
175 pub(crate) disable_ssl_verification: bool,
176 pub(crate) proxy: Option<String>,
177 pub(crate) user_agent: Option<String>,
178 pub(crate) timeout: Option<Duration>,
179 pub(crate) read_timeout: Option<Duration>,
180 pub(crate) additional_root_certificates: Vec<Certificate>,
181 pub(crate) disable_built_in_root_certificates: bool,
182}
183
184#[cfg(not(target_family = "wasm"))]
185impl Default for HttpSettings {
186 fn default() -> Self {
187 Self {
188 disable_ssl_verification: false,
189 proxy: None,
190 user_agent: None,
191 timeout: Some(DEFAULT_REQUEST_TIMEOUT),
192 read_timeout: None,
193 additional_root_certificates: Default::default(),
194 disable_built_in_root_certificates: false,
195 }
196 }
197}
198
199#[cfg(not(target_family = "wasm"))]
200impl HttpSettings {
201 pub(crate) fn make_client(&self) -> Result<reqwest::Client, HttpError> {
203 let user_agent = self.user_agent.clone().unwrap_or_else(|| "matrix-rust-sdk".to_owned());
204 let mut http_client = reqwest::Client::builder()
205 .user_agent(user_agent)
206 .min_tls_version(tls::Version::TLS_1_2);
209
210 if let Some(timeout) = self.timeout {
211 http_client = http_client.timeout(timeout);
212 }
213
214 if let Some(read_timeout) = self.read_timeout {
215 http_client = http_client.read_timeout(read_timeout);
216 }
217
218 if self.disable_ssl_verification {
219 warn!("SSL verification disabled in the HTTP client!");
220 http_client = http_client.danger_accept_invalid_certs(true);
221 }
222
223 http_client = if self.disable_built_in_root_certificates {
224 info!("Built-in root certificates disabled in the HTTP client.");
225 http_client.tls_certs_only(self.additional_root_certificates.clone())
226 } else {
227 http_client.tls_certs_merge(self.additional_root_certificates.clone())
228 };
229
230 if let Some(p) = &self.proxy {
231 info!(proxy_url = p, "Setting the proxy for the HTTP client");
232 http_client = http_client.proxy(reqwest::Proxy::all(p.as_str())?);
233 }
234
235 Ok(http_client.build()?)
236 }
237}
238
239pub(super) async fn execute_request(
240 client: &reqwest::Client,
241 request: &http::Request<Bytes>,
242 timeout: Option<Duration>,
243 send_progress: SharedObservable<TransmissionProgress>,
244) -> Result<http::Response<Bytes>, HttpError> {
245 use std::convert::Infallible;
246
247 use futures_util::stream;
248
249 let request = request.clone();
250 let request = {
251 let mut request = if send_progress.subscriber_count() != 0 {
252 let content_length = request.body().len();
253 send_progress.update(|p| p.total += content_length);
254
255 tokio::task::yield_now().await;
259
260 let mut req = reqwest::Request::try_from(request.map(|body| {
261 let chunks = stream::iter(BytesChunks::new(body, 8192).map(
262 move |chunk| -> Result<_, Infallible> {
263 send_progress.update(|p| p.current += chunk.len());
264 Ok(chunk)
265 },
266 ));
267 reqwest::Body::wrap_stream(chunks)
268 }))?;
269
270 req.headers_mut().insert(CONTENT_LENGTH, content_length.into());
274
275 req
276 } else {
277 reqwest::Request::try_from(request)?
278 };
279
280 *request.timeout_mut() = timeout;
281 request
282 };
283
284 let response = client.execute(request).await?;
285 Ok(response_to_http_response(response).await?)
286}
287
288struct BytesChunks {
289 bytes: Bytes,
290 size: usize,
291}
292
293impl BytesChunks {
294 fn new(bytes: Bytes, size: usize) -> Self {
295 assert_ne!(size, 0);
296 Self { bytes, size }
297 }
298}
299
300impl Iterator for BytesChunks {
301 type Item = Bytes;
302
303 fn next(&mut self) -> Option<Self::Item> {
304 if self.bytes.is_empty() {
305 None
306 } else if self.bytes.len() < self.size {
307 Some(mem::take(&mut self.bytes))
308 } else {
309 Some(self.bytes.split_to(self.size))
310 }
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use bytes::Bytes;
317
318 use super::BytesChunks;
319
320 #[test]
321 fn test_bytes_chunks() {
322 let bytes = Bytes::new();
323 assert!(BytesChunks::new(bytes, 1).collect::<Vec<_>>().is_empty());
324
325 let bytes = Bytes::from_iter([1, 2]);
326 assert_eq!(BytesChunks::new(bytes, 2).collect::<Vec<_>>(), [Bytes::from_iter([1, 2])]);
327
328 let bytes = Bytes::from_iter([1, 2]);
329 assert_eq!(BytesChunks::new(bytes, 3).collect::<Vec<_>>(), [Bytes::from_iter([1, 2])]);
330
331 let bytes = Bytes::from_iter([1, 2, 3]);
332 assert_eq!(
333 BytesChunks::new(bytes, 1).collect::<Vec<_>>(),
334 [Bytes::from_iter([1]), Bytes::from_iter([2]), Bytes::from_iter([3])]
335 );
336
337 let bytes = Bytes::from_iter([1, 2, 3]);
338 assert_eq!(
339 BytesChunks::new(bytes, 2).collect::<Vec<_>>(),
340 [Bytes::from_iter([1, 2]), Bytes::from_iter([3])]
341 );
342
343 let bytes = Bytes::from_iter([1, 2, 3, 4]);
344 assert_eq!(
345 BytesChunks::new(bytes, 2).collect::<Vec<_>>(),
346 [Bytes::from_iter([1, 2]), Bytes::from_iter([3, 4])]
347 );
348 }
349}