Skip to main content

cdk_http_client/transport/
mod.rs

1//! HTTP transport trait and implementations
2
3use std::fmt::Debug;
4
5use async_trait::async_trait;
6use cashu::nuts::nut22::AuthToken;
7use serde::de::DeserializeOwned;
8use serde::Serialize;
9use url::Url;
10
11#[cfg(any(target_arch = "wasm32", feature = "bitreq", feature = "reqwest"))]
12use crate::{HttpClient, HttpClientBuilder};
13use crate::{HttpError, RawResponse};
14
15/// Expected HTTP transport.
16///
17/// Callers that construct a transport implicitly may add a [`Default`] bound,
18/// while configured transports can be supplied directly without implementing
19/// a meaningless default configuration.
20#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
21#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
22pub trait Transport: Send + Sync + Debug + Clone {
23    /// Connect to a WebSocket endpoint using this transport.
24    async fn ws_connect(
25        &self,
26        url: &str,
27        headers: &[(&str, &str)],
28    ) -> Result<(crate::ws::WsSender, crate::ws::WsReceiver), crate::ws::WsError> {
29        crate::ws::connect(url, headers).await
30    }
31
32    /// Make the transport use a proxy.
33    ///
34    /// SOCKS proxy schemes such as `socks5h` are available only when this crate
35    /// is built with the `reqwest` feature. The default `bitreq` backend accepts
36    /// HTTP proxy URLs only.
37    fn with_proxy(
38        &mut self,
39        proxy: Url,
40        host_matcher: Option<&str>,
41        accept_invalid_certs: bool,
42    ) -> Result<(), HttpError>;
43
44    /// DNS resolver to get TXT records from a domain name.
45    ///
46    /// Transports that support DNS resolution should override this method. The
47    /// default implementation keeps the trait API stable when the `bip353`
48    /// feature is disabled.
49    async fn resolve_dns_txt(&self, _domain: &str) -> Result<Vec<String>, HttpError> {
50        Err(HttpError::Other(
51            "DNS TXT resolution is not enabled for this transport".to_owned(),
52        ))
53    }
54
55    /// HTTP GET request.
56    async fn http_get<R>(&self, url: Url, auth: Option<AuthToken>) -> Result<R, HttpError>
57    where
58        R: DeserializeOwned;
59
60    /// HTTP GET request returning a raw response.
61    async fn http_get_raw(
62        &self,
63        url: Url,
64        auth: Option<AuthToken>,
65    ) -> Result<RawResponse, HttpError>;
66
67    /// HTTP POST request.
68    async fn http_post<P, R>(
69        &self,
70        url: Url,
71        auth_token: Option<AuthToken>,
72        payload: &P,
73    ) -> Result<R, HttpError>
74    where
75        P: Serialize + Send + Sync,
76        R: DeserializeOwned;
77
78    /// HTTP POST request with a form body returning a raw response.
79    async fn http_post_form_raw<P>(
80        &self,
81        url: Url,
82        auth_token: Option<AuthToken>,
83        payload: &P,
84    ) -> Result<RawResponse, HttpError>
85    where
86        P: Serialize + Send + Sync;
87}
88
89/// Default async transport backed by the crate `HttpClient`.
90#[cfg(any(target_arch = "wasm32", feature = "bitreq", feature = "reqwest"))]
91#[derive(Debug, Clone)]
92pub struct Async {
93    inner: HttpClient,
94}
95
96#[cfg(any(target_arch = "wasm32", feature = "bitreq", feature = "reqwest"))]
97impl Default for Async {
98    fn default() -> Self {
99        Self {
100            inner: HttpClient::builder()
101                .no_redirects()
102                .build()
103                .expect("default no-redirect client"),
104        }
105    }
106}
107
108#[cfg(any(target_arch = "wasm32", feature = "bitreq", feature = "reqwest"))]
109#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
110#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
111impl Transport for Async {
112    fn with_proxy(
113        &mut self,
114        proxy: Url,
115        host_matcher: Option<&str>,
116        accept_invalid_certs: bool,
117    ) -> Result<(), HttpError> {
118        let builder = HttpClientBuilder::default()
119            .no_redirects()
120            .danger_accept_invalid_certs(accept_invalid_certs);
121
122        let builder = match host_matcher {
123            Some(pattern) => builder.proxy_with_matcher(proxy, pattern)?,
124            None => builder.proxy(proxy),
125        };
126
127        self.inner = builder.build()?;
128        Ok(())
129    }
130
131    #[cfg(all(feature = "bip353", not(target_arch = "wasm32")))]
132    async fn resolve_dns_txt(&self, domain: &str) -> Result<Vec<String>, HttpError> {
133        crate::dns::resolve_dns_txt(domain).await
134    }
135
136    async fn http_get<R>(&self, url: Url, auth: Option<AuthToken>) -> Result<R, HttpError>
137    where
138        R: DeserializeOwned,
139    {
140        self.http_get_raw(url, auth).await?.json_or_status_error()
141    }
142
143    async fn http_get_raw(
144        &self,
145        url: Url,
146        auth: Option<AuthToken>,
147    ) -> Result<RawResponse, HttpError> {
148        let url_str = url.to_string();
149        let mut request = self.inner.get(&url_str);
150
151        if let Some(auth) = auth {
152            request = request.header(auth.header_key(), auth.to_string());
153        }
154
155        request.send().await
156    }
157
158    async fn http_post<P, R>(
159        &self,
160        url: Url,
161        auth_token: Option<AuthToken>,
162        payload: &P,
163    ) -> Result<R, HttpError>
164    where
165        P: Serialize + Send + Sync,
166        R: DeserializeOwned,
167    {
168        let url_str = url.to_string();
169        let mut request = self.inner.post(&url_str).json(payload);
170
171        if let Some(auth) = auth_token {
172            request = request.header(auth.header_key(), auth.to_string());
173        }
174
175        request.send_json::<R>().await
176    }
177
178    async fn http_post_form_raw<P>(
179        &self,
180        url: Url,
181        auth_token: Option<AuthToken>,
182        payload: &P,
183    ) -> Result<RawResponse, HttpError>
184    where
185        P: Serialize + Send + Sync,
186    {
187        let url_str = url.to_string();
188        let mut request = self.inner.post(&url_str).form(payload);
189
190        if let Some(auth) = auth_token {
191            request = request.header(auth.header_key(), auth.to_string());
192        }
193
194        request.send().await
195    }
196}
197
198#[cfg(all(
199    feature = "bitreq",
200    not(feature = "reqwest"),
201    not(target_arch = "wasm32")
202))]
203/// Bitreq-backed transport implementation.
204pub type BitreqTransport = Async;
205
206#[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
207/// Reqwest-backed transport implementation.
208pub type ReqwestTransport = Async;
209
210#[cfg(all(feature = "tor", not(target_arch = "wasm32")))]
211mod tor_transport;
212
213#[cfg(all(feature = "tor", not(target_arch = "wasm32")))]
214pub use self::tor_transport::TorAsync;