1#![cfg_attr(docsrs, feature(doc_cfg))]
13#![allow(unused_features)]
14#![warn(missing_docs)]
15#![deny(rustdoc::broken_intra_doc_links)]
16#![doc(
17 html_logo_url = "https://github.com/compio-rs/compio-logo/raw/refs/heads/master/generated/colored-bold.svg"
18)]
19#![doc(
20 html_favicon_url = "https://github.com/compio-rs/compio-logo/raw/refs/heads/master/generated/colored-bold.svg"
21)]
22
23use std::{
24 io,
25 pin::Pin,
26 task::{Context, Poll, ready},
27};
28
29use compio_buf::IntoInner;
30use compio_driver::AsFd;
31use compio_net::{TcpStream, UnixStream};
32use compio_runtime::fd::PollFd;
33use compio_tls::{MaybeTlsStream, TlsStream};
34use futures_util::{Sink, SinkExt, Stream, StreamExt, stream::FusedStream};
35use pin_project_lite::pin_project;
36use socket2::Socket;
37use tungstenite::{
38 Error as WsError, Message,
39 client::IntoClientRequest,
40 handshake::server::{Callback, NoCallback},
41 protocol::{CloseFrame, Role, WebSocketConfig},
42};
43
44#[cfg(feature = "connect")]
45mod tls;
46#[cfg(feature = "connect")]
47pub use tls::*;
48pub use tungstenite;
49
50type MaybePollStream<S> = MaybeTlsStream<PollFd<S>>;
51
52pub struct Config {
63 websocket: Option<WebSocketConfig>,
65
66 disable_nagle: bool,
69}
70
71impl Config {
72 pub fn new() -> Self {
74 Self {
75 websocket: None,
76 disable_nagle: false,
77 }
78 }
79
80 pub fn websocket_config(&self) -> Option<&WebSocketConfig> {
82 self.websocket.as_ref()
83 }
84
85 pub fn disable_nagle(mut self, disable: bool) -> Self {
90 self.disable_nagle = disable;
91 self
92 }
93}
94
95impl Default for Config {
96 fn default() -> Self {
97 Self::new()
98 }
99}
100
101impl From<WebSocketConfig> for Config {
102 fn from(config: WebSocketConfig) -> Self {
103 Self {
104 websocket: Some(config),
105 ..Default::default()
106 }
107 }
108}
109
110impl From<Option<WebSocketConfig>> for Config {
111 fn from(config: Option<WebSocketConfig>) -> Self {
112 Self {
113 websocket: config,
114 ..Default::default()
115 }
116 }
117}
118
119mod private {
120 use super::*;
121
122 pub trait Sealed<S>
123 where
124 S: AsFd,
125 {
126 }
127
128 impl<S: AsFd> Sealed<S> for PollFd<S> {}
129 impl<S: AsFd> Sealed<S> for MaybePollStream<S> {}
130 impl<S: AsFd> Sealed<S> for TlsStream<PollFd<S>> {}
131 impl Sealed<Socket> for TcpStream {}
132 impl Sealed<Socket> for UnixStream {}
133}
134
135pub trait IntoMaybeTlsStream<S>: private::Sealed<S>
137where
138 S: AsFd,
139{
140 fn into_maybe_tls_stream(self) -> io::Result<MaybePollStream<S>>;
142}
143
144impl<S: AsFd> IntoMaybeTlsStream<S> for PollFd<S> {
145 fn into_maybe_tls_stream(self) -> io::Result<MaybePollStream<S>> {
146 Ok(MaybeTlsStream::new_plain(self))
147 }
148}
149
150impl<S: AsFd> IntoMaybeTlsStream<S> for MaybePollStream<S> {
151 fn into_maybe_tls_stream(self) -> io::Result<MaybePollStream<S>> {
152 Ok(self)
153 }
154}
155
156impl<S: AsFd> IntoMaybeTlsStream<S> for TlsStream<PollFd<S>> {
157 fn into_maybe_tls_stream(self) -> io::Result<MaybePollStream<S>> {
158 Ok(MaybeTlsStream::new_tls(self))
159 }
160}
161
162impl IntoMaybeTlsStream<Socket> for TcpStream {
163 fn into_maybe_tls_stream(self) -> io::Result<MaybePollStream<Socket>> {
164 Ok(MaybeTlsStream::new_plain(self.into_poll_fd()?))
165 }
166}
167
168impl IntoMaybeTlsStream<Socket> for UnixStream {
169 fn into_maybe_tls_stream(self) -> io::Result<MaybePollStream<Socket>> {
170 Ok(MaybeTlsStream::new_plain(self.into_poll_fd()?))
171 }
172}
173
174pin_project! {
175 #[derive(Debug)]
177 pub struct WebSocketStream<S: AsFd> {
178 #[pin]
179 inner: async_tungstenite::WebSocketStream<MaybePollStream<S>>,
180 next_item: Option<Option<Result<Message, WsError>>>,
181 }
182}
183
184impl<S: AsFd + 'static> WebSocketStream<S> {
185 pub fn get_ref(&self) -> &MaybePollStream<S> {
187 self.inner.get_ref()
188 }
189
190 pub fn get_mut(&mut self) -> &mut MaybePollStream<S> {
192 self.inner.get_mut()
193 }
194
195 pub async fn from_raw_socket<T: IntoMaybeTlsStream<S>>(
202 stream: T,
203 role: Role,
204 config: impl Into<Config>,
205 ) -> io::Result<Self> {
206 let config = config.into();
207
208 Ok(Self::from_inner(
209 async_tungstenite::WebSocketStream::from_raw_socket(
210 stream.into_maybe_tls_stream()?,
211 role,
212 config.websocket,
213 )
214 .await,
215 ))
216 }
217
218 pub async fn from_partially_read<T: IntoMaybeTlsStream<S>>(
225 stream: T,
226 part: Vec<u8>,
227 role: Role,
228 config: impl Into<Config>,
229 ) -> io::Result<Self> {
230 let config = config.into();
231
232 Ok(Self::from_inner(
233 async_tungstenite::WebSocketStream::from_partially_read(
234 stream.into_maybe_tls_stream()?,
235 part,
236 role,
237 config.websocket,
238 )
239 .await,
240 ))
241 }
242
243 fn from_inner(inner: async_tungstenite::WebSocketStream<MaybePollStream<S>>) -> Self {
244 WebSocketStream {
245 inner,
246 next_item: None,
247 }
248 }
249
250 pub async fn send(&mut self, message: Message) -> Result<(), WsError> {
252 SinkExt::send(self, message).await
253 }
254
255 pub async fn read(&mut self) -> Result<Message, WsError> {
257 self.next()
258 .await
259 .unwrap_or_else(|| Err(WsError::ConnectionClosed))
260 }
261
262 pub async fn flush(&mut self) -> Result<(), WsError> {
264 SinkExt::flush(self).await
265 }
266
267 pub async fn close(&mut self, close_frame: Option<CloseFrame>) -> Result<(), WsError> {
269 self.send(Message::Close(close_frame)).await
270 }
271}
272
273impl<S: AsFd> IntoInner for WebSocketStream<S> {
274 type Inner = MaybePollStream<S>;
275
276 fn into_inner(self) -> Self::Inner {
277 self.inner.into_inner()
278 }
279}
280
281impl<S: AsFd + 'static> Sink<Message> for WebSocketStream<S> {
282 type Error = WsError;
283
284 fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), WsError>> {
285 self.project().inner.poll_ready(cx)
286 }
287
288 fn start_send(self: Pin<&mut Self>, item: Message) -> Result<(), Self::Error> {
289 self.project().inner.start_send(item)
290 }
291
292 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
293 ready!(self.as_mut().project().inner.poll_flush(cx))?;
294 ready!(futures_util::AsyncWrite::poll_flush(
295 Pin::new(self.project().inner.get_mut().get_mut()),
296 cx
297 ))?;
298 Poll::Ready(Ok(()))
299 }
300
301 fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
302 self.project().inner.poll_close(cx)
303 }
304}
305
306impl<S: AsFd + 'static> Stream for WebSocketStream<S> {
307 type Item = Result<Message, WsError>;
308
309 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
310 let mut this = self.project();
311 loop {
312 if this.next_item.is_some() {
313 ready!(this.inner.as_mut().poll_flush(cx))?;
314 ready!(futures_util::AsyncWrite::poll_flush(
315 Pin::new(this.inner.get_mut().get_mut()),
316 cx
317 ))?;
318 break Poll::Ready(this.next_item.take().expect("next_item should be Some"));
319 } else {
320 let item = ready!(this.inner.as_mut().poll_next(cx));
321 *this.next_item = Some(item);
322 }
323 }
324 }
325}
326
327impl<S: AsFd + 'static> FusedStream for WebSocketStream<S> {
328 fn is_terminated(&self) -> bool {
329 self.inner.is_terminated()
330 }
331}
332
333pub async fn accept_async<S, T>(stream: T) -> Result<WebSocketStream<S>, WsError>
344where
345 S: AsFd + 'static,
346 T: IntoMaybeTlsStream<S>,
347{
348 accept_hdr_async(stream, NoCallback).await
349}
350
351pub async fn accept_async_with_config<S, T>(
353 stream: T,
354 config: impl Into<Config>,
355) -> Result<WebSocketStream<S>, WsError>
356where
357 S: AsFd + 'static,
358 T: IntoMaybeTlsStream<S>,
359{
360 accept_hdr_with_config_async(stream, NoCallback, config).await
361}
362
363pub async fn accept_hdr_async<S, T, C>(
369 stream: T,
370 callback: C,
371) -> Result<WebSocketStream<S>, WsError>
372where
373 S: AsFd + 'static,
374 T: IntoMaybeTlsStream<S>,
375 C: Callback + Unpin,
376{
377 accept_hdr_with_config_async(stream, callback, None).await
378}
379
380pub async fn accept_hdr_with_config_async<S, T, C>(
382 stream: T,
383 callback: C,
384 config: impl Into<Config>,
385) -> Result<WebSocketStream<S>, WsError>
386where
387 S: AsFd + 'static,
388 T: IntoMaybeTlsStream<S>,
389 C: Callback + Unpin,
390{
391 let config = config.into();
392 let inner = async_tungstenite::accept_hdr_async_with_config(
393 stream.into_maybe_tls_stream()?,
394 callback,
395 config.websocket,
396 )
397 .await?;
398 Ok(WebSocketStream::from_inner(inner))
399}
400
401pub async fn client_async<R, S, T>(
415 request: R,
416 stream: T,
417) -> Result<(WebSocketStream<S>, tungstenite::handshake::client::Response), WsError>
418where
419 R: IntoClientRequest + Unpin,
420 S: AsFd + 'static,
421 T: IntoMaybeTlsStream<S>,
422{
423 client_async_with_config(request, stream, None).await
424}
425
426pub async fn client_async_with_config<R, S, T>(
428 request: R,
429 stream: T,
430 config: impl Into<Config>,
431) -> Result<(WebSocketStream<S>, tungstenite::handshake::client::Response), WsError>
432where
433 R: IntoClientRequest + Unpin,
434 S: AsFd + 'static,
435 T: IntoMaybeTlsStream<S>,
436{
437 let config = config.into();
438 let (inner, response) = async_tungstenite::client_async_with_config(
439 request,
440 stream.into_maybe_tls_stream()?,
441 config.websocket,
442 )
443 .await?;
444 Ok((WebSocketStream::from_inner(inner), response))
445}