codex_websocket_client/
lib.rs1mod dialer;
4
5use std::pin::Pin;
6use std::sync::Arc;
7use std::task::Context;
8use std::task::Poll;
9
10use codex_http_client::BuildCustomCaTransportError;
11use codex_http_client::HttpClientFactory;
12use codex_http_client::build_rustls_client_config_with_custom_ca;
13use futures::Sink;
14use futures::Stream;
15use rustls::ClientConfig;
16use tokio::io::AsyncRead;
17use tokio::io::AsyncWrite;
18use tokio::net::TcpStream;
19use tokio_tungstenite::MaybeTlsStream;
20use tokio_tungstenite::WebSocketStream as TungsteniteStream;
21use tokio_tungstenite::tungstenite::Error as WebSocketError;
22use tokio_tungstenite::tungstenite::Message;
23use tokio_tungstenite::tungstenite::handshake::client::Request;
24use tokio_tungstenite::tungstenite::handshake::client::Response;
25use tokio_tungstenite::tungstenite::protocol::WebSocketConfig;
26
27#[derive(Clone)]
33pub struct WebSocketConnector {
34 http_client_factory: HttpClientFactory,
35 tls_config: Arc<ClientConfig>,
36}
37
38impl WebSocketConnector {
39 pub fn new(
41 http_client_factory: &HttpClientFactory,
42 ) -> Result<Self, BuildCustomCaTransportError> {
43 Ok(Self {
44 http_client_factory: http_client_factory.clone(),
45 tls_config: build_rustls_client_config_with_custom_ca()?,
46 })
47 }
48
49 pub async fn connect(
52 &self,
53 request: Request,
54 config: WebSocketConfig,
55 ) -> Result<(WebSocketConnection, Response), WebSocketError> {
56 let proxy_route = self
57 .http_client_factory
58 .resolve_proxy_route(&request.uri().to_string());
59 let (inner, response) =
60 dialer::connect(request, config, Arc::clone(&self.tls_config), proxy_route).await?;
61 Ok((WebSocketConnection { inner }, response))
62 }
63}
64
65pub struct WebSocketConnection {
70 inner: ConnectionInner,
71}
72
73impl Stream for WebSocketConnection {
74 type Item = Result<Message, WebSocketError>;
75
76 fn poll_next(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
77 match &mut self.get_mut().inner {
78 ConnectionInner::TransportDefault(stream) => Pin::new(stream).poll_next(context),
79 ConnectionInner::Routed(stream) => Pin::new(stream).poll_next(context),
80 }
81 }
82}
83
84impl Sink<Message> for WebSocketConnection {
85 type Error = WebSocketError;
86
87 fn poll_ready(
88 self: Pin<&mut Self>,
89 context: &mut Context<'_>,
90 ) -> Poll<Result<(), Self::Error>> {
91 match &mut self.get_mut().inner {
92 ConnectionInner::TransportDefault(stream) => Pin::new(stream).poll_ready(context),
93 ConnectionInner::Routed(stream) => Pin::new(stream).poll_ready(context),
94 }
95 }
96
97 fn start_send(self: Pin<&mut Self>, message: Message) -> Result<(), Self::Error> {
98 match &mut self.get_mut().inner {
99 ConnectionInner::TransportDefault(stream) => Pin::new(stream).start_send(message),
100 ConnectionInner::Routed(stream) => Pin::new(stream).start_send(message),
101 }
102 }
103
104 fn poll_flush(
105 self: Pin<&mut Self>,
106 context: &mut Context<'_>,
107 ) -> Poll<Result<(), Self::Error>> {
108 match &mut self.get_mut().inner {
109 ConnectionInner::TransportDefault(stream) => Pin::new(stream).poll_flush(context),
110 ConnectionInner::Routed(stream) => Pin::new(stream).poll_flush(context),
111 }
112 }
113
114 fn poll_close(
115 self: Pin<&mut Self>,
116 context: &mut Context<'_>,
117 ) -> Poll<Result<(), Self::Error>> {
118 match &mut self.get_mut().inner {
119 ConnectionInner::TransportDefault(stream) => Pin::new(stream).poll_close(context),
120 ConnectionInner::Routed(stream) => Pin::new(stream).poll_close(context),
121 }
122 }
123}
124
125pub(crate) enum ConnectionInner {
126 TransportDefault(TungsteniteStream<MaybeTlsStream<TcpStream>>),
127 Routed(TungsteniteStream<MaybeTlsStream<Box<dyn AsyncIo>>>),
128}
129
130pub(crate) trait AsyncIo: AsyncRead + AsyncWrite + Send + Unpin {}
132
133impl<T> AsyncIo for T where T: AsyncRead + AsyncWrite + Send + Unpin {}