Skip to main content

whatsapp_rust_ureq_http_client/
lib.rs

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