starknet_rust_providers/jsonrpc/transports/
http.rs

1use async_trait::async_trait;
2use log::trace;
3use reqwest::{Client, Url};
4use serde::{de::DeserializeOwned, Serialize};
5
6use crate::{
7    jsonrpc::{transports::JsonRpcTransport, JsonRpcMethod, JsonRpcResponse},
8    ProviderRequestData,
9};
10
11/// A [`JsonRpcTransport`] implementation that uses HTTP connections.
12#[derive(Debug, Clone)]
13pub struct HttpTransport {
14    client: Client,
15    url: Url,
16    headers: Vec<(String, String)>,
17}
18
19/// Errors using [`HttpTransport`].
20#[derive(Debug, thiserror::Error)]
21#[error(transparent)]
22pub enum HttpTransportError {
23    /// HTTP-related errors.
24    Reqwest(reqwest::Error),
25    /// JSON serialization/deserialization errors.
26    Json(serde_json::Error),
27    /// Unexpected response ID.
28    #[error("unexpected response ID: {0}")]
29    UnexpectedResponseId(u64),
30}
31
32#[derive(Debug, Serialize)]
33struct JsonRpcRequest<T> {
34    id: u64,
35    jsonrpc: &'static str,
36    method: JsonRpcMethod,
37    params: T,
38}
39
40impl HttpTransport {
41    /// Constructs [`HttpTransport`] from a JSON-RPC server URL, using default HTTP client settings.
42    ///
43    /// To use custom HTTP settings (e.g. proxy, timeout), use
44    /// [`new_with_client`](fn.new_with_client) instead.
45    pub fn new(url: impl Into<Url>) -> Self {
46        Self::new_with_client(url, Client::new())
47    }
48
49    /// Constructs [`HttpTransport`] from a JSON-RPC server URL and a custom `reqwest` client.
50    pub fn new_with_client(url: impl Into<Url>, client: Client) -> Self {
51        Self {
52            client,
53            url: url.into(),
54            headers: vec![],
55        }
56    }
57
58    /// Consumes the current [`HttpTransport`] instance and returns a new one with the header
59    /// appended. Same as calling [`add_header`](fn.add_header).
60    pub fn with_header(self, name: String, value: String) -> Self {
61        let mut headers = self.headers;
62        headers.push((name, value));
63
64        Self {
65            client: self.client,
66            url: self.url,
67            headers,
68        }
69    }
70
71    /// Adds a custom HTTP header to be sent for requests.
72    pub fn add_header(&mut self, name: String, value: String) {
73        self.headers.push((name, value))
74    }
75}
76
77#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
78#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
79impl JsonRpcTransport for HttpTransport {
80    type Error = HttpTransportError;
81
82    async fn send_request<P, R>(
83        &self,
84        method: JsonRpcMethod,
85        params: P,
86    ) -> Result<JsonRpcResponse<R>, Self::Error>
87    where
88        P: Serialize + Send,
89        R: DeserializeOwned + Send,
90    {
91        let request_body = JsonRpcRequest {
92            id: 1,
93            jsonrpc: "2.0",
94            method,
95            params,
96        };
97
98        let request_body = serde_json::to_string(&request_body).map_err(Self::Error::Json)?;
99        trace!("Sending request via JSON-RPC: {}", request_body);
100
101        let mut request = self
102            .client
103            .post(self.url.clone())
104            .body(request_body)
105            .header("Content-Type", "application/json");
106        for (name, value) in &self.headers {
107            request = request.header(name, value);
108        }
109
110        let response = request.send().await.map_err(Self::Error::Reqwest)?;
111
112        let response_body = response.text().await.map_err(Self::Error::Reqwest)?;
113        trace!("Response from JSON-RPC: {}", response_body);
114
115        let parsed_response = serde_json::from_str(&response_body).map_err(Self::Error::Json)?;
116
117        Ok(parsed_response)
118    }
119
120    async fn send_requests<R>(
121        &self,
122        requests: R,
123    ) -> Result<Vec<JsonRpcResponse<serde_json::Value>>, Self::Error>
124    where
125        R: AsRef<[ProviderRequestData]> + Send + Sync,
126    {
127        let request_bodies = requests
128            .as_ref()
129            .iter()
130            .enumerate()
131            .map(|(ind, request)| JsonRpcRequest {
132                id: ind as u64,
133                jsonrpc: "2.0",
134                method: request.jsonrpc_method(),
135                params: request,
136            })
137            .collect::<Vec<_>>();
138
139        let request_count = request_bodies.len();
140
141        let request_body = serde_json::to_string(&request_bodies).map_err(Self::Error::Json)?;
142        trace!("Sending request via JSON-RPC: {}", request_body);
143
144        let mut request = self
145            .client
146            .post(self.url.clone())
147            .body(request_body)
148            .header("Content-Type", "application/json");
149        for (name, value) in &self.headers {
150            request = request.header(name, value);
151        }
152
153        let response = request.send().await.map_err(Self::Error::Reqwest)?;
154
155        let response_body = response.text().await.map_err(Self::Error::Reqwest)?;
156        trace!("Response from JSON-RPC: {}", response_body);
157
158        let parsed_response: Vec<JsonRpcResponse<serde_json::Value>> =
159            serde_json::from_str(&response_body).map_err(Self::Error::Json)?;
160
161        let mut responses: Vec<Option<JsonRpcResponse<serde_json::Value>>> = vec![];
162        responses.resize(request_bodies.len(), None);
163
164        // Re-order the responses as servers do not maintain order.
165        for response_item in parsed_response {
166            let id = match &response_item {
167                JsonRpcResponse::Success { id, .. } | JsonRpcResponse::Error { id, .. } => {
168                    *id as usize
169                }
170            };
171
172            if id >= request_count {
173                return Err(HttpTransportError::UnexpectedResponseId(id as u64));
174            }
175
176            responses[id] = Some(response_item);
177        }
178
179        let responses = responses.into_iter().flatten().collect::<Vec<_>>();
180        Ok(responses)
181    }
182}