noosphere_ipfs/client/
gateway.rs1use super::{IpfsClient, IpfsClientAsyncReadSendSync};
2use anyhow::Result;
3use async_trait::async_trait;
4use cid::Cid;
5use noosphere_common::ConditionalSend;
6use reqwest::Client;
7use reqwest::StatusCode;
8use std::str::FromStr;
9use url::Url;
10
11#[derive(Clone, Debug)]
15pub struct GatewayClient {
16 client: Client,
17 api_url: Url,
18}
19
20impl GatewayClient {
21 pub fn new(api_url: Url) -> Self {
22 let client = Client::new();
23 GatewayClient { client, api_url }
24 }
25
26 pub(crate) fn make_block_url(&self, cid: &Cid) -> Url {
27 let mut url = self.api_url.clone();
28
29 if let Some(domain) = url.domain() {
30 let mut parts = domain.split('.');
31
32 if let Some(fragment) = parts.next() {
33 if Cid::from_str(fragment).is_ok() {
34 let upper_domain = parts
35 .map(|part| part.to_string())
36 .collect::<Vec<String>>()
37 .join(".");
38
39 let mut host = format!("{cid}.{upper_domain}");
40
41 if let Some(port) = url.port() {
42 host = format!("{host}:{port}");
43 }
44
45 if let Ok(()) = url.set_host(Some(&host)) {
46 return url;
47 }
48 }
49 }
50 }
51
52 url.set_path(&format!("/ipfs/{cid}"));
53 url
54 }
55}
56
57#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
58#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
59impl IpfsClient for GatewayClient {
60 async fn pin_blocks<'a, I>(&self, _cids: I) -> Result<()>
61 where
62 I: IntoIterator<Item = &'a Cid> + ConditionalSend + std::fmt::Debug,
63 I::IntoIter: ConditionalSend,
64 {
65 unimplemented!("IPFS HTTP Gateway does not have this capability.");
66 }
67
68 async fn block_is_pinned(&self, _cid: &Cid) -> Result<bool> {
69 unimplemented!("IPFS HTTP Gateway does not have this capability.");
70 }
71
72 async fn server_identity(&self) -> Result<String> {
73 unimplemented!("IPFS HTTP Gateway does not have this capability.");
74 }
75
76 async fn syndicate_blocks<R>(&self, _car: R) -> Result<()>
77 where
78 R: IpfsClientAsyncReadSendSync,
79 {
80 unimplemented!("IPFS HTTP Gateway does not have this capability.");
81 }
82
83 async fn put_block(&mut self, _cid: &Cid, _block: &[u8]) -> Result<()> {
84 unimplemented!("IPFS HTTP Gateway does not have this capability.");
85 }
86
87 async fn get_block(&self, cid: &Cid) -> Result<Option<Vec<u8>>> {
88 let api_url = self.make_block_url(cid);
89 let response = self
90 .client
91 .get(api_url)
92 .header("Accept", "application/vnd.ipld.raw")
93 .send()
94 .await?;
95
96 match response.status() {
97 StatusCode::OK => Ok(Some(response.bytes().await?.into())),
98 _ => Ok(None),
99 }
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn it_can_derive_a_block_url_for_subdomain_gateways() {
109 let gateway_url = Url::from_str(
110 "https://bafybeieh53mh2gt4khnrixfro7wvbvtrux4247cfwse642e36z67medkzq.ipfs.noo.pub",
111 )
112 .unwrap();
113 let test_cid =
114 Cid::from_str("bafy2bzacecsjls67zqx25dcvbu6p4z4rsdkm2k6hanhd5qowrvwmhtov2sjpo")
115 .unwrap();
116 let client = GatewayClient::new(gateway_url);
117 let derived_url = client.make_block_url(&test_cid);
118 let expected_url = Url::from_str(
119 "https://bafy2bzacecsjls67zqx25dcvbu6p4z4rsdkm2k6hanhd5qowrvwmhtov2sjpo.ipfs.noo.pub",
120 )
121 .unwrap();
122
123 assert_eq!(derived_url, expected_url);
124 }
125}