This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / bobbin / crates / runtime / src / network.rs
10 kB 309 lines
1use std::future::Future; 2use std::net::SocketAddr; 3use std::pin::Pin; 4use std::sync::Arc; 5 6use bytes::Bytes; 7use futures::stream::{Stream, StreamExt}; 8use http::{HeaderMap, StatusCode}; 9use thiserror::Error; 10use tokio_tungstenite::tungstenite::{ 11 Bytes as WsBytes, Message as TungsteniteMessage, protocol::CloseFrame as TungsteniteClose, 12 protocol::frame::coding::CloseCode as TungsteniteCloseCode, 13}; 14use url::Url; 15 16#[derive(Debug, Error)] 17pub enum NetworkError { 18 #[error("connect: {0}")] 19 Connect(String), 20 #[error("timeout: {0}")] 21 Timeout(String), 22 #[error("redirect: {0}")] 23 Redirect(String), 24 #[error("transport: {0}")] 25 Transport(String), 26 #[error("body: {0}")] 27 Body(String), 28 #[error("protocol: {0}")] 29 Protocol(String), 30} 31 32pub struct HttpRequest { 33 pub url: Url, 34 pub headers: HeaderMap, 35} 36 37pub type BodyStream = Pin<Box<dyn Stream<Item = Result<Bytes, NetworkError>> + Send + 'static>>; 38 39pub struct HttpResponseHead { 40 pub status: StatusCode, 41 pub headers: HeaderMap, 42 pub content_length: Option<u64>, 43 pub body: BodyStream, 44} 45 46pub type HttpResult = Result<HttpResponseHead, NetworkError>; 47pub type HttpResponseFuture = Pin<Box<dyn Future<Output = HttpResult> + Send + 'static>>; 48 49pub trait HttpTransport: Send + Sync + 'static { 50 fn execute(&self, request: HttpRequest) -> HttpResponseFuture; 51} 52 53#[derive(Clone, Debug)] 54pub struct ReqwestHttp { 55 client: reqwest::Client, 56} 57 58impl ReqwestHttp { 59 pub fn new(client: reqwest::Client) -> Self { 60 Self { client } 61 } 62 63 pub fn shared(client: reqwest::Client) -> Arc<dyn HttpTransport> { 64 Arc::new(Self::new(client)) 65 } 66} 67 68impl HttpTransport for ReqwestHttp { 69 fn execute(&self, request: HttpRequest) -> HttpResponseFuture { 70 let client = self.client.clone(); 71 Box::pin(async move { 72 let resp = client 73 .get(request.url) 74 .headers(request.headers) 75 .send() 76 .await 77 .map_err(map_reqwest)?; 78 let status = resp.status(); 79 let headers = resp.headers().clone(); 80 let content_length = resp.content_length(); 81 let body: BodyStream = Box::pin( 82 resp.bytes_stream() 83 .map(|chunk| chunk.map_err(|e| NetworkError::Body(e.to_string()))), 84 ); 85 Ok(HttpResponseHead { 86 status, 87 headers, 88 content_length, 89 body, 90 }) 91 }) 92 } 93} 94 95/// Lets jacquard resolve identities over the workspace reqwest (0.13); jacquard's own 96/// `HttpClient` impl is against reqwest 0.12, which is built here without TLS. 97impl jacquard_common::http_client::HttpClient for ReqwestHttp { 98 type Error = reqwest::Error; 99 100 async fn send_http( 101 &self, 102 request: http::Request<Vec<u8>>, 103 ) -> Result<http::Response<Vec<u8>>, reqwest::Error> { 104 let (parts, body) = request.into_parts(); 105 let mut req = self 106 .client 107 .request(parts.method, parts.uri.to_string()) 108 .body(body); 109 for (name, value) in parts.headers.iter() { 110 req = req.header(name, value); 111 } 112 113 let resp = req.send().await?; 114 let mut builder = http::Response::builder().status(resp.status()); 115 for (name, value) in resp.headers().iter() { 116 builder = builder.header(name, value); 117 } 118 let body = resp.bytes().await?.to_vec(); 119 Ok(builder 120 .body(body) 121 .expect("response parts came from reqwest")) 122 } 123} 124 125fn map_reqwest(err: reqwest::Error) -> NetworkError { 126 let msg = err.to_string(); 127 if err.is_timeout() { 128 NetworkError::Timeout(msg) 129 } else if err.is_connect() { 130 NetworkError::Connect(msg) 131 } else if err.is_redirect() { 132 NetworkError::Redirect(msg) 133 } else { 134 NetworkError::Transport(msg) 135 } 136} 137 138#[derive(Clone, Debug)] 139pub enum WsMessage { 140 Text(String), 141 Binary(Bytes), 142 Ping(Bytes), 143 Pong(Bytes), 144 Close { code: u16, reason: String }, 145} 146 147pub type WsSendFuture<'a> = Pin<Box<dyn Future<Output = Result<(), NetworkError>> + Send + 'a>>; 148pub type WsMessageFuture<'a> = 149 Pin<Box<dyn Future<Output = Option<Result<WsMessage, NetworkError>>> + Send + 'a>>; 150 151pub trait WsSink: Send + 'static { 152 fn send<'a>(&'a mut self, message: WsMessage) -> WsSendFuture<'a>; 153} 154 155pub trait WsStream: Send + 'static { 156 fn next<'a>(&'a mut self) -> WsMessageFuture<'a>; 157} 158 159pub struct WsConn { 160 pub sink: Box<dyn WsSink>, 161 pub stream: Box<dyn WsStream>, 162} 163 164pub type WsConnectFuture = 165 Pin<Box<dyn Future<Output = Result<WsConn, NetworkError>> + Send + 'static>>; 166 167pub trait WsTransport: Send + Sync + 'static { 168 fn connect(&self, url: Url) -> WsConnectFuture; 169} 170 171pub type AddrGuard = Arc<dyn Fn(&[SocketAddr]) -> Result<(), NetworkError> + Send + Sync>; 172 173#[derive(Clone, Copy, Debug, Default)] 174pub struct TungsteniteWs; 175 176impl TungsteniteWs { 177 pub fn shared() -> Arc<dyn WsTransport> { 178 Arc::new(Self) 179 } 180} 181 182impl WsTransport for TungsteniteWs { 183 fn connect(&self, url: Url) -> WsConnectFuture { 184 Box::pin(async move { 185 let url_str = url.as_str().to_owned(); 186 let (ws, _resp) = tokio_tungstenite::connect_async(&url_str) 187 .await 188 .map_err(|e| NetworkError::Connect(e.to_string()))?; 189 let (sink_inner, stream_inner) = futures::StreamExt::split(ws); 190 let sink: Box<dyn WsSink> = Box::new(TungsteniteSink { inner: sink_inner }); 191 let stream: Box<dyn WsStream> = Box::new(TungsteniteStream { 192 inner: stream_inner, 193 }); 194 Ok(WsConn { sink, stream }) 195 }) 196 } 197} 198 199pub struct GuardedWs { 200 guard: AddrGuard, 201} 202 203impl GuardedWs { 204 pub fn shared(guard: AddrGuard) -> Arc<dyn WsTransport> { 205 Arc::new(Self { guard }) 206 } 207} 208 209impl WsTransport for GuardedWs { 210 fn connect(&self, url: Url) -> WsConnectFuture { 211 let guard = self.guard.clone(); 212 Box::pin(async move { 213 let host = url 214 .host_str() 215 .ok_or_else(|| NetworkError::Connect("ws url missing host".to_owned()))? 216 .to_owned(); 217 let port = url 218 .port_or_known_default() 219 .ok_or_else(|| NetworkError::Connect("ws url missing port".to_owned()))?; 220 let addrs: Vec<SocketAddr> = tokio::net::lookup_host((host.as_str(), port)) 221 .await 222 .map_err(|e| NetworkError::Connect(e.to_string()))? 223 .collect(); 224 guard(&addrs)?; 225 let addr = addrs 226 .into_iter() 227 .next() 228 .ok_or_else(|| NetworkError::Connect(format!("no addresses for {host}")))?; 229 let tcp = tokio::net::TcpStream::connect(addr) 230 .await 231 .map_err(|e| NetworkError::Connect(e.to_string()))?; 232 let (ws, _resp) = tokio_tungstenite::client_async_tls(url.as_str(), tcp) 233 .await 234 .map_err(|e| NetworkError::Connect(e.to_string()))?; 235 let (sink_inner, stream_inner) = futures::StreamExt::split(ws); 236 let sink: Box<dyn WsSink> = Box::new(TungsteniteSink { inner: sink_inner }); 237 let stream: Box<dyn WsStream> = Box::new(TungsteniteStream { 238 inner: stream_inner, 239 }); 240 Ok(WsConn { sink, stream }) 241 }) 242 } 243} 244 245type TungsteniteWsStream = 246 tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>; 247 248struct TungsteniteSink { 249 inner: futures::stream::SplitSink<TungsteniteWsStream, TungsteniteMessage>, 250} 251 252impl WsSink for TungsteniteSink { 253 fn send<'a>(&'a mut self, message: WsMessage) -> WsSendFuture<'a> { 254 Box::pin(async move { 255 use futures::SinkExt; 256 self.inner 257 .send(message_to_tungstenite(message)) 258 .await 259 .map_err(|e| NetworkError::Transport(e.to_string())) 260 }) 261 } 262} 263 264struct TungsteniteStream { 265 inner: futures::stream::SplitStream<TungsteniteWsStream>, 266} 267 268impl WsStream for TungsteniteStream { 269 fn next<'a>(&'a mut self) -> WsMessageFuture<'a> { 270 Box::pin(async move { 271 let item = StreamExt::next(&mut self.inner).await?; 272 Some( 273 item.map_err(|e| NetworkError::Transport(e.to_string())) 274 .and_then(message_from_tungstenite), 275 ) 276 }) 277 } 278} 279 280fn message_to_tungstenite(message: WsMessage) -> TungsteniteMessage { 281 match message { 282 WsMessage::Text(text) => TungsteniteMessage::Text(text.into()), 283 WsMessage::Binary(bytes) => TungsteniteMessage::Binary(WsBytes::copy_from_slice(&bytes)), 284 WsMessage::Ping(bytes) => TungsteniteMessage::Ping(WsBytes::copy_from_slice(&bytes)), 285 WsMessage::Pong(bytes) => TungsteniteMessage::Pong(WsBytes::copy_from_slice(&bytes)), 286 WsMessage::Close { code, reason } => TungsteniteMessage::Close(Some(TungsteniteClose { 287 code: TungsteniteCloseCode::from(code), 288 reason: reason.into(), 289 })), 290 } 291} 292 293fn message_from_tungstenite(message: TungsteniteMessage) -> Result<WsMessage, NetworkError> { 294 match message { 295 TungsteniteMessage::Text(t) => Ok(WsMessage::Text(t.to_string())), 296 TungsteniteMessage::Binary(b) => Ok(WsMessage::Binary(Bytes::copy_from_slice(&b))), 297 TungsteniteMessage::Ping(b) => Ok(WsMessage::Ping(Bytes::copy_from_slice(&b))), 298 TungsteniteMessage::Pong(b) => Ok(WsMessage::Pong(Bytes::copy_from_slice(&b))), 299 TungsteniteMessage::Close(close) => { 300 let (code, reason) = close 301 .map(|c| (u16::from(c.code), c.reason.to_string())) 302 .unwrap_or((1000, String::new())); 303 Ok(WsMessage::Close { code, reason }) 304 } 305 TungsteniteMessage::Frame(_) => Err(NetworkError::Protocol( 306 "tungstenite raw frame surfaced unexpectedly".to_owned(), 307 )), 308 } 309}