1use std::{
2 convert::Infallible,
3 io,
4 time::{Duration, Instant},
5};
6
7use base64::{DecodeError, Engine as _, engine::general_purpose::URL_SAFE};
8use chrono::{Local, NaiveTime};
9use http::Response;
10use http_body_util::combinators::BoxBody;
11use hyper::{
12 Request,
13 body::{Bytes, Incoming},
14 client::conn::{http1, http2},
15};
16use hyper_util::rt::{TokioExecutor, TokioIo};
17use opentelemetry::KeyValue;
18use prosa::{
19 core::{
20 adaptor::Adaptor,
21 error::ProcError,
22 msg::{InternalMsg, Msg, RequestMsg},
23 proc::{Proc, ProcBusParam as _, proc, proc_settings},
24 },
25 io::stream::TargetSetting,
26};
27use serde::{Deserialize, Serialize};
28use thiserror::Error;
29use tokio::{
30 sync::{mpsc, watch},
31 time,
32};
33use tracing::{debug, error, info, warn};
34
35use crate::adaptor::FetcherAdaptor;
36
37#[derive(Debug, Error)]
38pub enum FetcherError<M>
40where
41 M: std::marker::Send,
42{
43 #[error("IO error during the fetch `{0}`")]
45 Io(#[from] io::Error),
46 #[error("Hyper error during the fetch `{0:?}` from `{1}`")]
48 Hyper(hyper::Error, String),
49 #[error("HTTP error on object parsing `{0}`")]
51 Http(#[from] http::Error),
52 #[error("Fetcher communication error `{0}`")]
54 Queue(#[from] watch::error::SendError<FetchAction<M>>),
55 #[error("No HTTP task available to process the message `{0}`")]
57 HttpQueue(Box<mpsc::error::SendError<http::Request<BoxBody<Bytes, Infallible>>>>),
58 #[error("Can't decode Base64 data `{0}`")]
60 B64Decode(#[from] DecodeError),
61 #[error("Fetcher other error `{0}`")]
63 Other(String),
64}
65
66impl<M> From<mpsc::error::SendError<http::Request<BoxBody<Bytes, Infallible>>>> for FetcherError<M>
67where
68 M: std::marker::Send,
69{
70 fn from(error: mpsc::error::SendError<http::Request<BoxBody<Bytes, Infallible>>>) -> Self {
71 FetcherError::<M>::HttpQueue(Box::new(error))
72 }
73}
74
75impl<M> ProcError for FetcherError<M>
76where
77 M: 'static + std::fmt::Debug + std::marker::Send,
78{
79 fn recoverable(&self) -> bool {
80 match self {
81 FetcherError::Io(error) => error.recoverable(),
82 FetcherError::Hyper(_error, _addr) => true,
83 FetcherError::Http(_error) => true,
84 FetcherError::Queue(_send_error) => false,
85 FetcherError::HttpQueue(_send_error) => false,
86 FetcherError::B64Decode(_decode_error) => false,
87 FetcherError::Other(_) => false,
88 }
89 }
90}
91
92#[derive(Debug, Deserialize, Serialize, Copy, Clone)]
93pub struct TimeRange {
94 pub start: NaiveTime,
96 pub end: NaiveTime,
98}
99
100impl TimeRange {
101 pub fn contains(&self, time: &NaiveTime) -> bool {
103 if self.start <= self.end {
104 time >= &self.start && time <= &self.end
105 } else {
106 time >= &self.start || time <= &self.end
107 }
108 }
109}
110
111#[proc_settings]
113#[derive(Debug, Deserialize, Serialize, Clone)]
114pub struct FetcherSettings {
115 target: Option<TargetSetting>,
117 service_name: Option<String>,
119 #[serde(default = "FetcherSettings::get_default_authorization")]
121 pub authorization: bool,
122 #[serde(default = "FetcherSettings::get_default_period")]
124 period: Duration,
125 #[serde(default = "FetcherSettings::get_default_timeout")]
127 timeout: Duration,
128 #[serde(default = "FetcherSettings::get_default_max_retry")]
130 max_retry: u8,
131 pub(crate) active_time_range: Option<TimeRange>,
133 #[serde(default)]
134 title_case_headers: bool,
135}
136
137impl FetcherSettings {
138 fn get_default_authorization() -> bool {
139 true
140 }
141
142 fn get_default_period() -> Duration {
143 Duration::from_secs(60)
144 }
145
146 fn get_default_timeout() -> Duration {
147 Duration::from_secs(10)
148 }
149
150 fn get_default_max_retry() -> u8 {
151 2
152 }
153
154 pub fn new(
156 target: TargetSetting,
157 service_name: String,
158 authorization: bool,
159 period: Duration,
160 timeout: Duration,
161 ) -> FetcherSettings {
162 FetcherSettings {
163 target: Some(target),
164 service_name: Some(service_name),
165 authorization,
166 period,
167 timeout,
168 ..Default::default()
169 }
170 }
171
172 pub fn username(&self) -> Option<&str> {
174 self.target.as_ref().map(|t| t.url.username())
175 }
176
177 pub fn password(&self) -> Result<Option<Vec<u8>>, DecodeError> {
179 if let Some(password) = self.target.as_ref().and_then(|t| t.url.password()) {
180 Ok(Some(URL_SAFE.decode(password.replace("%3D", "="))?))
181 } else {
182 Ok(None)
183 }
184 }
185
186 pub fn challenge_password<H, M>(
189 &self,
190 challenge: &[u8],
191 ) -> Result<Option<bytes::Bytes>, FetcherError<M>>
192 where
193 H: hmac::Mac + hmac::digest::KeyInit,
194 M: Send,
195 {
196 if let Some(password) = self.target.as_ref().and_then(|t| t.url.password()) {
197 let binary_password = URL_SAFE.decode(password.replace("%3D", "="))?;
198 let mut mac =
199 <H as hmac::digest::KeyInit>::new_from_slice(&binary_password).map_err(|e| {
200 FetcherError::Other(format!("Crypto error on password challenge {e}"))
201 })?;
202 mac.update(challenge);
203 return Ok(Some(bytes::Bytes::copy_from_slice(
204 &mac.finalize().into_bytes(),
205 )));
206 }
207
208 Ok(None)
209 }
210
211 pub fn is_active(&self) -> bool {
214 if let Some(active_time_range) = self.active_time_range {
215 active_time_range.contains(&Local::now().time())
216 } else {
217 true
218 }
219 }
220
221 pub fn get_http1_ctx(&self) -> http1::Builder {
223 let mut http1_ctx = http1::Builder::new();
224
225 if self.title_case_headers {
226 http1_ctx.title_case_headers(true);
228 }
229
230 http1_ctx
231 }
232}
233
234#[proc_settings]
235impl Default for FetcherSettings {
236 fn default() -> Self {
237 FetcherSettings {
238 target: None,
239 service_name: None,
240 authorization: Self::get_default_authorization(),
241 period: Self::get_default_period(),
242 timeout: Self::get_default_timeout(),
243 max_retry: Self::get_default_max_retry(),
244 active_time_range: None,
245 title_case_headers: false,
246 }
247 }
248}
249
250#[derive(Debug)]
252pub enum FetchAction<M>
253where
254 M: std::marker::Send,
255{
256 None,
258 Http,
260 Srv(String, M),
262}
263
264impl<M> FetchAction<M>
265where
266 M: std::marker::Send,
267{
268 pub fn have_action(&self) -> bool {
270 !matches!(self, FetchAction::<M>::None)
271 }
272}
273
274#[proc(settings = FetcherSettings)]
275pub struct FetcherProc {}
276
277#[proc]
278impl FetcherProc {
279 fn spawn_http_fetch(
280 settings: &FetcherSettings,
281 target: TargetSetting,
282 mut req_rx: mpsc::Receiver<Request<BoxBody<Bytes, Infallible>>>,
283 resp_tx: mpsc::Sender<Result<Response<Incoming>, FetcherError<M>>>,
284 ) {
285 let timeout = settings.timeout;
286 let have_time_range = settings.active_time_range.is_some();
287 let http1_ctx = settings.get_http1_ctx();
288 let max_retry = settings.max_retry;
289 tokio::spawn(async move {
290 let mut msg_to_send = None;
291 let mut nb_retry = 0;
292 'conn: loop {
293 if msg_to_send.is_none() {
295 msg_to_send = req_rx.recv().await;
296 if msg_to_send.is_none() {
297 if let Err(e) = resp_tx.try_send(Err(FetcherError::Other(
298 "Internal HTTP queue is closed".to_string(),
299 ))) {
300 warn!(
301 addr = target.to_string(),
302 "Error during message openning: {e}"
303 );
304 }
305 return;
306 }
307 }
308
309 match target.connect().await {
310 Ok(stream) => {
311 let is_http2 = stream.selected_alpn_check(|alpn| alpn == b"h2");
312 let stream = TokioIo::new(stream);
313
314 if is_http2 {
315 match time::timeout(
316 timeout,
317 http2::handshake(TokioExecutor::new(), stream),
318 )
319 .await
320 {
321 Ok(Ok((mut sender, mut connection))) => loop {
322 if let Some(msg) = msg_to_send.take() {
323 tokio::select! {
324 Err(_) = &mut connection => {
326 debug!(addr = target.to_string(), "Remote close the HTTP2 socket");
327 continue 'conn;
328 }
329 resp = sender.try_send_request(msg) => {
331 match resp {
332 Ok(r) => {
333 if let Err(e) = resp_tx.try_send(Ok(r)) {
334 warn!(addr = target.to_string(), "Error during HTTP2 response return: {e}");
335 } else {
336 nb_retry = 0;
337 }
338 }
339 Err(mut e) => {
340 msg_to_send = e.take_message();
341 let hyper_err = e.into_error();
342 if nb_retry < max_retry {
343 nb_retry += 1;
345 continue 'conn;
346 } else {
347 error!(addr = target.to_string(), "Failed to fetch HTTP2 `{msg_to_send:?}`, because of `{hyper_err}`, after {nb_retry}/{max_retry} retries");
348 if let Err(e) = resp_tx.try_send(Err(FetcherError::Hyper(hyper_err, target.to_string()))) {
349 warn!(addr = target.to_string(), "Error during HTTP2 error response return: {e}");
350 }
351 continue 'conn;
352 }
353 }
354 }
355 }
356 Some(mut msg) = req_rx.recv() => {
358 *msg.version_mut() = http::Version::HTTP_2;
359 msg_to_send = Some(msg);
360 }
361 }
362 } else {
363 tokio::select! {
364 Err(_) = &mut connection => {
366 continue 'conn;
367 }
368 Some(mut msg) = req_rx.recv() => {
370 *msg.version_mut() = http::Version::HTTP_2;
371 msg_to_send = Some(msg);
372 }
373 }
374 }
375 },
376 Ok(Err(handshake_error)) => warn!(
377 addr = target.to_string(),
378 "HTTP2 handshake error: {handshake_error}"
379 ),
380 Err(_) => warn!(
381 addr = target.to_string(),
382 "HTTP2 handshake timeout after {}ms", target.connect_timeout
383 ),
384 }
385 } else {
386 match time::timeout(timeout, http1_ctx.handshake(stream)).await {
387 Ok(Ok((mut sender, mut connection))) => loop {
388 if let Some(msg) = msg_to_send.take() {
389 tokio::select! {
390 Err(_) = &mut connection => {
392 debug!(addr = target.to_string(), "Remote close the HTTP1 socket");
393 continue 'conn;
394 }
395 resp = sender.try_send_request(msg) => {
397 match resp {
398 Ok(r) => {
399 if let Err(e) = resp_tx.try_send(Ok(r)) {
400 warn!(addr = target.to_string(), "Error during HTTP1 response return: {e}");
401 } else {
402 nb_retry = 0;
403 }
404 }
405 Err(mut e) => {
406 msg_to_send = e.take_message();
407 let hyper_err = e.into_error();
408 if nb_retry < max_retry {
409 nb_retry += 1;
411 continue 'conn;
412 } else {
413 error!(addr = target.to_string(), "Failed to fetch HTTP1 `{msg_to_send:?}`, because of `{hyper_err}`, after {nb_retry}/{max_retry} retries");
414 if let Err(e) = resp_tx.try_send(Err(FetcherError::Hyper(hyper_err, target.to_string()))) {
415 warn!(addr = target.to_string(), "Error during HTTP1 error response return: {e}");
416 }
417 continue 'conn;
418 }
419 }
420 }
421 }
422 Some(mut msg) = req_rx.recv() => {
424 *msg.version_mut() = http::Version::HTTP_11;
425 msg_to_send = Some(msg);
426 }
427 }
428 } else {
429 tokio::select! {
430 Err(_) = &mut connection => {
432 continue 'conn;
433 }
434 Some(mut msg) = req_rx.recv() => {
436 *msg.version_mut() = http::Version::HTTP_11;
437 msg_to_send = Some(msg);
438 }
439 }
440 }
441 },
442 Ok(Err(handshake_error)) => warn!(
443 addr = target.to_string(),
444 "HTTP1 handshake error: {handshake_error}"
445 ),
446 Err(_) => warn!(
447 addr = target.to_string(),
448 "HTTP1 handshake timeout after {}ms", target.connect_timeout
449 ),
450 }
451 }
452 }
453 Err(e) => {
454 if have_time_range {
456 info!(
457 addr = target.to_string(),
458 "Can't connect to remote: {:?}", e
459 );
460 } else {
461 warn!(
462 addr = target.to_string(),
463 "Can't connect to remote: {:?}", e
464 );
465 }
466 }
467 }
468 }
469 });
470 }
471}
472
473macro_rules! process_action {
474 ($self:ident, $action:ident, $adaptor:ident, $http_req_tx:ident) => {
475 match $action {
476 FetchAction::Http => {
477 let request_builder = if let Some(target) = &$self.settings.target {
478 let mut authority_url = target.url.clone();
479 let _ = authority_url.set_username("");
480 let _ = authority_url.set_password(None);
481 let mut request_builder = Request::builder().header(hyper::header::HOST, authority_url.authority());
482 if $self.settings.authorization
483 && let Some(authorization) = target.get_authentication()
484 {
485 request_builder = request_builder.header(hyper::header::AUTHORIZATION, authorization);
486 }
487
488 request_builder
490 } else {
491 Request::builder()
492 };
493 let request = $adaptor.create_http_request(request_builder)?;
494 debug!(addr = $self.settings.target.as_ref().map(|t| t.to_string()), "Send: {:?}", request);
495 $http_req_tx.send(request).await.map_err(FetcherError::<M>::from)?;
496 }
497 FetchAction::Srv(service_name, msg) => {
498 debug!("Call Service({}) Fetch action", service_name);
499 if let Some(service) = $self.service.get_proc_service(&service_name) {
500 let req_msg = RequestMsg::new(service_name.clone(), msg, $self.proc.get_service_queue());
501 debug!(name: "fetcher_proc", target: "prosa_proc_fetcher::proc", parent: req_msg.get_span(), proc_name = $self.proc.name(), service = service_name, request = format!("{:?}", req_msg.get_data()));
502 service.proc_queue.send(InternalMsg::Request(req_msg)).await?;
503 }
504 },
505 FetchAction::None => { }
506 }
507 };
508}
509
510#[proc]
512impl<A> Proc<A> for FetcherProc
513where
514 A: Adaptor + FetcherAdaptor<M> + std::marker::Send,
515{
516 async fn internal_run(&mut self) -> Result<(), Box<dyn ProcError + Send + Sync>> {
517 let mut adaptor = A::new(self)?;
519
520 self.proc.add_proc().await?;
524
525 let mut fetch_interval = time::interval(self.settings.period);
527
528 let (http_req_tx, http_req_rx) = mpsc::channel(1);
530 let (http_resp_tx, mut http_resp_rx) = mpsc::channel(1);
531 if let Some(target) = &self.settings.target {
532 Self::spawn_http_fetch(&self.settings, target.clone(), http_req_rx, http_resp_tx);
533 }
534
535 let meter = self.proc.meter("fetcher");
536 let action_histogram = meter
537 .u64_histogram("prosa_fetcher_duration")
538 .with_description("Fetcher duration histogram")
539 .build();
540
541 let mut is_active = true;
542 let mut sent_time = Instant::now();
543 loop {
544 tokio::select! {
545 _interval = fetch_interval.tick() => if self.settings.is_active() {
546 is_active = true;
547 let action = adaptor.fetch()?;
548 process_action!(self, action, adaptor, http_req_tx);
549 sent_time = Instant::now();
550 } else if is_active {
551 is_active = false;
552 adaptor.end_active_period();
553 },
554 Some(http_resp) = http_resp_rx.recv() => {
555 let mut histogram_attributes = vec![
556 KeyValue::new("type", "http"),
557 KeyValue::new("code", http_resp.as_ref().map(|r| r.status().as_u16()).unwrap_or(502) as i64),
558 ];
559 if let Some(target) = &self.settings.target {
560 histogram_attributes.push(KeyValue::new("target", target.to_string()));
561 }
562 action_histogram.record(
563 sent_time.elapsed().as_millis() as u64,
564 &histogram_attributes,
565 );
566
567 let action = adaptor.process_http_response(http_resp).await?;
568 process_action!(self, action, adaptor, http_req_tx);
569 sent_time = Instant::now();
570 }
571 Some(msg) = self.internal_rx_queue.recv() => {
572 match msg {
573 InternalMsg::Request(msg) => panic!(
574 "The fetcher processor {} should not receive a request {:?}",
575 self.get_proc_id(),
576 msg
577 ),
578 InternalMsg::Response(msg) => {
579 action_histogram.record(
580 sent_time.elapsed().as_millis() as u64,
581 &[
582 KeyValue::new("type", "service"),
583 KeyValue::new("service", msg.get_service().clone()),
584 KeyValue::new("code", 0),
585 ],
586 );
587 let action = adaptor.process_service_response(msg)?;
588 process_action!(self, action, adaptor, http_req_tx);
589 sent_time = Instant::now();
590 },
591 InternalMsg::Error(err) => {
592 action_histogram.record(
593 sent_time.elapsed().as_millis() as u64,
594 &[
595 KeyValue::new("type", "service"),
596 KeyValue::new("service", err.get_service().clone()),
597 KeyValue::new("code", err.get_err().get_code() as i64),
598 ],
599 );
600 let action = adaptor.process_service_error(err)?;
601 process_action!(self, action, adaptor, http_req_tx);
602 sent_time = Instant::now();
603 },
604 InternalMsg::Command(_) => todo!(),
605 InternalMsg::Config => todo!(),
606 InternalMsg::Service(table) => self.service = table,
607 InternalMsg::Shutdown => {
608 adaptor.terminate();
610 self.proc.remove_proc(None).await?;
611 return Ok(());
612 }
613 }
614 }
615 }
616 }
617 }
618}