1use std::cell::RefCell;
8use std::future::Future;
9use std::rc::Rc;
10
11use futures::future::{FutureExt, LocalBoxFuture, Shared};
12use futures::stream::{LocalBoxStream, Stream, StreamExt};
13use h2ts_client::{
14 ConnectOptions, H2Connection, RequestBody, RequestInit, Response, Trailers, Transport,
15};
16
17use crate::codec::{encode_message, Deframer};
18use crate::metadata::Metadata;
19use crate::state::{ConnectivityState, StateWatch};
20use crate::status::{Code, Status};
21
22pub type Connector =
25 Rc<dyn Fn() -> LocalBoxFuture<'static, Result<H2Connection, Status>>>;
26
27type SharedDial = Shared<LocalBoxFuture<'static, Result<Rc<H2Connection>, Status>>>;
28
29const CONTENT_TYPE: &str = "application/grpc+proto";
30
31#[derive(Debug, Clone, Default)]
33pub struct CallOptions {
34 pub metadata: Metadata,
35 pub timeout: Option<std::time::Duration>,
47 pub max_message_bytes: Option<usize>,
49}
50
51impl CallOptions {
52 pub fn new() -> CallOptions {
53 CallOptions::default()
54 }
55 pub fn with_metadata(mut self, metadata: Metadata) -> Self {
56 self.metadata = metadata;
57 self
58 }
59 pub fn with_timeout(mut self, timeout: std::time::Duration) -> Self {
60 self.timeout = Some(timeout);
61 self
62 }
63 pub fn with_max_message_bytes(mut self, max: usize) -> Self {
64 self.max_message_bytes = Some(max);
65 self
66 }
67}
68
69#[derive(Clone)]
77pub struct Client {
78 inner: Rc<Inner>,
79}
80
81struct Inner {
82 connector: Option<Connector>,
85 tunnel: RefCell<Option<Rc<H2Connection>>>,
86 dialing: RefCell<Option<SharedDial>>,
89 state: StateWatch,
90 authority: String,
91}
92
93impl Client {
94 pub fn from_connection(conn: H2Connection, authority: impl Into<String>) -> Client {
98 let state = StateWatch::default();
99 state.set(ConnectivityState::Ready);
100 Client {
101 inner: Rc::new(Inner {
102 connector: None,
103 tunnel: RefCell::new(Some(Rc::new(conn))),
104 dialing: RefCell::new(None),
105 state,
106 authority: authority.into(),
107 }),
108 }
109 }
110
111 pub fn with_connector(connector: Connector, authority: impl Into<String>) -> Client {
115 Client {
116 inner: Rc::new(Inner {
117 connector: Some(connector),
118 tunnel: RefCell::new(None),
119 dialing: RefCell::new(None),
120 state: StateWatch::default(),
121 authority: authority.into(),
122 }),
123 }
124 }
125
126 pub fn over_transport(
133 transport: Transport,
134 authority: impl Into<String>,
135 options: ConnectOptions,
136 ) -> (Client, impl Future<Output = ()>) {
137 let (conn, driver) = h2ts_client::connect(transport, options);
138 (Client::from_connection(conn, authority), driver)
139 }
140
141 pub fn state(&self) -> ConnectivityState {
143 if matches!(self.inner.state.get(), ConnectivityState::Ready)
145 && self.inner.tunnel.borrow().as_ref().is_none_or(|c| c.is_closed())
146 {
147 self.inner.state.set(ConnectivityState::Idle);
148 }
149 self.inner.state.get()
150 }
151
152 pub fn state_changes(&self) -> impl Stream<Item = ConnectivityState> + 'static {
155 self.inner.state.watch()
156 }
157
158 pub fn is_closed(&self) -> bool {
161 self.inner.tunnel.borrow().as_ref().is_none_or(|c| c.is_closed())
162 }
163
164 async fn tunnel(&self) -> Result<Rc<H2Connection>, Status> {
166 let cached_is_dead = {
168 let tunnel = self.inner.tunnel.borrow();
169 match tunnel.as_ref() {
170 Some(conn) if !conn.is_closed() => return Ok(conn.clone()),
171 Some(_) => true,
172 None => false,
173 }
174 };
175 if cached_is_dead {
176 self.inner.tunnel.borrow_mut().take();
181 self.inner.state.set(ConnectivityState::Idle);
182 }
183
184 let Some(connector) = self.inner.connector.clone() else {
185 return Err(Status::unavailable(
186 "the tunnel is closed and this client cannot redial \
187 (built over a caller-supplied transport)",
188 ));
189 };
190
191 let dial = {
193 let mut dialing = self.inner.dialing.borrow_mut();
194 match dialing.as_ref() {
195 Some(shared) => shared.clone(),
196 None => {
197 self.inner.state.set(ConnectivityState::Connecting);
198 let shared: SharedDial =
199 async move { connector().await.map(Rc::new) }.boxed_local().shared();
200 *dialing = Some(shared.clone());
201 shared
202 }
203 }
204 };
205
206 let result = dial.await;
207 self.inner.dialing.borrow_mut().take();
209 match result {
210 Ok(conn) => {
211 self.inner.state.set(ConnectivityState::Ready);
212 *self.inner.tunnel.borrow_mut() = Some(conn.clone());
213 Ok(conn)
214 }
215 Err(e) => {
216 self.inner.state.set(ConnectivityState::TransientFailure);
217 Err(e)
218 }
219 }
220 }
221
222 fn forget(&self, dead: &Rc<H2Connection>) {
224 let mut tunnel = self.inner.tunnel.borrow_mut();
225 if tunnel.as_ref().is_some_and(|c| Rc::ptr_eq(c, dead)) {
226 tunnel.take();
227 self.inner.state.set(ConnectivityState::Idle);
228 }
229 }
230
231 pub async fn unary(
233 &self,
234 path: &str,
235 request: Vec<u8>,
236 options: CallOptions,
237 ) -> Result<UnaryResponse, Status> {
238 self.single_response(path, RequestBody::Bytes(encode_message(&request)), &options).await
239 }
240
241 pub async fn client_streaming<S>(
246 &self,
247 path: &str,
248 requests: S,
249 options: CallOptions,
250 ) -> Result<UnaryResponse, Status>
251 where
252 S: Stream<Item = Vec<u8>> + 'static,
253 {
254 let body = RequestBody::stream(requests.map(|m| encode_message(&m)));
255 self.single_response(path, body, &options).await
256 }
257
258 pub async fn server_streaming(
260 &self,
261 path: &str,
262 request: Vec<u8>,
263 options: CallOptions,
264 ) -> Result<Streaming, Status> {
265 let body = RequestBody::Bytes(encode_message(&request));
266 self.open_stream(path, body, &options).await
267 }
268
269 pub async fn bidi_streaming<S>(
271 &self,
272 path: &str,
273 requests: S,
274 options: CallOptions,
275 ) -> Result<Streaming, Status>
276 where
277 S: Stream<Item = Vec<u8>> + 'static,
278 {
279 let body = RequestBody::stream(requests.map(|m| encode_message(&m)));
280 self.open_stream(path, body, &options).await
281 }
282
283 async fn open_stream(
295 &self,
296 path: &str,
297 body: RequestBody,
298 options: &CallOptions,
299 ) -> Result<Streaming, Status> {
300 use futures::future::{select, Either};
301
302 let Some(timeout) = options.timeout else {
303 return Ok(Streaming::new(self.request(path, body, options).await?, options, None));
304 };
305
306 let mut timer = futures_timer::Delay::new(timeout);
307 let opened = {
311 let open = self.request(path, body, options);
312 futures::pin_mut!(open);
313 match select(open, &mut timer).await {
314 Either::Left((response, _)) => Some(response),
315 Either::Right(((), _)) => None,
316 }
317 };
318 match opened {
319 Some(response) => Ok(Streaming::new(response?, options, Some(timer))),
320 None => Err(Status::new(Code::DeadlineExceeded, "deadline exceeded")),
321 }
322 }
323
324 async fn single_response(
327 &self,
328 path: &str,
329 body: RequestBody,
330 options: &CallOptions,
331 ) -> Result<UnaryResponse, Status> {
332 match options.timeout {
333 Some(timeout) => {
334 deadline(timeout, self.single_response_inner(path, body, options)).await
335 }
336 None => self.single_response_inner(path, body, options).await,
337 }
338 }
339
340 async fn single_response_inner(
341 &self,
342 path: &str,
343 body: RequestBody,
344 options: &CallOptions,
345 ) -> Result<UnaryResponse, Status> {
346 let mut response = self.request(path, body, options).await?;
347
348 let bytes = response
350 .bytes()
351 .await
352 .map_err(|e| Status::unavailable(format!("response body failed: {e}")))?;
353
354 let mut deframer = Deframer::new(options.max_message_bytes);
355 let messages = deframer.push(&bytes)?;
356 if deframer.pending() > 0 {
357 return Err(Status::new(Code::Internal, "response body ended mid-message (truncated)"));
358 }
359
360 let status = match response.trailers() {
363 Some(trailers) => Status::from_headers(&trailers).unwrap_or_else(|| {
364 Status::new(Code::Internal, "response trailers carried no grpc-status")
365 }),
366 None => Status::new(Code::Internal, "response ended without trailers"),
367 };
368 if !status.is_ok() {
369 return Err(status);
370 }
371
372 let message = messages
373 .into_iter()
374 .next()
375 .ok_or_else(|| Status::new(Code::Internal, "response carried no message"))?;
376 Ok(UnaryResponse {
377 message,
378 headers: Metadata::from_headers(&response.headers),
379 trailers: status.metadata,
380 })
381 }
382
383 pub(crate) async fn send(
388 &self,
389 path: &str,
390 headers: Vec<(String, String)>,
391 body: RequestBody,
392 ) -> Result<Response, Status> {
393 let conn = self.tunnel().await?;
394 match conn
395 .request(RequestInit {
396 method: Some("POST".to_string()),
397 path: Some(path.to_string()),
398 authority: Some(self.inner.authority.clone()),
399 scheme: Some("http".to_string()),
400 headers,
401 body,
402 })
403 .await
404 {
405 Ok(response) => Ok(response),
406 Err(e) => {
407 if conn.is_closed() {
413 self.forget(&conn);
414 }
415 Err(Status::unavailable(format!("request failed: {e}")))
416 }
417 }
418 }
419
420 async fn request(
424 &self,
425 path: &str,
426 body: RequestBody,
427 options: &CallOptions,
428 ) -> Result<Response, Status> {
429 let mut headers = vec![
430 ("content-type".to_string(), CONTENT_TYPE.to_string()),
431 ("te".to_string(), "trailers".to_string()),
432 ];
433 if let Some(timeout) = options.timeout {
434 headers.push(("grpc-timeout".to_string(), format!("{}m", options_millis(timeout))));
436 }
437 headers.extend(options.metadata.to_headers());
438
439 let response = self.send(path, headers, body).await?;
440
441 if response.status != 200 {
442 return Err(Status::unavailable(format!("HTTP {}", response.status)));
443 }
444 if let Some(status) = Status::from_headers(&response.headers) {
446 return Err(if status.is_ok() {
447 Status::new(Code::Internal, "trailers-only response reported OK with no message")
448 } else {
449 status
450 });
451 }
452 Ok(response)
453 }
454}
455
456fn options_millis(timeout: std::time::Duration) -> u128 {
457 timeout.as_millis().max(1)
458}
459
460async fn deadline<T>(
464 timeout: std::time::Duration,
465 work: impl std::future::Future<Output = Result<T, Status>>,
466) -> Result<T, Status> {
467 use futures::future::{select, Either};
468 let timer = futures_timer::Delay::new(timeout);
469 futures::pin_mut!(work);
470 futures::pin_mut!(timer);
471 match select(work, timer).await {
472 Either::Left((result, _)) => result,
473 Either::Right(((), _)) => {
474 Err(Status::new(Code::DeadlineExceeded, "deadline exceeded"))
475 }
476 }
477}
478
479pub struct Streaming {
486 pub headers: Metadata,
488 body: LocalBoxStream<'static, Result<Vec<u8>, h2ts_client::H2Error>>,
490 trailers: Trailers,
491 deframer: Deframer,
492 ready: std::collections::VecDeque<Vec<u8>>,
493 ended: bool,
494 failed: Option<Status>,
495 deadline: Option<futures_timer::Delay>,
498}
499
500impl std::fmt::Debug for Streaming {
501 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
502 f.debug_struct("Streaming")
503 .field("headers", &self.headers)
504 .field("ended", &self.ended)
505 .field("failed", &self.failed)
506 .finish_non_exhaustive()
507 }
508}
509
510impl Streaming {
511 fn new(
512 response: Response,
513 options: &CallOptions,
514 deadline: Option<futures_timer::Delay>,
515 ) -> Streaming {
516 let headers = Metadata::from_headers(&response.headers);
517 let (body, trailers) = response.into_parts();
520 Streaming {
521 headers,
522 body: body.boxed_local(),
523 trailers,
524 deframer: Deframer::new(options.max_message_bytes),
525 ready: Default::default(),
526 ended: false,
527 failed: None,
528 deadline,
529 }
530 }
531
532 pub async fn message(&mut self) -> Result<Option<Vec<u8>>, Status> {
537 loop {
538 if let Some(message) = self.ready.pop_front() {
539 return Ok(Some(message));
540 }
541 if let Some(status) = self.failed.clone() {
542 return Err(status);
543 }
544 if self.ended {
545 return Ok(None);
546 }
547 let chunk = {
552 use futures::future::{select, Either};
553 let body = &mut self.body;
554 match self.deadline.as_mut() {
555 Some(timer) => match select(body.next(), timer).await {
556 Either::Left((chunk, _)) => Some(chunk),
557 Either::Right(((), _)) => None,
558 },
559 None => Some(body.next().await),
560 }
561 };
562 let Some(chunk) = chunk else {
563 return Err(self.fail(Status::new(Code::DeadlineExceeded, "deadline exceeded")));
564 };
565 match chunk {
566 Some(Ok(chunk)) => match self.deframer.push(&chunk) {
567 Ok(messages) => self.ready.extend(messages),
568 Err(status) => return Err(self.fail(status)),
569 },
570 Some(Err(e)) => {
571 return Err(self.fail(Status::unavailable(format!("stream failed: {e}"))))
572 }
573 None => {
574 self.ended = true;
575 if self.deframer.pending() > 0 {
578 return Err(self.fail(Status::new(
579 Code::Internal,
580 "response body ended mid-message (truncated)",
581 )));
582 }
583 }
584 }
585 }
586 }
587
588 pub fn status(&self) -> Status {
591 if let Some(status) = &self.failed {
592 return status.clone();
593 }
594 match self.trailers.get() {
595 Some(trailers) => Status::from_headers(&trailers).unwrap_or_else(|| {
596 Status::new(Code::Internal, "response trailers carried no grpc-status")
597 }),
598 None => Status::new(Code::Internal, "response ended without trailers"),
601 }
602 }
603
604 pub async fn finish(&mut self) -> Status {
606 loop {
607 match self.message().await {
608 Ok(Some(_)) => continue,
609 Ok(None) => return self.status(),
610 Err(status) => return status,
611 }
612 }
613 }
614
615 fn fail(&mut self, status: Status) -> Status {
616 self.ended = true;
617 self.failed = Some(status.clone());
618 status
619 }
620}
621
622#[derive(Debug, Clone)]
624pub struct UnaryResponse {
625 pub message: Vec<u8>,
626 pub headers: Metadata,
628 pub trailers: Metadata,
630}
631
632#[cfg(test)]
633mod tests {
634 use super::*;
635
636 #[test]
637 fn a_sub_millisecond_timeout_never_becomes_zero() {
638 assert_eq!(options_millis(std::time::Duration::from_micros(1)), 1);
641 assert_eq!(options_millis(std::time::Duration::from_millis(250)), 250);
642 }
643}