diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 147285ddf..eadbf6f46 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,14 +2,11 @@ ## Unreleased - 2021-xx-xx ### Changed -* Bumped `rand` to `0.8`. * Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] +* Bumped `rand` to `0.8`. * Update `bytes` to `1.0`. [#1813] * Update `h2` to `0.3`. [#1813] - - -[#1813]: https://github.com/actix/actix-web/pull/1813 - +* The `ws::Message::Text` enum variant now contains a `bytestring::ByteString`. [#1864] ### Removed * Deprecated `on_connect` methods have been removed. Prefer the new @@ -22,6 +19,7 @@ [#1813]: https://github.com/actix/actix-web/pull/1813 [#1857]: https://github.com/actix/actix-web/pull/1857 +[#1864]: https://github.com/actix/actix-web/pull/1864 ## 2.2.0 - 2020-11-25 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index e98bcf76d..e80800d06 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -51,9 +51,10 @@ actix = { version = "0.11.0-beta.1", optional = true } base64 = "0.13" bitflags = "1.2" bytes = "1" +bytestring = "1" cookie = { version = "0.14.1", features = ["percent-encode"] } copyless = "0.1.4" -derive_more = "0.99.2" +derive_more = "0.99.5" either = "1.5.3" encoding_rs = "0.8" futures-channel = { version = "0.3.7", default-features = false } diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index ba4a48bba..84f5b3c73 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -1,47 +1,60 @@ use actix_codec::{Decoder, Encoder}; +use bitflags::bitflags; use bytes::{Bytes, BytesMut}; +use bytestring::ByteString; use super::frame::Parser; use super::proto::{CloseReason, OpCode}; use super::ProtocolError; -/// `WebSocket` Message +/// A WebSocket message. #[derive(Debug, PartialEq)] pub enum Message { - /// Text message - Text(String), - /// Binary message + /// Text message. + Text(ByteString), + + /// Binary message. Binary(Bytes), - /// Continuation + + /// Continuation. Continuation(Item), - /// Ping message + + /// Ping message. Ping(Bytes), - /// Pong message + + /// Pong message. Pong(Bytes), - /// Close message with optional reason + + /// Close message with optional reason. Close(Option), - /// No-op. Useful for actix-net services + + /// No-op. Useful for low-level services. Nop, } -/// `WebSocket` frame +/// A WebSocket frame. #[derive(Debug, PartialEq)] pub enum Frame { - /// Text frame, codec does not verify utf8 encoding + /// Text frame. Note that the codec does not validate UTF-8 encoding. Text(Bytes), - /// Binary frame + + /// Binary frame. Binary(Bytes), - /// Continuation + + /// Continuation. Continuation(Item), - /// Ping message + + /// Ping message. Ping(Bytes), - /// Pong message + + /// Pong message. Pong(Bytes), - /// Close message with optional reason + + /// Close message with optional reason. Close(Option), } -/// `WebSocket` continuation item +/// A `WebSocket` continuation item. #[derive(Debug, PartialEq)] pub enum Item { FirstText(Bytes), @@ -51,13 +64,13 @@ pub enum Item { } #[derive(Debug, Copy, Clone)] -/// WebSockets protocol codec +/// WebSocket protocol codec. pub struct Codec { flags: Flags, max_size: usize, } -bitflags::bitflags! { +bitflags! { struct Flags: u8 { const SERVER = 0b0000_0001; const CONTINUATION = 0b0000_0010; @@ -66,7 +79,7 @@ bitflags::bitflags! { } impl Codec { - /// Create new websocket frames decoder + /// Create new websocket frames decoder. pub fn new() -> Codec { Codec { max_size: 65_536, @@ -74,9 +87,9 @@ impl Codec { } } - /// Set max frame size + /// Set max frame size. /// - /// By default max size is set to 64kb + /// By default max size is set to 64kb. pub fn max_size(mut self, size: usize) -> Self { self.max_size = size; self diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index cd212fb7e..a2b093ce4 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -1,11 +1,11 @@ //! WebSocket protocol support. //! -//! To setup a `WebSocket`, first do web socket handshake then on success -//! convert `Payload` into a `WsStream` stream and then use `WsWriter` to -//! communicate with the peer. +//! To setup a WebSocket, first do web socket handshake then on success convert `Payload` into a +//! `WsStream` stream and then use `WsWriter` to communicate with the peer. + use std::io; -use derive_more::{Display, From}; +use derive_more::{Display, Error, From}; use http::{header, Method, StatusCode}; use crate::error::ResponseError; @@ -23,86 +23,103 @@ pub use self::dispatcher::Dispatcher; pub use self::frame::Parser; pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode}; -/// Websocket protocol errors -#[derive(Debug, Display, From)] +/// WebSocket protocol errors. +#[derive(Debug, Display, From, Error)] pub enum ProtocolError { - /// Received an unmasked frame from client - #[display(fmt = "Received an unmasked frame from client")] + /// Received an unmasked frame from client. + #[display(fmt = "Received an unmasked frame from client.")] UnmaskedFrame, - /// Received a masked frame from server - #[display(fmt = "Received a masked frame from server")] + + /// Received a masked frame from server. + #[display(fmt = "Received a masked frame from server.")] MaskedFrame, - /// Encountered invalid opcode - #[display(fmt = "Invalid opcode: {}", _0)] - InvalidOpcode(u8), + + /// Encountered invalid opcode. + #[display(fmt = "Invalid opcode: {}.", _0)] + InvalidOpcode(#[error(not(source))] u8), + /// Invalid control frame length - #[display(fmt = "Invalid control frame length: {}", _0)] - InvalidLength(usize), - /// Bad web socket op code - #[display(fmt = "Bad web socket op code")] + #[display(fmt = "Invalid control frame length: {}.", _0)] + InvalidLength(#[error(not(source))] usize), + + /// Bad opcode. + #[display(fmt = "Bad opcode.")] BadOpCode, + /// A payload reached size limit. #[display(fmt = "A payload reached size limit.")] Overflow, - /// Continuation is not started + + /// Continuation is not started. #[display(fmt = "Continuation is not started.")] ContinuationNotStarted, - /// Received new continuation but it is already started - #[display(fmt = "Received new continuation but it is already started")] + + /// Received new continuation but it is already started. + #[display(fmt = "Received new continuation but it is already started.")] ContinuationStarted, - /// Unknown continuation fragment - #[display(fmt = "Unknown continuation fragment.")] - ContinuationFragment(OpCode), - /// Io error - #[display(fmt = "io error: {}", _0)] + + /// Unknown continuation fragment. + #[display(fmt = "Unknown continuation fragment: {}.", _0)] + ContinuationFragment(#[error(not(source))] OpCode), + + /// I/O error. + #[display(fmt = "I/O error: {}", _0)] Io(io::Error), } -impl std::error::Error for ProtocolError {} - impl ResponseError for ProtocolError {} -/// Websocket handshake errors +/// WebSocket handshake errors #[derive(PartialEq, Debug, Display)] pub enum HandshakeError { - /// Only get method is allowed - #[display(fmt = "Method not allowed")] + /// Only get method is allowed. + #[display(fmt = "Method not allowed.")] GetMethodRequired, - /// Upgrade header if not set to websocket - #[display(fmt = "Websocket upgrade is expected")] + + /// Upgrade header if not set to websocket. + #[display(fmt = "WebSocket upgrade is expected.")] NoWebsocketUpgrade, - /// Connection header is not set to upgrade - #[display(fmt = "Connection upgrade is expected")] + + /// Connection header is not set to upgrade. + #[display(fmt = "Connection upgrade is expected.")] NoConnectionUpgrade, - /// Websocket version header is not set - #[display(fmt = "Websocket version header is required")] + + /// WebSocket version header is not set. + #[display(fmt = "WebSocket version header is required.")] NoVersionHeader, - /// Unsupported websocket version - #[display(fmt = "Unsupported version")] + + /// Unsupported websocket version. + #[display(fmt = "Unsupported version.")] UnsupportedVersion, - /// Websocket key is not set or wrong - #[display(fmt = "Unknown websocket key")] + + /// WebSocket key is not set or wrong. + #[display(fmt = "Unknown websocket key.")] BadWebsocketKey, } impl ResponseError for HandshakeError { fn error_response(&self) -> Response { - match *self { + match self { HandshakeError::GetMethodRequired => Response::MethodNotAllowed() .header(header::ALLOW, "GET") .finish(), + HandshakeError::NoWebsocketUpgrade => Response::BadRequest() .reason("No WebSocket UPGRADE header found") .finish(), + HandshakeError::NoConnectionUpgrade => Response::BadRequest() .reason("No CONNECTION upgrade") .finish(), + HandshakeError::NoVersionHeader => Response::BadRequest() .reason("Websocket version header is required") .finish(), + HandshakeError::UnsupportedVersion => Response::BadRequest() .reason("Unsupported version") .finish(), + HandshakeError::BadWebsocketKey => { Response::BadRequest().reason("Handshake error").finish() } diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index 345681429..6fa3debc5 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -2,21 +2,27 @@ use std::convert::{From, Into}; use std::fmt; use self::OpCode::*; -/// Operation codes as part of rfc6455. +/// Operation codes as part of RFC6455. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum OpCode { /// Indicates a continuation frame of a fragmented message. Continue, + /// Indicates a text data frame. Text, + /// Indicates a binary data frame. Binary, + /// Indicates a close control frame. Close, + /// Indicates a ping control frame. Ping, + /// Indicates a pong control frame. Pong, + /// Indicates an invalid opcode was received. Bad, } diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index e31f2745c..976fc9164 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -74,7 +74,7 @@ async fn service(msg: ws::Frame) -> Result { let msg = match msg { ws::Frame::Ping(msg) => ws::Message::Pong(msg), ws::Frame::Text(text) => { - ws::Message::Text(String::from_utf8_lossy(&text).to_string()) + ws::Message::Text(String::from_utf8_lossy(&text).into_owned().into()) } ws::Frame::Binary(bin) => ws::Message::Binary(bin), ws::Frame::Continuation(item) => ws::Message::Continuation(item), @@ -101,10 +101,7 @@ async fn test_simple() { // client service let mut framed = srv.ws().await.unwrap(); - framed - .send(ws::Message::Text("text".to_string())) - .await - .unwrap(); + framed.send(ws::Message::Text("text".into())).await.unwrap(); let (item, mut framed) = framed.into_future().await; assert_eq!( item.unwrap().unwrap(), diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index e47f09135..dab35953a 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -3,8 +3,10 @@ ## Unreleased - 2021-xx-xx * Update `pin-project` to `1.0`. * Update `bytes` to `1.0`. [#1813] +* `WebsocketContext::text` now takes an `Into`. [#1864] [#1813]: https://github.com/actix/actix-web/pull/1813 +[#1864]: https://github.com/actix/actix-web/pull/1864 ## 3.0.0 - 2020-09-11 * No significant changes from `3.0.0-beta.2`. diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 28b9d6fa2..dac4060ba 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -22,6 +22,7 @@ actix-http = "2.0.0" actix-web = { version = "3.0.0", default-features = false } bytes = "1" +bytestring = "1" futures-core = { version = "0.3.7", default-features = false } pin-project = "1.0.0" tokio = { version = "1", features = ["sync"] } diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 9dd7bf500..60942c6c6 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -1,9 +1,10 @@ -//! Websocket integration -use std::collections::VecDeque; +//! Websocket integration. + use std::future::Future; use std::io; use std::pin::Pin; use std::task::{Context, Poll}; +use std::{collections::VecDeque, convert::TryFrom}; use actix::dev::{ AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, @@ -24,10 +25,11 @@ use actix_web::error::{Error, PayloadError}; use actix_web::http::{header, Method, StatusCode}; use actix_web::{HttpRequest, HttpResponse}; use bytes::{Bytes, BytesMut}; +use bytestring::ByteString; use futures_core::Stream; use tokio::sync::oneshot::Sender; -/// Do websocket handshake and start ws actor. +/// Perform WebSocket handshake and start actor. pub fn start(actor: A, req: &HttpRequest, stream: T) -> Result where A: Actor> @@ -38,7 +40,7 @@ where Ok(res.streaming(WebsocketContext::create(actor, stream))) } -/// Do websocket handshake and start ws actor. +/// Perform WebSocket handshake and start actor. /// /// `req` is an HTTP Request that should be requesting a websocket protocol /// change. `stream` should be a `Bytes` stream (such as @@ -338,13 +340,13 @@ where /// Send text frame #[inline] - pub fn text>(&mut self, text: T) { + pub fn text(&mut self, text: impl Into) { self.write_raw(Message::Text(text.into())); } /// Send binary frame #[inline] - pub fn binary>(&mut self, data: B) { + pub fn binary(&mut self, data: impl Into) { self.write_raw(Message::Binary(data.into())); } @@ -528,16 +530,14 @@ where } Some(frm) => { let msg = match frm { - Frame::Text(data) => Message::Text( - std::str::from_utf8(&data) - .map_err(|e| { - ProtocolError::Io(io::Error::new( - io::ErrorKind::Other, - format!("{}", e), - )) - })? - .to_string(), - ), + Frame::Text(data) => { + Message::Text(ByteString::try_from(data).map_err(|e| { + ProtocolError::Io(io::Error::new( + io::ErrorKind::Other, + format!("{}", e), + )) + })?) + } Frame::Binary(data) => Message::Binary(data), Frame::Ping(s) => Message::Ping(s), Frame::Pong(s) => Message::Pong(s), diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index a5233e5e0..7fd59a4a7 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -38,10 +38,7 @@ async fn test_simple() { // client service let mut framed = srv.ws().await.unwrap(); - framed - .send(ws::Message::Text("text".to_string())) - .await - .unwrap(); + framed.send(ws::Message::Text("text".into())).await.unwrap(); let item = framed.next().await.unwrap().unwrap(); assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text"))); diff --git a/awc/src/lib.rs b/awc/src/lib.rs index fb6ed086a..d9db7a2cf 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -76,7 +76,7 @@ //! .await?; //! //! connection -//! .send(awc::ws::Message::Text("Echo".to_string())) +//! .send(awc::ws::Message::Text("Echo".into())) //! .await?; //! let response = connection.next().await.unwrap()?; //! # assert_eq!(response, awc::ws::Frame::Text("Echo".as_bytes().into())); diff --git a/awc/src/ws.rs b/awc/src/ws.rs index a1fa07d8d..b90d0942b 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -17,7 +17,7 @@ //! .unwrap(); //! //! connection -//! .send(ws::Message::Text("Echo".to_string())) +//! .send(ws::Message::Text("Echo".into())) //! .await //! .unwrap(); //! let response = connection.next().await.unwrap().unwrap(); diff --git a/awc/tests/test_ws.rs b/awc/tests/test_ws.rs index 1c1068668..8eb912dac 100644 --- a/awc/tests/test_ws.rs +++ b/awc/tests/test_ws.rs @@ -11,7 +11,7 @@ async fn ws_service(req: ws::Frame) -> Result { match req { ws::Frame::Ping(msg) => Ok(ws::Message::Pong(msg)), ws::Frame::Text(text) => Ok(ws::Message::Text( - String::from_utf8(Vec::from(text.as_ref())).unwrap(), + String::from_utf8(Vec::from(text.as_ref())).unwrap().into(), )), ws::Frame::Binary(bin) => Ok(ws::Message::Binary(bin)), ws::Frame::Close(reason) => Ok(ws::Message::Close(reason)), @@ -43,10 +43,7 @@ async fn test_simple() { // client service let mut framed = srv.ws().await.unwrap(); - framed - .send(ws::Message::Text("text".to_string())) - .await - .unwrap(); + framed.send(ws::Message::Text("text".into())).await.unwrap(); let item = framed.next().await.unwrap().unwrap(); assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text")));