1use std::collections::HashMap;
7use std::io::Cursor;
8use std::io::Read;
9use std::str::FromStr;
10use std::sync::Arc;
11use std::sync::atomic::Ordering;
12use std::sync::mpsc;
13use std::sync::mpsc::Receiver;
14use std::sync::mpsc::Sender;
15use std::thread::JoinHandle;
16use std::time::Duration;
17use std::time::Instant;
18
19use curl::easy::Easy2;
20use curl::easy::Handler;
21use curl::easy::InfoType;
22use curl::easy::WriteError;
23use curl::multi::Easy2Handle;
24use curl::multi::Multi;
25use futures::channel::oneshot;
26use portable_atomic::AtomicI64;
27use portable_atomic::AtomicU64;
28use tracing::{debug, error, trace, warn};
29
30use crate::util::network::http::HandleConfiguration;
31use crate::util::network::http::HttpTimeout;
32
33type Response = http::Response<Vec<u8>>;
34type Request = http::Request<Vec<u8>>;
35type HttpResult<T> = std::result::Result<T, Error>;
36
37#[derive(Debug, Clone, thiserror::Error)]
38#[non_exhaustive]
39pub enum Error {
40 #[error("curl multi failed")]
41 Multi(#[from] curl::MultiError),
42
43 #[error("curl failed")]
44 Easy(#[from] curl::Error),
45
46 #[error(
47 "transfer too slow: failed to transfer more than {low_speed_limit} bytes in {}s (transferred {transferred} bytes)",
48 timeout_dur.as_secs()
49 )]
50 TooSlow {
51 low_speed_limit: u32,
52 timeout_dur: Duration,
53 transferred: u64,
54 },
55
56 #[error("failed to convert header value of `{name}` to string: {bytes:?}")]
57 BadHeader { name: String, bytes: Vec<u8> },
58}
59
60struct Message {
61 easy: Easy2<Collector>,
62 sender: oneshot::Sender<HttpResult<Response>>,
63}
64
65#[derive(Default)]
66struct Stats {
67 dl_remaining: AtomicI64,
68 dl_transferred: AtomicU64,
69}
70
71pub struct Client {
74 channel: Option<Sender<Message>>,
75 thread_handle: Option<JoinHandle<()>>,
76 handle_config: HandleConfiguration,
77 stats: Arc<Stats>,
78}
79
80impl Client {
81 pub fn new(handle_config: HandleConfiguration) -> Client {
83 let (tx, rx) = mpsc::channel();
84 let stats = Arc::new(Stats::default());
85 let timeout = handle_config.timeout.clone();
86 let worker_stats = stats.clone();
87 let handle = std::thread::spawn(move || {
88 WorkerServer::run(rx, handle_config.multiplexing, timeout, worker_stats)
89 });
90 Client {
91 channel: Some(tx),
92 thread_handle: Some(handle),
93 handle_config,
94 stats,
95 }
96 }
97
98 pub async fn request(&self, request: Request) -> HttpResult<Response> {
100 let url = request.uri().to_string();
101 debug!(target: "network::fetch", url);
102 let mut collector = Collector::new(self.stats.clone());
103 let (parts, body) = request.into_parts();
104 let body_len = body.len();
105 collector.request_body = Cursor::new(body);
106 collector.debug = self.handle_config.verbose;
107 let mut handle = curl::easy::Easy2::new(collector);
108 self.handle_config.configure2(&mut handle)?;
109
110 handle.url(&url)?;
111 handle.follow_location(true)?;
112 handle.progress(true)?;
113
114 match parts.method {
115 http::Method::HEAD => handle.nobody(true)?,
116 http::Method::GET => handle.get(true)?,
117 http::Method::POST => {
118 handle.post_field_size(body_len as u64)?;
119 handle.post(true)?;
120 }
121 http::Method::PUT => {
122 handle.in_filesize(body_len as u64)?;
123 handle.put(true)?;
124 }
125 method => {
126 handle.upload(true)?;
127 handle.in_filesize(body_len as u64)?;
128 handle.custom_request(method.as_str())?;
129 }
130 }
131
132 let mut headers = curl::easy::List::new();
133 for (name, value) in parts.headers {
134 if let Some(name) = name {
135 let value: &str = value.to_str().map_err(|_| Error::BadHeader {
136 name: name.to_string(),
137 bytes: value.as_bytes().to_owned(),
138 })?;
139 headers.append(&format!("{}: {}", name, value))?;
140 }
141 }
142 handle.http_headers(headers)?;
143
144 let (sender, receiver) = oneshot::channel();
145 let req = Message {
146 easy: handle,
147 sender,
148 };
149
150 self.channel.as_ref().unwrap().send(req).unwrap();
151 receiver.await.unwrap()
152 }
153
154 pub fn bytes_pending(&self) -> u64 {
156 self.stats
157 .dl_remaining
158 .load(Ordering::Acquire)
159 .try_into()
160 .unwrap()
161 }
162}
163
164impl Drop for Client {
165 fn drop(&mut self) {
166 drop(self.channel.take().unwrap());
168 let _ = self.thread_handle.take().unwrap().join();
170 }
171}
172
173impl std::fmt::Debug for Client {
174 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175 f.debug_struct("http_async::Client").finish()
176 }
177}
178
179struct WorkerServer {
182 incoming_work: Receiver<Message>,
184 multi: Multi,
186 handles: HashMap<
188 usize,
189 (
190 Easy2Handle<Collector>,
191 oneshot::Sender<HttpResult<Response>>,
192 ),
193 >,
194 token: usize,
196 timeout: HttpTimeout,
198 stats: Arc<Stats>,
200 low_speed_window_start: Instant,
202 low_speed_window_initial: u64,
204}
205
206impl WorkerServer {
207 fn run(
208 incoming_work: Receiver<Message>,
209 multiplex: bool,
210 timeout: HttpTimeout,
211 stats: Arc<Stats>,
212 ) {
213 let mut multi = Multi::new();
214 if let Err(e) = multi.set_max_host_connections(2) {
216 error!("failed to set max host connections in curl: {e}");
217 }
218 if let Err(e) = multi.pipelining(false, multiplex) {
219 error!("failed to enable multiplexing/pipelining in curl: {e}");
220 }
221
222 let mut worker = Self {
223 incoming_work,
224 multi,
225 handles: HashMap::new(),
226 token: 0,
227 timeout,
228 stats,
229 low_speed_window_start: Instant::now(),
230 low_speed_window_initial: 0,
231 };
232 worker.worker_loop();
233 }
234
235 fn fail_and_drain(&mut self, e: &Error) {
236 warn!(
237 target: "network",
238 "failing all outstanding HTTP requests: {e}"
239 );
240 for (_token, (_handle, sender)) in self.handles.drain() {
241 let _ = sender.send(Err(e.clone()));
242 }
243 }
244
245 fn reset_low_speed_timeout(&mut self) {
247 self.low_speed_window_start = Instant::now();
248 self.low_speed_window_initial = self.stats.dl_transferred.load(Ordering::Acquire);
249 }
250
251 fn check_low_speed_timeout(&mut self) -> Option<Error> {
254 if Instant::now().duration_since(self.low_speed_window_start) < self.timeout.dur {
256 return None;
257 }
258
259 let current = self.stats.dl_transferred.load(Ordering::Acquire);
261 let transferred = current.saturating_sub(self.low_speed_window_initial);
262 self.reset_low_speed_timeout();
263 if transferred < self.timeout.low_speed_limit.into() {
264 Some(Error::TooSlow {
265 low_speed_limit: self.timeout.low_speed_limit,
266 timeout_dur: self.timeout.dur,
267 transferred,
268 })
269 } else {
270 None
271 }
272 }
273
274 fn worker_loop(&mut self) {
275 const INITIAL_DELAY: Duration = Duration::from_millis(1);
276 let mut wait_backoff = INITIAL_DELAY;
277 loop {
278 while let Ok(msg) = self.incoming_work.try_recv() {
280 self.enqueue_request(msg);
281 wait_backoff = INITIAL_DELAY;
282 }
283
284 match self.multi.perform() {
285 Err(e) if e.is_call_perform() => {
286 }
288 Err(e) => {
289 self.fail_and_drain(&Error::Multi(e));
290 }
291 Ok(running) => {
292 self.multi.messages(|msg| {
293 let t = msg.token().expect("all handles have tokens");
294 trace!(token = t, "finish");
295 let Some((handle, sender)) = self.handles.remove(&t) else {
296 error!("missing entry {t} in handle table");
297 return;
298 };
299 let result = msg.result_for2(&handle).expect("handle must have a result");
300 let mut easy = self.multi.remove2(handle).expect("handle must be in multi");
301 let mut response = std::mem::replace(
302 &mut easy.get_mut().response,
303 Response::new(Vec::new()),
304 );
305 if let Ok(status) = easy.response_code()
306 && status != 0
307 && let Ok(status) = http::StatusCode::from_u16(status as u16)
308 {
309 *response.status_mut() = status;
310 }
311 let extensions = Extensions {
313 client_ip: easy.primary_ip().ok().flatten().map(str::to_string),
314 };
315 response.extensions_mut().insert(extensions);
316 let _ = sender.send(result.map(|()| response).map_err(Into::into));
317 });
318
319 if running > 0 {
320 if let Some(timeout_error) = self.check_low_speed_timeout() {
322 self.fail_and_drain(&timeout_error);
323 continue;
324 }
325
326 let max_timeout = Duration::from_millis(1000);
327 let mut timeout = self
328 .multi
329 .get_timeout()
330 .ok()
331 .flatten()
332 .unwrap_or(max_timeout)
333 .min(max_timeout);
334 if timeout.is_zero() {
335 continue;
337 }
338 if wait_backoff < timeout {
350 wait_backoff *= 2;
351 timeout = wait_backoff
352 }
353 trace!(
354 pending = self.handles.len(),
355 timeout = timeout.as_millis(),
356 "curl wait"
357 );
358 if let Err(e) = self.multi.wait(&mut [], timeout) {
359 self.fail_and_drain(&Error::Multi(e));
360 }
361 } else {
362 trace!("all work completed");
364 match self.incoming_work.recv() {
365 Ok(msg) => {
366 trace!("resuming work");
367 self.reset_low_speed_timeout();
368 self.enqueue_request(msg);
369 wait_backoff = INITIAL_DELAY;
370 }
371 Err(_) => {
372 break;
374 }
375 }
376 }
377 }
378 }
379 }
380 }
381
382 fn enqueue_request(&mut self, message: Message) {
384 match self.multi.add2(message.easy) {
385 Ok(mut handle) => {
386 self.token = self.token.wrapping_add(1);
387 handle.set_token(self.token).ok();
388 self.handles.insert(self.token, (handle, message.sender));
389 }
390 Err(e) => {
391 let _ = message.sender.send(Err(e.into()));
392 }
393 }
394 }
395}
396
397struct Collector {
399 response: Response,
401 request_body: Cursor<Vec<u8>>,
403 debug: bool,
405 global_stats: Arc<Stats>,
407 dl_remaining_delta: i64,
409}
410
411impl Collector {
412 fn new(stats: Arc<Stats>) -> Self {
413 Collector {
414 response: Response::new(Vec::new()),
415 request_body: Cursor::new(Vec::new()),
416 debug: false,
417 global_stats: stats,
418 dl_remaining_delta: 0,
419 }
420 }
421}
422
423impl Handler for Collector {
424 fn write(&mut self, data: &[u8]) -> Result<usize, WriteError> {
425 self.response.body_mut().extend_from_slice(data);
426 self.global_stats
427 .dl_transferred
428 .fetch_add(data.len() as u64, Ordering::Release);
429 Ok(data.len())
430 }
431
432 fn header(&mut self, data: &[u8]) -> bool {
433 if let Some((name, value)) = handle_http_header(data)
434 && let Ok(name) = http::HeaderName::from_str(name)
435 && let Ok(value) = http::HeaderValue::from_str(value)
436 {
437 self.response.headers_mut().append(name, value);
438 }
439 true
440 }
441
442 fn read(&mut self, data: &mut [u8]) -> Result<usize, curl::easy::ReadError> {
443 Ok(self.request_body.read(data).unwrap())
444 }
445
446 fn debug(&mut self, kind: InfoType, data: &[u8]) {
447 if self.debug {
448 super::http::debug(kind, data);
449 }
450 }
451
452 fn progress(&mut self, dltotal: f64, dlnow: f64, _ultotal: f64, _ulnow: f64) -> bool {
453 if dlnow > dltotal {
454 return true;
455 }
456 let dl_total = dltotal as i64;
457 let dl_current = dlnow as i64;
458
459 let remaining = dl_total - dl_current;
460
461 self.global_stats
462 .dl_remaining
463 .fetch_add(remaining - self.dl_remaining_delta, Ordering::Release);
464 self.dl_remaining_delta = remaining;
465 true
466 }
467}
468
469impl Drop for Collector {
470 fn drop(&mut self) {
471 self.global_stats
473 .dl_remaining
474 .fetch_add(-self.dl_remaining_delta, Ordering::Release);
475 }
476}
477
478#[derive(Clone)]
480struct Extensions {
481 client_ip: Option<String>,
482}
483
484pub trait ResponsePartsExtensions {
485 fn client_ip(&self) -> Option<&str>;
486}
487
488impl ResponsePartsExtensions for http::response::Parts {
489 fn client_ip(&self) -> Option<&str> {
490 self.extensions
491 .get::<Extensions>()
492 .and_then(|extensions| extensions.client_ip.as_deref())
493 }
494}
495
496impl ResponsePartsExtensions for Response {
497 fn client_ip(&self) -> Option<&str> {
498 self.extensions()
499 .get::<Extensions>()
500 .and_then(|extensions| extensions.client_ip.as_deref())
501 }
502}
503
504fn handle_http_header(buf: &[u8]) -> Option<(&str, &str)> {
506 if buf.is_empty() {
507 return None;
508 }
509 let buf = std::str::from_utf8(buf).ok()?.trim_end();
510 if buf.contains('\n') {
512 return None;
513 }
514 let (tag, value) = buf.split_once(':')?;
515 let value = value.trim();
516 Some((tag, value))
517}