ethers_providers/rpc/transports/
http.rs

1// Code adapted from: https://github.com/althea-net/guac_rs/tree/master/web3/src/jsonrpc
2
3use super::common::{Authorization, JsonRpcError, Request, Response};
4use crate::{errors::ProviderError, JsonRpcClient};
5use async_trait::async_trait;
6use reqwest::{header::HeaderValue, Client, Error as ReqwestError};
7use serde::{de::DeserializeOwned, Serialize};
8use std::{
9    str::FromStr,
10    sync::atomic::{AtomicU64, Ordering},
11};
12use thiserror::Error;
13use url::Url;
14
15/// A low-level JSON-RPC Client over HTTP.
16///
17/// # Example
18///
19/// ```no_run
20/// use ethers_core::types::U64;
21/// use ethers_providers::{JsonRpcClient, Http};
22/// use std::str::FromStr;
23///
24/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
25/// let provider = Http::from_str("http://localhost:8545")?;
26/// let block_number: U64 = provider.request("eth_blockNumber", ()).await?;
27/// # Ok(())
28/// # }
29/// ```
30#[derive(Debug)]
31pub struct Provider {
32    id: AtomicU64,
33    client: Client,
34    url: Url,
35}
36
37#[derive(Error, Debug)]
38/// Error thrown when sending an HTTP request
39pub enum ClientError {
40    /// Thrown if the request failed
41    #[error(transparent)]
42    ReqwestError(#[from] ReqwestError),
43    #[error(transparent)]
44    /// Thrown if the response could not be parsed
45    JsonRpcError(#[from] JsonRpcError),
46
47    #[error("Deserialization Error: {err}. Response: {text}")]
48    /// Serde JSON Error
49    SerdeJson {
50        /// Underlying error
51        err: serde_json::Error,
52        /// The contents of the HTTP response that could not be deserialized
53        text: String,
54    },
55}
56
57impl From<ClientError> for ProviderError {
58    fn from(src: ClientError) -> Self {
59        match src {
60            ClientError::ReqwestError(err) => ProviderError::HTTPError(err),
61            _ => ProviderError::JsonRpcClientError(Box::new(src)),
62        }
63    }
64}
65
66impl crate::RpcError for ClientError {
67    fn as_error_response(&self) -> Option<&super::JsonRpcError> {
68        if let ClientError::JsonRpcError(err) = self {
69            Some(err)
70        } else {
71            None
72        }
73    }
74
75    fn as_serde_error(&self) -> Option<&serde_json::Error> {
76        match self {
77            ClientError::SerdeJson { err, .. } => Some(err),
78            _ => None,
79        }
80    }
81}
82
83#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
84#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
85impl JsonRpcClient for Provider {
86    type Error = ClientError;
87
88    async fn request<T: Serialize + Send + Sync, R: DeserializeOwned>(
89        &self,
90        method: &str,
91        params: T,
92    ) -> Result<R, ClientError> {
93        let next_id = self.id.fetch_add(1, Ordering::SeqCst);
94        let payload = Request::new(next_id, method, params);
95
96        let res = self.client.post(self.url.as_ref()).json(&payload).send().await?;
97        let body = res.bytes().await?;
98
99        let raw = match serde_json::from_slice(&body) {
100            Ok(Response::Success { result, .. }) => result.to_owned(),
101            Ok(Response::Error { error, .. }) => return Err(error.into()),
102            Ok(_) => {
103                let err = ClientError::SerdeJson {
104                    err: serde::de::Error::custom("unexpected notification over HTTP transport"),
105                    text: String::from_utf8_lossy(&body).to_string(),
106                };
107                return Err(err)
108            }
109            Err(err) => {
110                return Err(ClientError::SerdeJson {
111                    err,
112                    text: String::from_utf8_lossy(&body).to_string(),
113                })
114            }
115        };
116
117        let res = serde_json::from_str(raw.get())
118            .map_err(|err| ClientError::SerdeJson { err, text: raw.to_string() })?;
119
120        Ok(res)
121    }
122}
123
124impl Provider {
125    /// Initializes a new HTTP Client
126    ///
127    /// # Example
128    ///
129    /// ```
130    /// use ethers_providers::Http;
131    /// use url::Url;
132    ///
133    /// let url = Url::parse("http://localhost:8545").unwrap();
134    /// let provider = Http::new(url);
135    /// ```
136    pub fn new(url: impl Into<Url>) -> Self {
137        Self::new_with_client(url, Client::new())
138    }
139
140    /// The Url to which requests are made
141    pub fn url(&self) -> &Url {
142        &self.url
143    }
144
145    /// Mutable access to the Url to which requests are made
146    pub fn url_mut(&mut self) -> &mut Url {
147        &mut self.url
148    }
149
150    /// Initializes a new HTTP Client with authentication
151    ///
152    /// # Example
153    ///
154    /// ```
155    /// use ethers_providers::{Authorization, Http};
156    /// use url::Url;
157    ///
158    /// let url = Url::parse("http://localhost:8545").unwrap();
159    /// let provider = Http::new_with_auth(url, Authorization::basic("admin", "good_password"));
160    /// ```
161    pub fn new_with_auth(
162        url: impl Into<Url>,
163        auth: Authorization,
164    ) -> Result<Self, HttpClientError> {
165        let mut auth_value = HeaderValue::from_str(&auth.to_string())?;
166        auth_value.set_sensitive(true);
167
168        let mut headers = reqwest::header::HeaderMap::new();
169        headers.insert(reqwest::header::AUTHORIZATION, auth_value);
170
171        let client = Client::builder().default_headers(headers).build()?;
172
173        Ok(Self::new_with_client(url, client))
174    }
175
176    /// Allows to customize the provider by providing your own http client
177    ///
178    /// # Example
179    ///
180    /// ```
181    /// use ethers_providers::Http;
182    /// use url::Url;
183    ///
184    /// let url = Url::parse("http://localhost:8545").unwrap();
185    /// let client = reqwest::Client::builder().build().unwrap();
186    /// let provider = Http::new_with_client(url, client);
187    /// ```
188    pub fn new_with_client(url: impl Into<Url>, client: reqwest::Client) -> Self {
189        Self { id: AtomicU64::new(1), client, url: url.into() }
190    }
191}
192
193impl FromStr for Provider {
194    type Err = url::ParseError;
195
196    fn from_str(src: &str) -> Result<Self, Self::Err> {
197        let url = Url::parse(src)?;
198        Ok(Provider::new(url))
199    }
200}
201
202impl Clone for Provider {
203    fn clone(&self) -> Self {
204        Self { id: AtomicU64::new(1), client: self.client.clone(), url: self.url.clone() }
205    }
206}
207
208#[derive(Error, Debug)]
209/// Error thrown when dealing with Http clients
210pub enum HttpClientError {
211    /// Thrown if unable to build headers for client
212    #[error(transparent)]
213    InvalidHeader(#[from] http::header::InvalidHeaderValue),
214
215    /// Thrown if unable to build client
216    #[error(transparent)]
217    ClientBuild(#[from] reqwest::Error),
218}