use crate::{
header,
websocket::{
ffi,
types::{WebSocketCloseFrame, WebSocketError, WebSocketResult},
},
Method, Request, Response, StatusCode,
};
pub use ffi::create_websocket_response;
use futures_channel::mpsc::{self, UnboundedReceiver, UnboundedSender};
use futures_core::Stream;
use http_kit::utils::ByteStr;
use http_kit::ws::{WebSocketConfig, WebSocketMessage};
use serde::Serialize;
use skyzen_core::{Extractor, Responder};
use std::{
cell::RefCell,
pin::Pin,
rc::Rc,
task::{Context, Poll},
};
use wasm_bindgen::{prelude::*, JsCast};
fn ensure_within_limit(config: &WebSocketConfig, len: usize) -> WebSocketResult<()> {
if let Some(limit) = config.max_message_size {
if len > limit {
return Err(WebSocketError::MessageTooLarge { len, limit });
}
}
Ok(())
}
pub struct WebSocket {
inner: ffi::WebSocket,
rx: UnboundedReceiver<WebSocketResult<WebSocketMessage>>,
_closures: Rc<RefCell<EventClosures>>,
config: WebSocketConfig,
}
struct EventClosures {
_on_message: Closure<dyn FnMut(ffi::MessageEvent)>,
_on_close: Closure<dyn FnMut(ffi::CloseEvent)>,
_on_error: Closure<dyn FnMut(ffi::ErrorEvent)>,
}
impl WebSocket {
pub(crate) fn from_ffi_socket(socket: ffi::WebSocket, config: WebSocketConfig) -> Self {
let (tx, rx) = mpsc::unbounded();
let closures = Self::setup_event_handlers(&socket, tx);
Self {
inner: socket,
rx,
_closures: Rc::new(RefCell::new(closures)),
config,
}
}
fn setup_event_handlers(
socket: &ffi::WebSocket,
tx: UnboundedSender<WebSocketResult<WebSocketMessage>>,
) -> EventClosures {
let tx_message = tx.clone();
let on_message = Closure::wrap(Box::new(move |event: ffi::MessageEvent| {
let data = event.data();
let message = if let Some(text) = data.as_string() {
WebSocketMessage::Text(text.into())
} else if js_sys::Uint8Array::instanceof(&data) {
let array = js_sys::Uint8Array::from(data);
let mut bytes = vec![0u8; array.length() as usize];
array.copy_to(&mut bytes);
WebSocketMessage::Binary(bytes.into())
} else {
return;
};
let _ = tx_message.unbounded_send(Ok(message));
}) as Box<dyn FnMut(ffi::MessageEvent)>);
let tx_close = tx.clone();
let on_close = Closure::wrap(Box::new(move |_event: ffi::CloseEvent| {
let _ = tx_close.unbounded_send(Ok(WebSocketMessage::Close));
}) as Box<dyn FnMut(ffi::CloseEvent)>);
let on_error = Closure::wrap(Box::new(move |event: ffi::ErrorEvent| {
let _ = tx.unbounded_send(Err(WebSocketError::Protocol(event.message())));
}) as Box<dyn FnMut(ffi::ErrorEvent)>);
socket.add_event_listener("message", on_message.as_ref().unchecked_ref());
socket.add_event_listener("close", on_close.as_ref().unchecked_ref());
socket.add_event_listener("error", on_error.as_ref().unchecked_ref());
EventClosures {
_on_message: on_message,
_on_close: on_close,
_on_error: on_error,
}
}
#[cfg(feature = "json")]
pub async fn send<T: Serialize>(&mut self, value: T) -> WebSocketResult<()> {
let payload = serde_json::to_string(&value)?;
self.send_text(payload).await
}
pub async fn send_text(&mut self, text: impl Into<ByteStr>) -> WebSocketResult<()> {
let text = text.into();
ensure_within_limit(&self.config, text.len())?;
self.inner
.send(&JsValue::from_str(&text))
.map_err(|e| WebSocketError::Protocol(format!("{e:?}")))
}
pub async fn send_binary(&mut self, data: impl Into<Vec<u8>>) -> WebSocketResult<()> {
let bytes = data.into();
ensure_within_limit(&self.config, bytes.len())?;
let array = js_sys::Uint8Array::from(&bytes[..]);
self.inner
.send(&array.into())
.map_err(|e| WebSocketError::Protocol(format!("{e:?}")))
}
pub async fn send_ping(&mut self, _data: impl Into<Vec<u8>>) -> WebSocketResult<()> {
Err(WebSocketError::Protocol(
"Ping frames not supported on WASM platform".into(),
))
}
pub async fn send_pong(&mut self, _data: impl Into<Vec<u8>>) -> WebSocketResult<()> {
Err(WebSocketError::Protocol(
"Pong frames not supported on WASM platform".into(),
))
}
pub async fn send_message(&mut self, message: WebSocketMessage) -> WebSocketResult<()> {
match message {
WebSocketMessage::Text(text) => self.send_text(text).await,
WebSocketMessage::Binary(data) => self.send_binary(data).await,
WebSocketMessage::Close => self.close(None).await,
WebSocketMessage::Ping(_) => self.send_ping(vec![]).await,
WebSocketMessage::Pong(_) => self.send_pong(vec![]).await,
}
}
#[cfg(feature = "json")]
pub async fn recv_json<T: serde::de::DeserializeOwned>(
&mut self,
) -> Option<WebSocketResult<T>> {
use futures_util::StreamExt;
loop {
match self.next().await {
Some(Ok(msg)) => {
if let Some(result) = msg.into_json() {
return Some(result.map_err(WebSocketError::from));
}
}
Some(Err(e)) => return Some(Err(e)),
None => return None,
}
}
}
pub fn get_config(&self) -> &WebSocketConfig {
&self.config
}
pub async fn close(&mut self, close_frame: Option<WebSocketCloseFrame>) -> WebSocketResult<()> {
if let Some(frame) = close_frame {
self.inner.close(Some(frame.code), Some(&frame.reason));
} else {
self.inner.close(None, None);
}
Ok(())
}
pub fn split(self) -> (WebSocketSender, WebSocketReceiver) {
let config = self.config.clone();
let inner = self.inner;
let closures = self._closures;
(
WebSocketSender {
inner: inner.clone(),
config: config.clone(),
_closures: closures.clone(),
},
WebSocketReceiver {
rx: self.rx,
config,
_closures: closures,
},
)
}
}
impl std::fmt::Debug for WebSocket {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WebSocket").finish_non_exhaustive()
}
}
impl Stream for WebSocket {
type Item = WebSocketResult<WebSocketMessage>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Pin::new(&mut self.rx).poll_next(cx)
}
}
pub struct WebSocketSender {
inner: ffi::WebSocket,
config: WebSocketConfig,
_closures: Rc<RefCell<EventClosures>>,
}
impl WebSocketSender {
#[cfg(feature = "json")]
pub async fn send<T: Serialize>(&mut self, value: T) -> WebSocketResult<()> {
let payload = serde_json::to_string(&value)?;
self.send_text(payload).await
}
pub async fn send_text(&mut self, text: impl Into<ByteStr>) -> WebSocketResult<()> {
let text = text.into();
ensure_within_limit(&self.config, text.len())?;
self.inner
.send(&JsValue::from_str(&text))
.map_err(|e| WebSocketError::Protocol(format!("{e:?}")))
}
pub async fn send_binary(&mut self, data: impl Into<Vec<u8>>) -> WebSocketResult<()> {
let bytes = data.into();
ensure_within_limit(&self.config, bytes.len())?;
let array = js_sys::Uint8Array::from(&bytes[..]);
self.inner
.send(&array.into())
.map_err(|e| WebSocketError::Protocol(format!("{e:?}")))
}
pub async fn send_ping(&mut self, _data: impl Into<Vec<u8>>) -> WebSocketResult<()> {
Err(WebSocketError::Protocol(
"Ping frames not supported on WASM platform".into(),
))
}
pub async fn send_pong(&mut self, _data: impl Into<Vec<u8>>) -> WebSocketResult<()> {
Err(WebSocketError::Protocol(
"Pong frames not supported on WASM platform".into(),
))
}
pub async fn send_message(&mut self, message: WebSocketMessage) -> WebSocketResult<()> {
match message {
WebSocketMessage::Text(text) => self.send_text(text).await,
WebSocketMessage::Binary(data) => self.send_binary(data).await,
WebSocketMessage::Close => self.close(None).await,
WebSocketMessage::Ping(_) | WebSocketMessage::Pong(_) => Err(WebSocketError::Protocol(
"Ping/Pong not supported on WASM".into(),
)),
}
}
pub async fn close(&mut self, close_frame: Option<WebSocketCloseFrame>) -> WebSocketResult<()> {
if let Some(frame) = close_frame {
self.inner.close(Some(frame.code), Some(&frame.reason));
} else {
self.inner.close(None, None);
}
Ok(())
}
pub fn get_config(&self) -> &WebSocketConfig {
&self.config
}
}
impl std::fmt::Debug for WebSocketSender {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WebSocketSender").finish_non_exhaustive()
}
}
pub struct WebSocketReceiver {
rx: UnboundedReceiver<WebSocketResult<WebSocketMessage>>,
config: WebSocketConfig,
_closures: Rc<RefCell<EventClosures>>,
}
impl WebSocketReceiver {
#[cfg(feature = "json")]
pub async fn recv_json<T: serde::de::DeserializeOwned>(
&mut self,
) -> Option<WebSocketResult<T>> {
use futures_util::StreamExt;
loop {
match self.next().await {
Some(Ok(msg)) => {
if let Some(result) = msg.into_json() {
return Some(result.map_err(WebSocketError::from));
}
}
Some(Err(e)) => return Some(Err(e)),
None => return None,
}
}
}
pub fn get_config(&self) -> &WebSocketConfig {
&self.config
}
}
impl std::fmt::Debug for WebSocketReceiver {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WebSocketReceiver").finish_non_exhaustive()
}
}
impl Stream for WebSocketReceiver {
type Item = WebSocketResult<WebSocketMessage>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Pin::new(&mut self.rx).poll_next(cx)
}
}
#[skyzen::error(status = StatusCode::BAD_REQUEST)]
pub enum WebSocketUpgradeError {
#[error("Method not allowed", status = StatusCode::METHOD_NOT_ALLOWED)]
MethodNotAllowed,
#[error("Missing or invalid upgrade header")]
MissingUpgradeHeader,
#[error("Missing Connection header for WebSocket request")]
MissingConnectionHeader,
#[error("Missing Sec-WebSocket-Key header")]
MissingSecWebSocketKey,
#[error("Upgrade header must be `websocket`")]
InvalidUpgradeHeader,
#[error("Invalid Connection header for WebSocket request")]
InvalidConnectionHeader,
#[error("Unsupported Sec-WebSocket-Version. Only version 13 is accepted")]
UnsupportedVersion,
}
struct SendSyncWebSocketPair(ffi::WebSocketPair);
unsafe impl Send for SendSyncWebSocketPair {}
unsafe impl Sync for SendSyncWebSocketPair {}
pub struct WebSocketUpgrade {
pair: Option<SendSyncWebSocketPair>,
protocols: Vec<String>,
config: WebSocketConfig,
}
impl WebSocketUpgrade {
fn new() -> Self {
Self {
pair: Some(SendSyncWebSocketPair(ffi::WebSocketPair::new())),
protocols: Vec::new(),
config: WebSocketConfig::default(),
}
}
#[must_use]
pub fn protocols<I, S>(mut self, protocols: I) -> Self
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.protocols = protocols
.into_iter()
.map(|s| s.as_ref().to_string())
.collect();
self
}
#[must_use]
pub const fn config(mut self, config: WebSocketConfig) -> Self {
self.config = config;
self
}
#[must_use]
pub fn max_message_size(mut self, max_size: Option<usize>) -> Self {
self.config.max_message_size = max_size;
self
}
pub fn on_upgrade<F, Fut>(mut self, callback: F) -> WebSocketUpgradeResponder
where
F: FnOnce(WebSocket) -> Fut + 'static,
Fut: std::future::Future<Output = ()> + 'static,
{
let pair = self.pair.take().expect("pair already consumed").0;
let server = pair.server();
let client = pair.client();
server.accept();
let socket = WebSocket::from_ffi_socket(server, self.config);
wasm_bindgen_futures::spawn_local(async move {
callback(socket).await;
});
WebSocketUpgradeResponder {
client: SendSyncWebSocket(client),
}
}
}
impl std::fmt::Debug for WebSocketUpgrade {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WebSocketUpgrade")
.field("protocols", &self.protocols)
.field("config", &self.config)
.finish()
}
}
fn header_has_token(value: &header::HeaderValue, token: &str) -> bool {
value
.to_str()
.map(|value| {
value
.split(',')
.any(|part| part.trim().eq_ignore_ascii_case(token))
})
.unwrap_or(false)
}
impl Extractor for WebSocketUpgrade {
type Error = WebSocketUpgradeError;
async fn extract(request: &mut Request) -> Result<Self, Self::Error> {
if request.method() != Method::GET {
return Err(WebSocketUpgradeError::MethodNotAllowed);
}
let headers = request.headers();
headers
.get(header::SEC_WEBSOCKET_KEY)
.ok_or(WebSocketUpgradeError::MissingSecWebSocketKey)?;
let connection = headers
.get(header::CONNECTION)
.ok_or(WebSocketUpgradeError::MissingConnectionHeader)?;
if !header_has_token(connection, "upgrade") {
return Err(WebSocketUpgradeError::InvalidConnectionHeader);
}
let upgrade_header = headers
.get(header::UPGRADE)
.ok_or(WebSocketUpgradeError::MissingUpgradeHeader)?;
if !upgrade_header
.to_str()
.map(|value| value.eq_ignore_ascii_case("websocket"))
.unwrap_or(false)
{
return Err(WebSocketUpgradeError::InvalidUpgradeHeader);
}
match headers.get(header::SEC_WEBSOCKET_VERSION) {
Some(version) if version == "13" => {}
_ => return Err(WebSocketUpgradeError::UnsupportedVersion),
}
Ok(WebSocketUpgrade::new())
}
}
#[derive(Clone)]
pub struct SendSyncWebSocket(pub(crate) ffi::WebSocket);
impl SendSyncWebSocket {
pub fn into_inner(self) -> ffi::WebSocket {
self.0
}
}
impl std::fmt::Debug for SendSyncWebSocket {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SendSyncWebSocket").finish_non_exhaustive()
}
}
unsafe impl Send for SendSyncWebSocket {}
unsafe impl Sync for SendSyncWebSocket {}
pub struct WebSocketUpgradeResponder {
client: SendSyncWebSocket,
}
impl std::fmt::Debug for WebSocketUpgradeResponder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WebSocketUpgradeResponder").finish()
}
}
impl Responder for WebSocketUpgradeResponder {
type Error = std::convert::Infallible;
fn respond_to(self, _request: &Request, response: &mut Response) -> Result<(), Self::Error> {
*response.status_mut() = StatusCode::SWITCHING_PROTOCOLS;
response.extensions_mut().insert(self.client);
Ok(())
}
}