Skip to main content

ruwa_ureq_http_client/
lib.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use wacore_ng::net::{HttpClient, HttpRequest, HttpResponse, StreamingHttpResponse};
4
5/// HTTP client implementation using `ureq` for synchronous HTTP requests.
6/// Since `ureq` is blocking, all requests are wrapped in `tokio::task::spawn_blocking`.
7#[derive(Debug, Clone)]
8pub struct UreqHttpClient {
9    agent: ureq::Agent,
10}
11
12impl UreqHttpClient {
13    pub fn new() -> Self {
14        let agent = build_agent();
15        Self { agent }
16    }
17}
18
19impl Default for UreqHttpClient {
20    fn default() -> Self {
21        Self::new()
22    }
23}
24
25fn build_agent() -> ureq::Agent {
26    #[cfg(feature = "danger-skip-tls-verify")]
27    {
28        use ureq::config::Config;
29        use ureq::tls::TlsConfig;
30        Config::builder()
31            .tls_config(TlsConfig::builder().disable_verification(true).build())
32            .build()
33            .into()
34    }
35
36    #[cfg(not(feature = "danger-skip-tls-verify"))]
37    {
38        ureq::Agent::new_with_defaults()
39    }
40}
41
42#[async_trait]
43impl HttpClient for UreqHttpClient {
44    async fn execute(&self, request: HttpRequest) -> Result<HttpResponse> {
45        let agent = self.agent.clone();
46        // Since ureq is blocking, we must use spawn_blocking
47        tokio::task::spawn_blocking(move || {
48            let response = match request.method.as_str() {
49                "GET" => {
50                    let mut req = agent.get(&request.url);
51                    for (key, value) in &request.headers {
52                        req = req.header(key, value);
53                    }
54                    req.call()?
55                }
56                "POST" => {
57                    let mut req = agent.post(&request.url);
58                    for (key, value) in &request.headers {
59                        req = req.header(key, value);
60                    }
61                    if let Some(body) = request.body {
62                        req.send(&body[..])?
63                    } else {
64                        req.send(&[])?
65                    }
66                }
67                method => {
68                    return Err(anyhow::anyhow!("Unsupported HTTP method: {}", method));
69                }
70            };
71
72            let status_code = response.status().as_u16();
73
74            // Read the response body
75            let mut body = response.into_body();
76            let body_bytes = body.read_to_vec()?;
77
78            Ok(HttpResponse {
79                status_code,
80                body: body_bytes,
81            })
82        })
83        .await?
84    }
85
86    fn execute_streaming(&self, request: HttpRequest) -> Result<StreamingHttpResponse> {
87        // Note: no spawn_blocking here — this is called FROM within spawn_blocking
88        // by the streaming download code. The entire HTTP fetch + decrypt happens
89        // in one blocking thread.
90        let response = match request.method.as_str() {
91            "GET" => {
92                let mut req = self.agent.get(&request.url);
93                for (key, value) in &request.headers {
94                    req = req.header(key, value);
95                }
96                req.call()?
97            }
98            method => {
99                return Err(anyhow::anyhow!(
100                    "Streaming only supports GET, got: {}",
101                    method
102                ));
103            }
104        };
105
106        let status_code = response.status().as_u16();
107        let reader = response.into_body().into_reader();
108
109        Ok(StreamingHttpResponse {
110            status_code,
111            body: Box::new(reader),
112        })
113    }
114}