noosphere_ns/server/
client.rs

1use crate::server::routes::Route;
2use crate::{dht_client::DhtClient, Multiaddr, NetworkInfo, Peer, PeerId};
3use anyhow::{anyhow, Result};
4use async_trait::async_trait;
5use noosphere_core::data::{Did, LinkRecord};
6use reqwest::Body;
7use url::Url;
8
9pub struct HttpClient {
10    api_base: Url,
11    client: reqwest::Client,
12    peer_id: PeerId,
13}
14
15impl HttpClient {
16    pub async fn new(api_base: Url) -> Result<Self> {
17        let client = reqwest::Client::new();
18        let mut url = api_base.clone();
19        url.set_path(&Route::GetPeerId.to_string());
20        let peer_id = client.get(url).send().await?.json().await?;
21
22        Ok(HttpClient {
23            api_base,
24            client,
25            peer_id,
26        })
27    }
28}
29
30#[async_trait]
31impl DhtClient for HttpClient {
32    /// Returns current network information for this node.
33    async fn network_info(&self) -> Result<NetworkInfo> {
34        let mut url = self.api_base.clone();
35        url.set_path(&Route::NetworkInfo.to_string());
36        Ok(self.client.get(url).send().await?.json().await?)
37    }
38
39    /// Returns the current peers of this node.
40    fn peer_id(&self) -> &PeerId {
41        &self.peer_id
42    }
43
44    /// Returns the current peers of this node.
45    async fn peers(&self) -> Result<Vec<Peer>> {
46        let mut url = self.api_base.clone();
47        url.set_path(&Route::GetPeers.to_string());
48        Ok(self.client.get(url).send().await?.json().await?)
49    }
50
51    /// Adds peers to connect to. Unless bootstrapping a network, at least one
52    /// peer is needed.
53    async fn add_peers(&self, peers: Vec<Multiaddr>) -> Result<()> {
54        if peers.len() > 1 {
55            return Err(anyhow!("Only one peer may be added at a time over HTTP."));
56        }
57        let address = peers.first().unwrap();
58        let mut url = self.api_base.clone();
59        let path = Route::AddPeers
60            .to_string()
61            .replace("*addr", &address.to_string());
62        url.set_path(&path);
63        Ok(self.client.post(url).send().await?.json().await?)
64    }
65
66    /// Starts listening for connections on provided address.
67    async fn listen(&self, listening_address: Multiaddr) -> Result<Multiaddr> {
68        let mut url = self.api_base.clone();
69        let path = Route::Listen
70            .to_string()
71            .replace("*addr", &listening_address.to_string());
72        url.set_path(&path);
73        Ok(self.client.post(url).send().await?.json().await?)
74    }
75
76    /// Stops listening for connections.
77    async fn stop_listening(&self) -> Result<()> {
78        let mut url = self.api_base.clone();
79        let path = Route::StopListening.to_string();
80        url.set_path(&path);
81        Ok(self.client.delete(url).send().await?.json().await?)
82    }
83
84    /// Begins the bootstrap process on this node.
85    async fn bootstrap(&self) -> Result<()> {
86        let mut url = self.api_base.clone();
87        url.set_path(&Route::Bootstrap.to_string());
88        Ok(self.client.post(url).send().await?.json().await?)
89    }
90
91    /// Returns the listening addresses of this node.
92    async fn address(&self) -> Result<Option<Multiaddr>> {
93        let mut url = self.api_base.clone();
94        url.set_path(&Route::Address.to_string());
95        Ok(self.client.get(url).send().await?.json().await?)
96    }
97
98    async fn get_record(&self, identity: &Did) -> Result<Option<LinkRecord>> {
99        let mut url = self.api_base.clone();
100        let path = Route::GetRecord
101            .to_string()
102            .replace(":identity", identity.into());
103        url.set_path(&path);
104        Ok(self.client.get(url).send().await?.json().await?)
105    }
106
107    async fn put_record(&self, record: LinkRecord, quorum: usize) -> Result<()> {
108        let mut url = self.api_base.clone();
109        url.set_path(&Route::PostRecord.to_string());
110        url.set_query(Some(&format!("quorum={quorum}")));
111        let json_data = serde_json::to_string(&record)?;
112
113        let res = self
114            .client
115            .post(url)
116            .header("Content-Type", "application/json")
117            .body(Body::from(json_data))
118            .send()
119            .await?
120            .json()
121            .await?;
122        Ok(res)
123    }
124}
125
126#[cfg(test)]
127mod test {
128    use super::*;
129    use crate::dht_client_tests;
130    use crate::{server::ApiServer, utils::wait_for_peers};
131    use crate::{NameSystem, NameSystemBuilder};
132    use noosphere_core::authority::generate_ed25519_key;
133    use noosphere_storage::{MemoryStorage, SphereDb};
134    use std::net::TcpListener;
135    use std::sync::Arc;
136    use tokio::sync::Mutex;
137
138    /// This struct is used to persist non-Client objects, like
139    /// the name system and/or server, through the duration
140    /// of each test.
141    struct DataPlaceholder {
142        _server: ApiServer,
143        _bootstrap: NameSystem,
144    }
145
146    async fn before_each() -> Result<(DataPlaceholder, Arc<Mutex<HttpClient>>)> {
147        let (bootstrap, bootstrap_address) = {
148            let key_material = generate_ed25519_key();
149            let store = SphereDb::new(&MemoryStorage::default()).await.unwrap();
150            let ns = NameSystemBuilder::default()
151                .ucan_store(store)
152                .key_material(&key_material)
153                .listening_port(0)
154                .use_test_config()
155                .build()
156                .await
157                .unwrap();
158            ns.bootstrap().await.unwrap();
159            let address = ns.address().await?.unwrap();
160            (ns, vec![address])
161        };
162
163        let api_listener = TcpListener::bind("127.0.0.1:0")?;
164        let api_port = api_listener.local_addr().unwrap().port();
165        let api_url = Url::parse(&format!("http://127.0.0.1:{}", api_port))?;
166        let key_material = generate_ed25519_key();
167        let store = SphereDb::new(&MemoryStorage::default()).await.unwrap();
168
169        let ns = NameSystemBuilder::default()
170            .ucan_store(store)
171            .key_material(&key_material)
172            .bootstrap_peers(&bootstrap_address)
173            .use_test_config()
174            .build()
175            .await
176            .unwrap();
177
178        let ns = Arc::new(ns);
179        let server = ApiServer::serve(ns, api_listener);
180        let data = DataPlaceholder {
181            _server: server,
182            _bootstrap: bootstrap,
183        };
184
185        let client = {
186            let client = HttpClient::new(api_url).await?;
187            // Bootstrap via the HTTP client to test the route
188            client.bootstrap().await?;
189            wait_for_peers::<HttpClient>(&client, 1).await?;
190            Arc::new(Mutex::new(client))
191        };
192
193        Ok((data, client))
194    }
195
196    dht_client_tests!(HttpClient, before_each, DataPlaceholder);
197}