Skip to main content

df_hyperliquid_core/client/native/
mod.rs

1//! HTTP and websocket for native programs: [`Native`] sends requests through
2//! reqwest on rustls, and [`clock`] is the system clock. Hand both to
3//! [`crate::client::Api`]:
4//!
5//! ```no_run
6//! let api = df_hyperliquid_core::client::Api::new(df_hyperliquid_core::client::native::Native::new(), df_hyperliquid_core::client::Network::Mainnet, df_hyperliquid_core::client::native::clock);
7//! ```
8//!
9//! A [`Native`] future must be polled inside a tokio runtime. Nothing here
10//! retries.
11
12pub mod socket;
13
14use crate::client::{Http, Method, Request, Response, TransportError};
15use std::time::{Duration, SystemTime, UNIX_EPOCH};
16
17pub use socket::{Event, Feed, FeedError};
18
19/// How long a request may take, connection and answer together, unless the
20/// program says otherwise.
21pub const TIMEOUT: Duration = Duration::from_secs(30);
22
23/// One HTTP client, cheap to clone: clones share their connections.
24#[derive(Debug, Clone)]
25pub struct Native {
26    client: reqwest::Client,
27}
28
29impl Native {
30    /// A client with [`TIMEOUT`].
31    #[must_use]
32    pub fn new() -> Self {
33        Native::with_timeout(TIMEOUT)
34    }
35
36    /// A client whose every request gives up after `timeout`. A request that
37    /// times out comes back as [`TransportError::Unreachable`]: an action may
38    /// still have landed.
39    #[must_use]
40    pub fn with_timeout(timeout: Duration) -> Self {
41        let client = reqwest::Client::builder().timeout(timeout).build().unwrap_or_else(|_| reqwest::Client::new());
42        Native { client }
43    }
44}
45
46impl Default for Native {
47    fn default() -> Self {
48        Native::new()
49    }
50}
51
52impl Http for Native {
53    async fn send(&self, request: Request) -> Result<Response, TransportError> {
54        let unreachable = |error: reqwest::Error| TransportError::Unreachable(error.without_url().to_string());
55        let mut outgoing = self.client.request(method(request.method), &request.url);
56        for (name, value) in &request.headers {
57            outgoing = outgoing.header(name, value);
58        }
59        if !request.body.is_empty() {
60            outgoing = outgoing.body(request.body);
61        }
62        let answer = outgoing.send().await.map_err(unreachable)?;
63        let status = answer.status().as_u16();
64        let headers = answer
65            .headers()
66            .iter()
67            .map(|(name, value)| (name.as_str().to_owned(), String::from_utf8_lossy(value.as_bytes()).into_owned()))
68            .collect();
69        let body = answer.bytes().await.map_err(unreachable)?.to_vec();
70        Ok(Response { status, headers, body })
71    }
72}
73
74fn method(method: Method) -> reqwest::Method {
75    match method {
76        Method::Get => reqwest::Method::GET,
77        Method::Post => reqwest::Method::POST,
78    }
79}
80
81/// Unix milliseconds from the system clock: the [`crate::client::Clock`] for a
82/// native program. A clock set before 1970 reads 0.
83#[must_use]
84pub fn clock() -> u64 {
85    SystemTime::now().duration_since(UNIX_EPOCH).map_or(0, |since| u64::try_from(since.as_millis()).unwrap_or(u64::MAX))
86}
87
88#[cfg(test)]
89#[path = "tests/http.rs"]
90mod tests;