futu_backend/auth/
http_client.rs1use futu_core::error::{FutuError, Result};
11
12pub fn build_http_client(client_type: u8) -> Result<reqwest::Client> {
13 build_http_client_with_resolve(client_type, None)
14}
15
16pub(crate) fn build_http_client_with_resolve(
17 client_type: u8,
18 resolve: Option<(&str, std::net::SocketAddr)>,
19) -> Result<reqwest::Client> {
20 let _ = client_type;
39 let mut builder = reqwest::Client::builder().timeout(std::time::Duration::from_secs(15));
40 if let Some((domain, addr)) = resolve {
41 builder = builder.resolve(domain, addr);
42 }
43 builder
44 .build()
45 .map_err(|e| FutuError::Encryption(format!("http client: {e}")))
46}
47
48pub(crate) fn auth_http_default_headers(client_type: u8) -> Result<reqwest::header::HeaderMap> {
57 let mut default_headers = reqwest::header::HeaderMap::new();
58 default_headers.insert(
59 "X-Futu-Client-Type",
60 http_header_value("X-Futu-Client-Type", client_type)?,
61 );
62 default_headers.insert(
63 "X-Futu-Client-Version",
64 http_header_value(
65 "X-Futu-Client-Version",
66 crate::conn::BackendConn::CLIENT_VER_FTGTW,
67 )?,
68 );
69 default_headers.insert(
70 "X-Futu-Client-Lang",
71 reqwest::header::HeaderValue::from_static("sc"),
72 );
73 default_headers.insert(
74 "Content-Type",
75 reqwest::header::HeaderValue::from_static("application/json"),
76 );
77 Ok(default_headers)
78}
79
80pub(crate) fn auth_business_headers(
81 client_type: u8,
82 device_id: &str,
83) -> Result<reqwest::header::HeaderMap> {
84 let mut headers = reqwest::header::HeaderMap::new();
85 headers.insert(
86 reqwest::header::CONTENT_TYPE,
87 reqwest::header::HeaderValue::from_static("application/json"),
88 );
89 headers.insert(
90 reqwest::header::COOKIE,
91 http_header_value("Cookie", format!("device_id={device_id}"))?,
92 );
93 headers.insert(
94 reqwest::header::USER_AGENT,
95 http_header_value("User-Agent", opend_auth_user_agent(client_type))?,
96 );
97 AuthTraceHeaders::new().insert_http_headers(&mut headers)?;
98 Ok(headers)
99}
100
101#[derive(Debug, Clone)]
102pub(crate) struct AuthTraceHeaders {
103 trace_id: String,
104 parent_span_id: String,
105 span_id: String,
106}
107
108impl AuthTraceHeaders {
109 pub(crate) fn new() -> Self {
110 Self {
114 trace_id: hex::encode(rand::random::<[u8; 16]>()),
115 parent_span_id: hex::encode(rand::random::<[u8; 8]>()),
116 span_id: hex::encode(rand::random::<[u8; 8]>()),
117 }
118 }
119
120 pub(crate) fn entries(&self) -> [(&'static str, &str); 3] {
121 [
122 ("x-b3-traceid", self.trace_id.as_str()),
123 ("x-b3-parentspanid", self.parent_span_id.as_str()),
124 ("x-b3-spanid", self.span_id.as_str()),
125 ]
126 }
127
128 fn insert_http_headers(&self, headers: &mut reqwest::header::HeaderMap) -> Result<()> {
129 for (name, value) in self.entries() {
130 headers.insert(name, http_header_value(name, value)?);
131 }
132 Ok(())
133 }
134}
135
136pub(crate) fn opend_auth_user_agent(client_type: u8) -> String {
137 format!(
139 "ClientType/{client_type} ClientVersion/{} CliLang/zh-cn ClientHourClock/24 OsType/{} RequestSource/Http",
140 crate::conn::BackendConn::CLIENT_VER_FTGTW,
141 opend_user_agent_os_type(),
142 )
143}
144
145fn opend_user_agent_os_type() -> &'static str {
146 if cfg!(target_os = "macos") {
147 "11"
148 } else if cfg!(target_os = "linux") {
149 "14"
150 } else {
151 "10"
152 }
153}
154
155fn http_header_value(
156 name: &'static str,
157 value: impl std::fmt::Display,
158) -> Result<reqwest::header::HeaderValue> {
159 reqwest::header::HeaderValue::from_str(&value.to_string())
160 .map_err(|e| FutuError::Codec(format!("{name}: invalid header value: {e}")))
161}