Skip to main content

longbridge_httpcli/
client.rs

1use std::sync::Arc;
2
3use longbridge_geo::DcRegion;
4use reqwest::{
5    Client, Method,
6    header::{HeaderMap, HeaderName, HeaderValue},
7};
8use serde::Deserialize;
9
10use crate::{
11    AuthConfig, HttpClientConfig, HttpClientError, HttpClientResult, Json, RequestBuilder,
12};
13
14/// Longbridge HTTP client
15#[derive(Clone)]
16pub struct HttpClient {
17    pub(crate) http_cli: Client,
18    pub(crate) config: Arc<HttpClientConfig>,
19    pub(crate) default_headers: HeaderMap,
20}
21
22impl HttpClient {
23    /// Create a new `HttpClient`
24    pub fn new(config: HttpClientConfig) -> Self {
25        Self {
26            http_cli: Client::new(),
27            config: Arc::new(config),
28            default_headers: HeaderMap::new(),
29        }
30    }
31
32    /// Set the default header
33    pub fn header<K, V>(mut self, key: K, value: V) -> Self
34    where
35        K: TryInto<HeaderName>,
36        V: TryInto<HeaderValue>,
37    {
38        let key = key.try_into();
39        let value = value.try_into();
40        if let (Ok(key), Ok(value)) = (key, value) {
41            self.default_headers.insert(key, value);
42        }
43        self
44    }
45
46    /// The data-center region (`us`/`ap`) derived from this client's auth
47    /// credentials. Used by non-HTTP call sites (e.g. WebSocket quote commands)
48    /// to apply the same AP-only routing guard the HTTP path enforces.
49    pub async fn dc_region(&self) -> DcRegion {
50        match &self.config.auth {
51            AuthConfig::ApiKey {
52                app_key,
53                app_secret,
54                access_token,
55            } => DcRegion::from_credentials(&[app_key, access_token, app_secret]),
56            AuthConfig::OAuth(oauth) => oauth
57                .access_token()
58                .await
59                .map(|token| DcRegion::from_credential(&token))
60                .unwrap_or(DcRegion::Ap),
61        }
62    }
63
64    /// Create a new request builder
65    #[inline]
66    pub fn request(
67        &self,
68        method: Method,
69        path: impl Into<String>,
70    ) -> RequestBuilder<'_, (), (), ()> {
71        RequestBuilder::new(self, method, path)
72    }
73
74    /// Get the socket OTP(One Time Password)
75    ///
76    /// Reference: <https://open.longbridge.com/en/docs/socket-token-api>
77    pub async fn get_otp(&self) -> HttpClientResult<String> {
78        #[derive(Debug, Deserialize)]
79        struct Response {
80            otp: String,
81            limit: i32,
82            online: i32,
83        }
84
85        let resp = self
86            .request(Method::GET, "/v1/socket/token")
87            .response::<Json<Response>>()
88            .send()
89            .await?
90            .0;
91        tracing::info!(limit = resp.limit, online = resp.online, "create otp");
92
93        if resp.online >= resp.limit {
94            return Err(HttpClientError::ConnectionLimitExceeded {
95                limit: resp.limit,
96                online: resp.online,
97            });
98        }
99
100        Ok(resp.otp)
101    }
102}