use crate::Error;
use super::FromRequestParts;
use crate::Result;
use async_trait::async_trait;
use axol_http::{
Method, StatusCode, extensions::Removed, request::RequestPartsRef, response::Response,
};
use futures_util::{
sink::{Sink, SinkExt},
stream::{Stream, StreamExt},
};
pub use hyper::Error as HyperError;
use hyper::upgrade::{OnUpgrade, Upgraded};
use hyper_util::rt::TokioIo;
use sha1::{Digest, Sha1};
use std::{
borrow::Cow,
future::Future,
pin::Pin,
str::Utf8Error,
task::{Context, Poll},
};
pub use tokio_tungstenite::tungstenite::Error as WsError;
use tokio_tungstenite::{
WebSocketStream,
tungstenite::{
self as ts,
protocol::{self, WebSocketConfig},
},
};
#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
pub struct WebSocketUpgrade<F = DefaultOnFailedUpgrade> {
config: WebSocketConfig,
protocol: Option<String>,
sec_websocket_key: String,
on_upgrade: OnUpgrade,
on_failed_upgrade: F,
sec_websocket_protocol: Option<String>,
}
impl<F> std::fmt::Debug for WebSocketUpgrade<F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WebSocketUpgrade")
.field("config", &self.config)
.field("protocol", &self.protocol)
.field("sec_websocket_key", &self.sec_websocket_key)
.field("sec_websocket_protocol", &self.sec_websocket_protocol)
.finish_non_exhaustive()
}
}
impl<F> WebSocketUpgrade<F> {
pub fn write_buffer_size(mut self, size: usize) -> Self {
self.config.write_buffer_size = size;
self
}
pub fn max_write_buffer_size(mut self, max: usize) -> Self {
self.config.max_write_buffer_size = max;
self
}
pub fn max_message_size(mut self, max: usize) -> Self {
self.config.max_message_size = Some(max);
self
}
pub fn max_frame_size(mut self, max: usize) -> Self {
self.config.max_frame_size = Some(max);
self
}
pub fn accept_unmasked_frames(mut self, accept: bool) -> Self {
self.config.accept_unmasked_frames = accept;
self
}
pub fn protocols<I>(mut self, protocols: I) -> Self
where
I: IntoIterator,
I::Item: Into<Cow<'static, str>>,
{
if let Some(req_protocols) = self.sec_websocket_protocol.as_ref() {
self.protocol = protocols
.into_iter()
.map(Into::into)
.find(|protocol| {
req_protocols
.split(',')
.any(|req_protocol| req_protocol.trim() == protocol)
})
.map(|protocol| protocol.into_owned());
}
self
}
pub fn on_failed_upgrade<C>(self, callback: C) -> WebSocketUpgrade<C>
where
C: OnFailedUpgrade,
{
WebSocketUpgrade {
config: self.config,
protocol: self.protocol,
sec_websocket_key: self.sec_websocket_key,
on_upgrade: self.on_upgrade,
on_failed_upgrade: callback,
sec_websocket_protocol: self.sec_websocket_protocol,
}
}
#[must_use = "to setup the WebSocket connection, this response must be returned"]
pub fn on_upgrade<C, Fut>(self, callback: C) -> Response
where
C: FnOnce(WebSocket) -> Fut + Send + 'static,
Fut: Future<Output = ()> + Send + 'static,
F: OnFailedUpgrade,
{
let on_upgrade = self.on_upgrade;
let config = self.config;
let on_failed_upgrade = self.on_failed_upgrade;
let protocol = self.protocol.clone();
tokio::spawn(async move {
let upgraded = match on_upgrade.await {
Ok(upgraded) => upgraded,
Err(err) => {
on_failed_upgrade.call(err);
return;
}
};
let socket = WebSocketStream::from_raw_socket(
TokioIo::new(upgraded),
protocol::Role::Server,
Some(config),
)
.await;
let socket = WebSocket {
inner: socket,
protocol,
};
callback(socket).await;
});
let mut builder = Response::builder()
.status(StatusCode::SwitchingProtocols)
.header("connection", "upgrade")
.header("upgrade", "websocket")
.header(
"sec-websocket-accept",
sign(self.sec_websocket_key.as_bytes()),
);
if let Some(protocol) = self.protocol {
builder = builder.header("sec-websocket-protocol", protocol);
}
builder.body(()).unwrap()
}
}
pub trait OnFailedUpgrade: Send + 'static {
fn call(self, error: HyperError);
}
impl<F> OnFailedUpgrade for F
where
F: FnOnce(HyperError) + Send + 'static,
{
fn call(self, error: HyperError) {
self(error)
}
}
#[non_exhaustive]
#[derive(Debug)]
pub struct DefaultOnFailedUpgrade;
impl OnFailedUpgrade for DefaultOnFailedUpgrade {
fn call(self, _error: HyperError) {}
}
#[async_trait]
impl<'a> FromRequestParts<'a> for WebSocketUpgrade<DefaultOnFailedUpgrade> {
async fn from_request_parts(request: RequestPartsRef<'a>) -> Result<Self> {
if request.method != Method::Get {
return Err(Error::method_not_allowed("Request method must be `GET`"));
}
if !request
.headers
.get("connection")
.map(|x| x.eq_ignore_ascii_case("upgrade"))
.unwrap_or_default()
{
return Err(Error::bad_request(
"`Connection` header did not include 'upgrade'",
));
}
if request.headers.get("upgrade") != Some("websocket") {
return Err(Error::bad_request(
"`Upgrade` header did not include 'websocket'",
));
}
if request.headers.get("sec-websocket-version") != Some("13") {
return Err(Error::bad_request(
"`Sec-WebSocket-Version` header did not include '13'",
));
}
let Some(sec_websocket_key) = request.headers.get("sec-websocket-key") else {
return Err(Error::bad_request("`Sec-WebSocket-Key` header missing"));
};
let on_upgrade = request.extensions.remove::<OnUpgrade>().ok_or_else(|| {
Error::response((
StatusCode::UpgradeRequired,
"WebSocket request couldn't be upgraded since no upgrade state was present",
))
})?;
let on_upgrade = match on_upgrade {
Removed::Invalidated | Removed::Referenced(_) => {
return Err(Error::response((
StatusCode::UpgradeRequired,
"WebSocket request couldn't be upgraded since no upgrade state was available",
)));
}
Removed::Removed(x) => x,
};
let sec_websocket_protocol = request
.headers
.get("sec-websocket-protocol")
.map(|x| x.to_string());
Ok(Self {
config: Default::default(),
protocol: None,
sec_websocket_key: sec_websocket_key.to_string(),
on_upgrade,
sec_websocket_protocol,
on_failed_upgrade: DefaultOnFailedUpgrade,
})
}
}
#[derive(Debug)]
pub struct WebSocket {
inner: WebSocketStream<TokioIo<Upgraded>>,
protocol: Option<String>,
}
impl WebSocket {
pub async fn recv(&mut self) -> Option<Result<Message, WsError>> {
self.next().await
}
pub async fn send(&mut self, msg: Message) -> Result<(), WsError> {
self.inner.send(msg.into_tungstenite()).await
}
pub async fn close(mut self) -> Result<(), WsError> {
self.inner.close(None).await
}
pub fn protocol(&self) -> Option<&str> {
self.protocol.as_deref()
}
}
impl Stream for WebSocket {
type Item = Result<Message, WsError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
loop {
match futures_util::ready!(self.inner.poll_next_unpin(cx)) {
Some(Ok(msg)) => {
if let Some(msg) = Message::from_tungstenite(msg) {
return Poll::Ready(Some(Ok(msg)));
}
}
Some(Err(err)) => return Poll::Ready(Some(Err(err))),
None => return Poll::Ready(None),
}
}
}
}
impl Sink<Message> for WebSocket {
type Error = WsError;
fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Pin::new(&mut self.inner).poll_ready(cx)
}
fn start_send(mut self: Pin<&mut Self>, item: Message) -> Result<(), Self::Error> {
Pin::new(&mut self.inner).start_send(item.into_tungstenite())
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Pin::new(&mut self.inner).poll_flush(cx)
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Pin::new(&mut self.inner).poll_close(cx)
}
}
pub type CloseCode = u16;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct CloseFrame<'t> {
pub code: CloseCode,
pub reason: Cow<'t, str>,
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum Message {
Text(String),
Binary(Vec<u8>),
Ping(Vec<u8>),
Pong(Vec<u8>),
Close(Option<CloseFrame<'static>>),
}
impl Message {
fn into_tungstenite(self) -> ts::Message {
match self {
Self::Text(text) => ts::Message::Text(text.into()),
Self::Binary(binary) => ts::Message::Binary(binary.into()),
Self::Ping(ping) => ts::Message::Ping(ping.into()),
Self::Pong(pong) => ts::Message::Pong(pong.into()),
Self::Close(Some(close)) => ts::Message::Close(Some(ts::protocol::CloseFrame {
code: ts::protocol::frame::coding::CloseCode::from(close.code),
reason: close.reason.into_owned().into(),
})),
Self::Close(None) => ts::Message::Close(None),
}
}
fn from_tungstenite(message: ts::Message) -> Option<Self> {
match message {
ts::Message::Text(text) => Some(Self::Text(text.as_str().to_owned())),
ts::Message::Binary(binary) => Some(Self::Binary(binary.into())),
ts::Message::Ping(ping) => Some(Self::Ping(ping.into())),
ts::Message::Pong(pong) => Some(Self::Pong(pong.into())),
ts::Message::Close(Some(close)) => Some(Self::Close(Some(CloseFrame {
code: close.code.into(),
reason: Cow::Owned(close.reason.as_str().to_owned()),
}))),
ts::Message::Close(None) => Some(Self::Close(None)),
ts::Message::Frame(_) => None,
}
}
pub fn into_data(self) -> Vec<u8> {
match self {
Self::Text(string) => string.into_bytes(),
Self::Binary(data) | Self::Ping(data) | Self::Pong(data) => data,
Self::Close(None) => Vec::new(),
Self::Close(Some(frame)) => frame.reason.into_owned().into_bytes(),
}
}
pub fn into_text(self) -> Result<String, Utf8Error> {
match self {
Self::Text(string) => Ok(string),
Self::Binary(data) | Self::Ping(data) | Self::Pong(data) => {
String::from_utf8(data).map_err(|err| err.utf8_error())
}
Self::Close(None) => Ok(String::new()),
Self::Close(Some(frame)) => Ok(frame.reason.into_owned()),
}
}
pub fn to_text(&self) -> Result<&str, Utf8Error> {
match *self {
Self::Text(ref string) => Ok(string),
Self::Binary(ref data) | Self::Ping(ref data) | Self::Pong(ref data) => {
std::str::from_utf8(data)
}
Self::Close(None) => Ok(""),
Self::Close(Some(ref frame)) => Ok(&frame.reason),
}
}
}
impl From<String> for Message {
fn from(string: String) -> Self {
Message::Text(string)
}
}
impl<'s> From<&'s str> for Message {
fn from(string: &'s str) -> Self {
Message::Text(string.into())
}
}
impl<'b> From<&'b [u8]> for Message {
fn from(data: &'b [u8]) -> Self {
Message::Binary(data.into())
}
}
impl From<Vec<u8>> for Message {
fn from(data: Vec<u8>) -> Self {
Message::Binary(data)
}
}
impl From<Message> for Vec<u8> {
fn from(msg: Message) -> Self {
msg.into_data()
}
}
fn sign(key: &[u8]) -> String {
use base64::engine::Engine as _;
let mut sha1 = Sha1::default();
sha1.update(key);
sha1.update(&b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"[..]);
base64::engine::general_purpose::STANDARD.encode(sha1.finalize())
}
pub mod close_code {
pub const NORMAL: u16 = 1000;
pub const AWAY: u16 = 1001;
pub const PROTOCOL: u16 = 1002;
pub const UNSUPPORTED: u16 = 1003;
pub const STATUS: u16 = 1005;
pub const ABNORMAL: u16 = 1006;
pub const INVALID: u16 = 1007;
pub const POLICY: u16 = 1008;
pub const SIZE: u16 = 1009;
pub const EXTENSION: u16 = 1010;
pub const ERROR: u16 = 1011;
pub const RESTART: u16 = 1012;
pub const AGAIN: u16 = 1013;
}