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 async fn request(
387 &self,
388 path: &str,
389 body: RequestBody,
390 options: &CallOptions,
391 ) -> Result<Response, Status> {
392 let conn = self.tunnel().await?;
393 let mut headers = vec![
394 ("content-type".to_string(), CONTENT_TYPE.to_string()),
395 ("te".to_string(), "trailers".to_string()),
396 ];
397 if let Some(timeout) = options.timeout {
398 headers.push(("grpc-timeout".to_string(), format!("{}m", options_millis(timeout))));
400 }
401 headers.extend(options.metadata.to_headers());
402
403 let response = match conn
404 .request(RequestInit {
405 method: Some("POST".to_string()),
406 path: Some(path.to_string()),
407 authority: Some(self.inner.authority.clone()),
408 scheme: Some("http".to_string()),
409 headers,
410 body,
411 })
412 .await
413 {
414 Ok(response) => response,
415 Err(e) => {
416 if conn.is_closed() {
422 self.forget(&conn);
423 }
424 return Err(Status::unavailable(format!("request failed: {e}")));
425 }
426 };
427
428 if response.status != 200 {
429 return Err(Status::unavailable(format!("HTTP {}", response.status)));
430 }
431 if let Some(status) = Status::from_headers(&response.headers) {
433 return Err(if status.is_ok() {
434 Status::new(Code::Internal, "trailers-only response reported OK with no message")
435 } else {
436 status
437 });
438 }
439 Ok(response)
440 }
441}
442
443fn options_millis(timeout: std::time::Duration) -> u128 {
444 timeout.as_millis().max(1)
445}
446
447async fn deadline<T>(
451 timeout: std::time::Duration,
452 work: impl std::future::Future<Output = Result<T, Status>>,
453) -> Result<T, Status> {
454 use futures::future::{select, Either};
455 let timer = futures_timer::Delay::new(timeout);
456 futures::pin_mut!(work);
457 futures::pin_mut!(timer);
458 match select(work, timer).await {
459 Either::Left((result, _)) => result,
460 Either::Right(((), _)) => {
461 Err(Status::new(Code::DeadlineExceeded, "deadline exceeded"))
462 }
463 }
464}
465
466pub struct Streaming {
473 pub headers: Metadata,
475 body: LocalBoxStream<'static, Result<Vec<u8>, h2ts_client::H2Error>>,
477 trailers: Trailers,
478 deframer: Deframer,
479 ready: std::collections::VecDeque<Vec<u8>>,
480 ended: bool,
481 failed: Option<Status>,
482 deadline: Option<futures_timer::Delay>,
485}
486
487impl std::fmt::Debug for Streaming {
488 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
489 f.debug_struct("Streaming")
490 .field("headers", &self.headers)
491 .field("ended", &self.ended)
492 .field("failed", &self.failed)
493 .finish_non_exhaustive()
494 }
495}
496
497impl Streaming {
498 fn new(
499 response: Response,
500 options: &CallOptions,
501 deadline: Option<futures_timer::Delay>,
502 ) -> Streaming {
503 let headers = Metadata::from_headers(&response.headers);
504 let (body, trailers) = response.into_parts();
507 Streaming {
508 headers,
509 body: body.boxed_local(),
510 trailers,
511 deframer: Deframer::new(options.max_message_bytes),
512 ready: Default::default(),
513 ended: false,
514 failed: None,
515 deadline,
516 }
517 }
518
519 pub async fn message(&mut self) -> Result<Option<Vec<u8>>, Status> {
524 loop {
525 if let Some(message) = self.ready.pop_front() {
526 return Ok(Some(message));
527 }
528 if let Some(status) = self.failed.clone() {
529 return Err(status);
530 }
531 if self.ended {
532 return Ok(None);
533 }
534 let chunk = {
539 use futures::future::{select, Either};
540 let body = &mut self.body;
541 match self.deadline.as_mut() {
542 Some(timer) => match select(body.next(), timer).await {
543 Either::Left((chunk, _)) => Some(chunk),
544 Either::Right(((), _)) => None,
545 },
546 None => Some(body.next().await),
547 }
548 };
549 let Some(chunk) = chunk else {
550 return Err(self.fail(Status::new(Code::DeadlineExceeded, "deadline exceeded")));
551 };
552 match chunk {
553 Some(Ok(chunk)) => match self.deframer.push(&chunk) {
554 Ok(messages) => self.ready.extend(messages),
555 Err(status) => return Err(self.fail(status)),
556 },
557 Some(Err(e)) => {
558 return Err(self.fail(Status::unavailable(format!("stream failed: {e}"))))
559 }
560 None => {
561 self.ended = true;
562 if self.deframer.pending() > 0 {
565 return Err(self.fail(Status::new(
566 Code::Internal,
567 "response body ended mid-message (truncated)",
568 )));
569 }
570 }
571 }
572 }
573 }
574
575 pub fn status(&self) -> Status {
578 if let Some(status) = &self.failed {
579 return status.clone();
580 }
581 match self.trailers.get() {
582 Some(trailers) => Status::from_headers(&trailers).unwrap_or_else(|| {
583 Status::new(Code::Internal, "response trailers carried no grpc-status")
584 }),
585 None => Status::new(Code::Internal, "response ended without trailers"),
588 }
589 }
590
591 pub async fn finish(&mut self) -> Status {
593 loop {
594 match self.message().await {
595 Ok(Some(_)) => continue,
596 Ok(None) => return self.status(),
597 Err(status) => return status,
598 }
599 }
600 }
601
602 fn fail(&mut self, status: Status) -> Status {
603 self.ended = true;
604 self.failed = Some(status.clone());
605 status
606 }
607}
608
609#[derive(Debug, Clone)]
611pub struct UnaryResponse {
612 pub message: Vec<u8>,
613 pub headers: Metadata,
615 pub trailers: Metadata,
617}
618
619#[cfg(test)]
620mod tests {
621 use super::*;
622
623 #[test]
624 fn a_sub_millisecond_timeout_never_becomes_zero() {
625 assert_eq!(options_millis(std::time::Duration::from_micros(1)), 1);
628 assert_eq!(options_millis(std::time::Duration::from_millis(250)), 250);
629 }
630}