fedimint_connectors/
http.rs

1use std::sync::Arc;
2
3use anyhow::anyhow;
4use fedimint_core::util::SafeUrl;
5use fedimint_core::{apply, async_trait_maybe_send};
6use reqwest::{Method, StatusCode};
7use serde_json::Value;
8
9use crate::error::ServerError;
10use crate::{
11    DynGatewayConnection, DynGuaridianConnection, IConnection, IGatewayConnection, ServerResult,
12};
13
14#[derive(Clone, Debug, Default)]
15pub(crate) struct HttpConnector {
16    client: Arc<reqwest::Client>,
17}
18
19#[async_trait::async_trait]
20impl crate::Connector for HttpConnector {
21    async fn connect_guardian(
22        &self,
23        _url: &SafeUrl,
24        _api_secret: Option<&str>,
25    ) -> ServerResult<DynGuaridianConnection> {
26        Err(ServerError::InternalClientError(anyhow!(
27            "Unsupported transport mechanism"
28        )))
29    }
30
31    async fn connect_gateway(&self, url: &SafeUrl) -> anyhow::Result<DynGatewayConnection> {
32        let http_connection = HttpConnection {
33            client: self.client.clone(),
34            base_url: url.clone(),
35        };
36
37        Ok(IGatewayConnection::into_dyn(http_connection))
38    }
39}
40
41#[derive(Debug)]
42pub(crate) struct HttpConnection {
43    client: Arc<reqwest::Client>,
44    base_url: SafeUrl,
45}
46
47#[apply(async_trait_maybe_send!)]
48impl IConnection for HttpConnection {
49    async fn await_disconnection(&self) {}
50
51    fn is_connected(&self) -> bool {
52        // `reqwest::Client` already implemented connection pooling. So instead of
53        // keeping this `HttpConnection` alive, we always return false here and
54        // force the `ConnectionRegistry` to re-create the connection. `HttpConnector`
55        // manages the `reqwest::Client` lifetime, so the same underlying TCP
56        // connection will be used for subsequent requests.
57        false
58    }
59}
60
61#[apply(async_trait_maybe_send!)]
62impl IGatewayConnection for HttpConnection {
63    async fn request(
64        &self,
65        password: Option<String>,
66        method: Method,
67        route: &str,
68        payload: Option<Value>,
69    ) -> ServerResult<Value> {
70        let url = self.base_url.join(route).expect("Invalid base url");
71        let mut builder = self.client.request(method, url.clone().to_unsafe());
72        if let Some(password) = password.clone() {
73            builder = builder.bearer_auth(password);
74        }
75        if let Some(payload) = payload {
76            builder = builder.json(&payload);
77        }
78
79        let response = builder
80            .send()
81            .await
82            .map_err(|e| ServerError::ServerError(e.into()))?;
83
84        match response.status() {
85            StatusCode::OK => Ok(response
86                .json::<Value>()
87                .await
88                .map_err(|e| ServerError::InvalidResponse(e.into()))?),
89            status => Err(ServerError::InvalidRequest(anyhow::anyhow!(
90                "HTTP request returned unexpected status: {status}"
91            ))),
92        }
93    }
94}