1use core::pin::Pin;
4use core::task::{Context, Poll, ready};
5
6use anyhow::Context as _;
7use bytes::Bytes;
8use futures::stream::{SplitSink, SplitStream};
9use futures::{Sink, Stream, StreamExt as _};
10use tokio::io::{AsyncRead, AsyncWrite};
11use tokio_util::io::{SinkWriter, StreamReader};
12use tokio_websockets::resolver::{Gai, Resolver};
13use tokio_websockets::{Message, WebSocketStream};
14use tracing::instrument;
15
16use wrpc_transport::Invoke;
17use wrpc_transport::frame::invoke;
18
19pub use tokio_websockets;
20#[doc(no_inline)]
21pub use tokio_websockets::{ClientBuilder, ServerBuilder};
22
23pub struct Outgoing<T> {
25 sink: SplitSink<WebSocketStream<T>, Message>,
26 eof_sent: bool,
27}
28
29impl<T: AsyncRead + AsyncWrite + Unpin> Sink<&[u8]> for Outgoing<T> {
30 type Error = std::io::Error;
31
32 fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
33 Pin::new(&mut self.sink)
34 .poll_ready(cx)
35 .map_err(std::io::Error::other)
36 }
37
38 fn start_send(mut self: Pin<&mut Self>, buf: &[u8]) -> Result<(), Self::Error> {
39 let buf = Message::binary(Bytes::copy_from_slice(buf));
40 Pin::new(&mut self.sink)
41 .start_send(buf)
42 .map_err(std::io::Error::other)
43 }
44
45 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
46 Pin::new(&mut self.sink)
47 .poll_flush(cx)
48 .map_err(std::io::Error::other)
49 }
50
51 fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
52 let this = self.as_mut().get_mut();
53 if !this.eof_sent {
54 ready!(Pin::new(&mut this.sink).poll_ready(cx)).map_err(std::io::Error::other)?;
55 Pin::new(&mut this.sink)
56 .start_send(Message::text(Bytes::new()))
57 .map_err(std::io::Error::other)?;
58 this.eof_sent = true;
59 }
60 Pin::new(&mut this.sink)
61 .poll_flush(cx)
62 .map_err(std::io::Error::other)
63 }
64}
65
66#[repr(transparent)]
68pub struct Incoming<T>(SplitStream<WebSocketStream<T>>);
69
70impl<T: AsyncRead + AsyncWrite + Unpin> Stream for Incoming<T> {
71 type Item = Result<Bytes, std::io::Error>;
72
73 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
74 loop {
75 match ready!(Pin::new(&mut self.0).poll_next(cx)) {
76 Some(Ok(msg)) if msg.is_text() => {
77 if msg.as_payload().is_empty() {
78 return Poll::Ready(None);
79 }
80 return Poll::Ready(Some(Err(std::io::Error::new(
81 std::io::ErrorKind::InvalidData,
82 "unexpected non-empty WebSocket text frame",
83 ))));
84 }
85 Some(Ok(msg)) if msg.is_close() => {
86 return Poll::Ready(Some(Err(std::io::Error::new(
87 std::io::ErrorKind::UnexpectedEof,
88 "WebSocket connection closed before EOF sentinel",
89 ))));
90 }
91 Some(Ok(msg)) if msg.is_ping() || msg.is_pong() => continue,
92 Some(Ok(msg)) => return Poll::Ready(Some(Ok(Bytes::from(msg.into_payload())))),
93 Some(Err(err)) => return Poll::Ready(Some(Err(std::io::Error::other(err)))),
94 None => return Poll::Ready(None),
95 }
96 }
97 }
98}
99
100pub fn split<T>(
104 ws: WebSocketStream<T>,
105) -> (SinkWriter<Outgoing<T>>, StreamReader<Incoming<T>, Bytes>)
106where
107 T: AsyncRead + AsyncWrite + Unpin,
108{
109 let (tx, rx) = ws.split();
110 (
111 SinkWriter::new(Outgoing {
112 sink: tx,
113 eof_sent: false,
114 }),
115 StreamReader::new(Incoming(rx)),
116 )
117}
118
119#[repr(transparent)]
121pub struct Client<'a, R: Resolver = Gai>(ClientBuilder<'a, R>);
122
123impl<'a, R: Resolver> Client<'a, R> {
124 #[must_use]
126 pub fn from_builder(builder: ClientBuilder<'a, R>) -> Self {
127 Self(builder)
128 }
129}
130
131impl<'a, R: Resolver> From<ClientBuilder<'a, R>> for Client<'a, R> {
132 fn from(builder: ClientBuilder<'a, R>) -> Self {
133 Self::from_builder(builder)
134 }
135}
136
137impl<R: Resolver + Send + Sync> Invoke for Client<'_, R> {
138 type Context = ();
139
140 #[instrument(level = "trace", skip(self, paths, params), fields(params = format!("{params:02x?}")))]
141 async fn invoke<P>(
142 &self,
143 (): Self::Context,
144 instance: &str,
145 func: &str,
146 params: Bytes,
147 paths: impl AsRef<[P]> + Send,
148 ) -> anyhow::Result<(
149 wrpc_transport::frame::Outgoing,
150 wrpc_transport::frame::Incoming,
151 )>
152 where
153 P: AsRef<[Option<usize>]> + Send + Sync,
154 {
155 let (ws, _res) = self
156 .0
157 .connect()
158 .await
159 .context("failed to establish WebSocket connection")?;
160 let (tx, rx) = split(ws);
161 invoke(tx, rx, instance, func, params, paths).await
162 }
163}