acick_util/service/
session.rs

1use std::io::Write as _;
2use std::time::Duration;
3
4use anyhow::Context as _;
5use reqwest::blocking::{Client, Request, RequestBuilder, Response};
6use retry::{delay, retry, OperationResult};
7
8use crate::abs_path::AbsPathBuf;
9use crate::service::CookieStorage;
10use crate::{Console, Error, Result};
11
12pub struct RetryRequestBuilder<'a> {
13    inner: RequestBuilder,
14    client: &'a Client,
15    cookies_path: &'a AbsPathBuf,
16    retry_limit: usize,
17    retry_interval: Duration,
18}
19
20impl<'a> RetryRequestBuilder<'a> {
21    pub fn retry_send(mut self, cnsl: &mut Console) -> Result<Response> {
22        let retry_interval = self.retry_interval.as_millis() as u64;
23        let durations = delay::Fixed::from_millis(retry_interval).take(self.retry_limit);
24        retry(durations, || self.send(cnsl)).map_err(|err| match err {
25            retry::Error::Operation { error, .. } => error,
26            retry::Error::Internal(msg) => Error::msg(msg),
27        })
28    }
29
30    fn send(&mut self, cnsl: &mut Console) -> OperationResult<Response, Error> {
31        let result = self
32            .inner
33            .try_clone()
34            .ok_or_else(|| Error::msg("Could not create request"))
35            .and_then(|builder| Ok(builder.build()?))
36            .context("Could not build request")
37            .and_then(|req| self.exec_session_pretty(req, cnsl));
38        match result {
39            Ok(res) => {
40                if res.status().is_server_error() {
41                    OperationResult::Retry(Error::msg("Received server error"))
42                } else {
43                    OperationResult::Ok(res)
44                }
45            }
46            Err(err) => OperationResult::Retry(err),
47        }
48    }
49
50    fn exec_session_pretty(&mut self, req: Request, cnsl: &mut Console) -> Result<Response> {
51        write!(cnsl, "{:7} {} ... ", req.method().as_str(), req.url()).unwrap_or(());
52        let result = self.exec_session(req).context("Could not send request");
53        match &result {
54            Ok(res) => writeln!(cnsl, "{}", res.status()),
55            Err(_) => writeln!(cnsl, "failed"),
56        }
57        .unwrap_or(());
58        result
59    }
60
61    fn exec_session(&self, mut request: Request) -> Result<Response> {
62        let mut storage =
63            CookieStorage::open(self.cookies_path).context("Could not open cookie storage")?;
64        storage
65            .load_into(&mut request)
66            .context("Could not load cookies into request")?;
67        let response = self.client.execute(request)?;
68        storage
69            .store_from(&response)
70            .context("Could not store cookies from response")?;
71        Ok(response)
72    }
73}
74
75pub trait WithRetry {
76    fn with_retry<'a>(
77        self,
78        client: &'a Client,
79        cookies_path: &'a AbsPathBuf,
80        retry_limit: usize,
81        retry_interval: Duration,
82    ) -> RetryRequestBuilder<'a>;
83}
84
85impl WithRetry for RequestBuilder {
86    fn with_retry<'a>(
87        self,
88        client: &'a Client,
89        cookies_path: &'a AbsPathBuf,
90        retry_limit: usize,
91        retry_interval: Duration,
92    ) -> RetryRequestBuilder<'a> {
93        RetryRequestBuilder {
94            inner: self,
95            client,
96            cookies_path,
97            retry_limit,
98            retry_interval,
99        }
100    }
101}