1mod h1;
5mod h2;
6#[cfg(feature = "http3")]
7mod h3;
8
9use std::sync::Arc;
10use std::time::Duration;
11
12#[cfg(feature = "tokio")]
13use bytes::Bytes;
14use http_body_util::BodyExt;
15#[cfg(feature = "tokio")]
16use http_body_util::Full;
17use hyper::StatusCode;
18use log::info;
19use serde::{de::DeserializeOwned, Serialize};
20use serde_json::json;
21
22#[cfg(feature = "tokio")]
23use hyper_util::client::legacy::{connect::HttpConnector, Client as HyperClient};
24
25#[cfg(feature = "tokio")]
26use crate::hyper_exec::HyperExecutor;
27
28use karyon_core::{
29 async_util::{timeout, TaskGroup},
30 util::random_32,
31};
32
33#[cfg(feature = "http3")]
34use karyon_core::async_util::TaskResult;
35
36use karyon_net::Endpoint;
37
38use crate::{
39 client::{Client, ClientBackend, ClientConfig, RequestID, WsCodec},
40 codec::{decode_body, JsonRpcCodec, DEFAULT_CONTENT_TYPE},
41 error::{Error, Result},
42 message::{self, SubscriptionID},
43};
44
45#[cfg(feature = "http3")]
46use std::collections::HashMap;
47
48#[cfg(feature = "http3")]
49use parking_lot::Mutex;
50
51#[cfg(feature = "http3")]
52use crate::client::subscriptions::{Subscription, Subscriptions};
53
54#[derive(Clone, Debug)]
57pub struct HttpConfig {
58 pub http2: bool,
62 pub host: Option<String>,
64 pub content_type: String,
67}
68
69impl Default for HttpConfig {
70 fn default() -> Self {
71 Self {
72 http2: false,
73 host: None,
74 content_type: DEFAULT_CONTENT_TYPE.to_string(),
75 }
76 }
77}
78
79pub(crate) struct HttpClientBackend {
81 inner: HttpTransport,
82 config: HttpConfig,
83 task_group: Arc<TaskGroup>,
86}
87
88enum HttpTransport {
94 #[cfg(feature = "smol")]
95 Smol { endpoint: Endpoint },
96 #[cfg(feature = "tokio")]
97 Tokio {
98 endpoint: Endpoint,
99 client: Box<HyperClient<HttpConnector, Full<Bytes>>>,
100 },
101 H2 {
102 endpoint: Endpoint,
103 sender: h2::H2SendRequest,
104 },
105 #[cfg(feature = "http3")]
106 H3 { send_request: h3::H3SendRequest },
107}
108
109impl HttpClientBackend {
110 pub(crate) fn new(
112 endpoint: &Endpoint,
113 config: &HttpConfig,
114 task_group: Arc<TaskGroup>,
115 ) -> Result<Self> {
116 #[cfg(feature = "smol")]
117 let inner = HttpTransport::Smol {
118 endpoint: endpoint.clone(),
119 };
120
121 #[cfg(feature = "tokio")]
122 let inner = HttpTransport::Tokio {
123 endpoint: endpoint.clone(),
124 client: Box::new(
125 HyperClient::builder(HyperExecutor::new(task_group.clone())).build_http(),
126 ),
127 };
128
129 Ok(Self {
130 inner,
131 config: config.clone(),
132 task_group,
133 })
134 }
135
136 pub(crate) async fn new_h2(
138 endpoint: &Endpoint,
139 config: &HttpConfig,
140 task_group: Arc<TaskGroup>,
141 ) -> Result<Self> {
142 let sender = h2::connect(endpoint, &task_group).await?;
143 Ok(Self {
144 inner: HttpTransport::H2 {
145 endpoint: endpoint.clone(),
146 sender,
147 },
148 config: config.clone(),
149 task_group,
150 })
151 }
152
153 #[cfg(feature = "http3")]
156 pub(crate) async fn new_h3(
157 endpoint: &Endpoint,
158 quic_config: &karyon_net::quic::ClientQuicConfig,
159 config: &HttpConfig,
160 task_group: Arc<TaskGroup>,
161 ) -> Result<Self> {
162 let send_request = h3::connect(endpoint, quic_config, task_group.clone()).await?;
163 Ok(Self {
164 inner: HttpTransport::H3 { send_request },
165 config: config.clone(),
166 task_group,
167 })
168 }
169
170 pub(crate) async fn send_request<C: JsonRpcCodec>(
172 &self,
173 msg: serde_json::Value,
174 codec: &C,
175 ) -> Result<message::Response> {
176 let _ = &self.task_group;
177 let config = &self.config;
178 match &self.inner {
179 #[cfg(feature = "smol")]
180 HttpTransport::Smol { endpoint } => {
181 h1::send(endpoint, config, msg, &self.task_group, codec).await
182 }
183 #[cfg(feature = "tokio")]
184 HttpTransport::Tokio { endpoint, client } => {
185 h1::send(client, endpoint, config, msg, codec).await
186 }
187 HttpTransport::H2 { endpoint, sender } => {
188 h2::send(sender, endpoint, config, msg, codec).await
189 }
190 #[cfg(feature = "http3")]
191 HttpTransport::H3 { send_request } => {
192 h3::send(send_request.clone(), config, msg, codec).await
193 }
194 }
195 }
196
197 #[cfg(feature = "http3")]
199 pub(crate) async fn subscribe_h3<C: JsonRpcCodec>(
200 &self,
201 msg: serde_json::Value,
202 codec: &C,
203 ) -> Result<(message::Response, h3::H3SendStream, h3::H3Reader<C>)> {
204 let HttpTransport::H3 { send_request } = &self.inner else {
205 return Err(Error::UnsupportedProtocol(
206 "subscribe_h3 requires HTTP/3".into(),
207 ));
208 };
209 h3::subscribe(send_request.clone(), &self.config, msg, codec).await
210 }
211}
212
213pub(super) async fn parse_response<B, C: JsonRpcCodec>(
215 response: hyper::Response<B>,
216 codec: &C,
217) -> Result<message::Response>
218where
219 B: hyper::body::Body,
220 B::Error: std::fmt::Display,
221{
222 if response.status() != StatusCode::OK {
223 return Err(Error::HttpError(format!(
224 "HTTP error: {}",
225 response.status()
226 )));
227 }
228
229 let body = response
230 .collect()
231 .await
232 .map_err(|e| Error::HttpError(e.to_string()))?
233 .to_bytes();
234
235 let rpc_response: message::Response = serde_json::from_value(decode_body(codec, &body)?)?;
236 Ok(rpc_response)
237}
238
239pub(super) async fn build_backend(
242 config: &ClientConfig,
243 task_group: Arc<TaskGroup>,
244) -> Result<ClientBackend> {
245 #[cfg(feature = "http3")]
246 if let Some(qc) = config.quic_config.as_ref() {
247 let backend =
248 HttpClientBackend::new_h3(&config.endpoint, qc, &config.http_config, task_group)
249 .await?;
250 return Ok(http_client_backend(config, backend));
251 }
252
253 let http_backend = if config.http_config.http2 {
254 HttpClientBackend::new_h2(&config.endpoint, &config.http_config, task_group).await?
255 } else {
256 HttpClientBackend::new(&config.endpoint, &config.http_config, task_group)?
257 };
258 Ok(http_client_backend(config, http_backend))
259}
260
261fn http_client_backend(config: &ClientConfig, http_backend: HttpClientBackend) -> ClientBackend {
262 info!("HTTP client configured for endpoint: {}", config.endpoint);
263
264 ClientBackend::Http {
265 http_backend: Box::new(http_backend),
266 #[cfg(feature = "http3")]
267 subscriptions: Subscriptions::new(config.subscription_buffer_size),
268 #[cfg(feature = "http3")]
269 unsub_chans: Mutex::new(HashMap::new()),
270 }
271}
272
273impl<B, W> Client<B, W>
276where
277 B: JsonRpcCodec,
278 W: WsCodec,
279{
280 pub(super) async fn http_call<T: Serialize + DeserializeOwned>(
281 &self,
282 http_backend: &HttpClientBackend,
283 method: &str,
284 params: T,
285 ) -> Result<message::Response> {
286 let id: RequestID = random_32();
287 let request = message::Request {
288 jsonrpc: message::JSONRPC_VERSION.to_string(),
289 id: json!(id),
290 method: method.to_string(),
291 params: Some(json!(params)),
292 };
293 let msg = serde_json::to_value(request)?;
294
295 let response = match self.config.timeout {
296 Some(t) => {
297 timeout(
298 Duration::from_millis(t),
299 http_backend.send_request(msg, &self.codec),
300 )
301 .await?
302 }
303 None => http_backend.send_request(msg, &self.codec).await,
304 }?;
305
306 Ok(response)
307 }
308
309 #[cfg(feature = "http3")]
311 pub(super) async fn h3_subscribe<T: Serialize + DeserializeOwned>(
312 self: &Arc<Self>,
313 http_backend: &HttpClientBackend,
314 method: &str,
315 params: T,
316 ) -> Result<Arc<Subscription>> {
317 let ClientBackend::Http {
318 subscriptions,
319 unsub_chans,
320 ..
321 } = &self.backend
322 else {
323 return Err(Error::InvalidState("not in HTTP mode".into()));
324 };
325
326 let id: RequestID = random_32();
327 let request = message::Request {
328 jsonrpc: message::JSONRPC_VERSION.to_string(),
329 id: json!(id),
330 method: method.to_string(),
331 params: Some(json!(params)),
332 };
333 let msg = serde_json::to_value(request)?;
334
335 let (response, send, reader) = http_backend.subscribe_h3(msg, &self.codec).await?;
336
337 if let Some(error) = response.error {
338 return Err(Error::SubscribeError(error.code, error.message));
339 }
340
341 let sub_id = match response.result {
342 Some(result) => serde_json::from_value::<SubscriptionID>(result)?,
343 None => return Err(Error::InvalidMsg("Invalid subscription id".into())),
344 };
345
346 let (unsub_tx, unsub_rx) = async_channel::bounded::<serde_json::Value>(1);
347 unsub_chans.lock().insert(sub_id, unsub_tx);
348
349 let sub = subscriptions.subscribe(sub_id).await;
350
351 self.task_group.spawn_then(
352 h3::subscription_task(
353 send,
354 reader,
355 unsub_rx,
356 sub.clone(),
357 self.config.notification_decoder,
358 ),
359 |res: TaskResult<Result<()>>| async move {
360 log::debug!("H3 subscription task ended: {res}");
361 },
362 );
363
364 Ok(sub)
365 }
366
367 #[cfg(feature = "http3")]
370 pub(super) async fn h3_unsubscribe(&self, method: &str, sub_id: SubscriptionID) -> Result<()> {
371 let ClientBackend::Http {
372 subscriptions,
373 unsub_chans,
374 ..
375 } = &self.backend
376 else {
377 return Err(Error::InvalidState("not in HTTP mode".into()));
378 };
379
380 let ch = unsub_chans.lock().remove(&sub_id);
382 if let Some(ch) = ch {
383 let request = message::Request {
384 jsonrpc: message::JSONRPC_VERSION.to_string(),
385 id: json!(random_32()),
386 method: method.to_string(),
387 params: Some(json!(sub_id)),
388 };
389 let _ = ch.send(serde_json::to_value(request)?).await;
390 }
391 subscriptions.unsubscribe(&sub_id).await;
392 Ok(())
393 }
394}