From d52f69a00db95b50eb8164ba972384aabe8eb675 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Tue, 4 Apr 2017 11:20:09 -0400 Subject: [PATCH 01/52] add websocket encode/decode codec for tokio --- .gitignore | 3 + Cargo.toml | 6 +- src/client/mod.rs | 2 - src/codec.rs | 182 ++++++++++++++++++++++++++++++++++++++++++ src/dataframe.rs | 70 ++++++++++------ src/lib.rs | 8 ++ src/message.rs | 7 +- src/receiver.rs | 1 + src/stream.rs | 23 +++++- src/ws/dataframe.rs | 17 ++-- src/ws/util/header.rs | 4 +- src/ws/util/mask.rs | 16 ++-- 12 files changed, 280 insertions(+), 59 deletions(-) create mode 100644 src/codec.rs diff --git a/.gitignore b/.gitignore index bfe5de37db..2c4fdf652d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ /target /Cargo.lock +# emacs +*.#*.rs + # Windows image file caches Thumbs.db ehthumbs.db diff --git a/Cargo.toml b/Cargo.toml index 3199a39d91..d4a378b97d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,9 @@ bitflags = "^0.8" rand = "^0.3" byteorder = "^1.0" sha1 = "^0.2" +tokio-core = { version = "^0.1", optional = true } +tokio-io = { version = "^0.1", optional = true } +bytes = { version = "^0.4", optional = true } openssl = { version = "^0.9.10", optional = true } base64 = "^0.5" @@ -31,6 +34,7 @@ base64 = "^0.5" serde_json = "^1.0" [features] -default = ["ssl"] +default = ["ssl", "async"] ssl = ["openssl"] nightly = ["hyper/nightly"] +async = ["tokio-core", "tokio-io", "bytes"] diff --git a/src/client/mod.rs b/src/client/mod.rs index 158e52bf95..4f919df683 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -129,8 +129,6 @@ impl Client Client { headers: headers, stream: stream, - // NOTE: these are always true & false, see - // https://tools.ietf.org/html/rfc6455#section-5 sender: Sender::new(out_mask), receiver: Receiver::new(in_mask), } diff --git a/src/codec.rs b/src/codec.rs new file mode 100644 index 0000000000..8505b8693f --- /dev/null +++ b/src/codec.rs @@ -0,0 +1,182 @@ +//! TODO: docs + +use std::borrow::Borrow; +use std::marker::PhantomData; +use std::io::Cursor; +use std::mem; +use std::io; + +use tokio_io::codec::Decoder; +use tokio_io::codec::Encoder; +use bytes::BytesMut; +use bytes::BufMut; + +use dataframe::DataFrame; +use message::Message; +use ws::dataframe::DataFrame as DataFrameTrait; +use ws::message::Message as MessageTrait; +use ws::util::header::read_header; +use result::WebSocketError; + +/************** + * Dataframes * + **************/ + +/// TODO: docs +pub struct DataFrameCodec { + masked: bool, + frame_type: PhantomData, +} + +impl DataFrameCodec { + /// TODO: docs + pub fn default(masked: bool) -> DataFrameCodec { + DataFrameCodec::new(masked) + } + + /// TODO: docs + pub fn new(masked: bool) -> DataFrameCodec { + DataFrameCodec { + masked: masked, + frame_type: PhantomData, + } + } +} + +impl Decoder for DataFrameCodec { + type Item = DataFrame; + type Error = WebSocketError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + let (header, bytes_read) = { + // we'll make a fake reader and keep track of the bytes read + let mut reader = Cursor::new(src.as_ref()); + + // read header to get the size, bail if not enough + let header = match read_header(&mut reader) { + Ok(head) => head, + // TODO: check if this is the correct error + Err(WebSocketError::IoError(ref e)) if e.kind() == io::ErrorKind::UnexpectedEof => { + return Ok(None) + } + Err(e) => return Err(e), + }; + + (header, reader.position()) + }; + + // check if we have enough bytes to continue + if header.len + bytes_read > src.len() as u64 { + return Ok(None); + } + + let body = src.split_off(bytes_read as usize).to_vec(); + // use up the rest of the buffer since we already copied it to header + let _ = src.take(); + + // construct a dataframe + Ok(Some(DataFrame::read_dataframe_body(header, body, self.masked)?)) + } +} + +// TODO: try to allow encoding of many kinds of dataframes +impl Encoder for DataFrameCodec + where D: Borrow +{ + type Item = D; + type Error = WebSocketError; + + fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { + // TODO: check size and grow dst accordingly + item.borrow().write_to(&mut dst.writer(), self.masked) + } +} + +/************ + * Messages * + ************/ + +/// TODO: docs +pub struct MessageCodec<'m, M> + where M: 'm +{ + buffer: Vec, + dataframe_codec: DataFrameCodec, + message_type: PhantomData, +} + +impl<'m, M> MessageCodec<'m, M> { + /// TODO: docs + pub fn default(masked: bool) -> MessageCodec<'m, Message<'m>> { + MessageCodec::new(masked) + } + + /// TODO: docs + pub fn new(masked: bool) -> MessageCodec<'m, M> { + MessageCodec { + buffer: Vec::new(), + dataframe_codec: DataFrameCodec { + masked: masked, + frame_type: PhantomData, + }, + message_type: PhantomData, + } + } +} + +impl<'m, M> Decoder for MessageCodec<'m, M> + where M: 'm +{ + type Item = Message<'m>; + type Error = WebSocketError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + while let Some(frame) = self.dataframe_codec.decode(src)? { + let is_first = self.buffer.is_empty(); + let finished = frame.finished; + + match frame.opcode as u8 { + // continuation code + 0 if is_first => { + return Err(WebSocketError::ProtocolError("Unexpected continuation data frame opcode")); + } + // control frame + 8...15 => { + mem::replace(&mut self.buffer, vec![frame]); + } + // data frame + 1...7 if !is_first => { + return Err(WebSocketError::ProtocolError("Unexpected data frame opcode")); + } + // its good + _ => { + self.buffer.push(frame); + } + }; + + if finished { + let buffer = mem::replace(&mut self.buffer, Vec::new()); + return Ok(Some(Message::from_dataframes(buffer)?)); + } + } + + Ok(None) + } +} + +impl<'m, M> Encoder for MessageCodec<'m, M> + where M: Borrow> +{ + type Item = M; + type Error = WebSocketError; + + fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { + for ref dataframe in item.borrow().dataframes() { + // TODO: check size and grow dst accordingly + dataframe.write_to(&mut dst.writer(), self.dataframe_codec.masked)? + } + Ok(()) + } +} + +// TODO: add tests to check boundary cases for reading dataframes diff --git a/src/dataframe.rs b/src/dataframe.rs index d7e32a4c73..e2c0ebd043 100644 --- a/src/dataframe.rs +++ b/src/dataframe.rs @@ -3,6 +3,7 @@ use std::io::Read; use std::borrow::Cow; use result::{WebSocketResult, WebSocketError}; use ws::dataframe::DataFrame as DataFrameable; +use ws::util::header::DataFrameHeader; use ws::util::header as dfh; use ws::util::mask; @@ -37,40 +38,57 @@ impl DataFrame { } } - /// Reads a DataFrame from a Reader. - pub fn read_dataframe(reader: &mut R, should_be_masked: bool) -> WebSocketResult - where R: Read - { - let header = try!(dfh::read_header(reader)); + /// TODO: docs + pub fn read_dataframe_body( + header: DataFrameHeader, + body: Vec, + should_be_masked: bool, + ) -> WebSocketResult { + let finished = header.flags.contains(dfh::FIN); - Ok(DataFrame { - finished: header.flags.contains(dfh::FIN), - reserved: [ + let reserved = [ header.flags.contains(dfh::RSV1), header.flags.contains(dfh::RSV2), header.flags.contains(dfh::RSV3), - ], - opcode: Opcode::new(header.opcode).expect("Invalid header opcode!"), - data: match header.mask { - Some(mask) => { - if !should_be_masked { - return Err(WebSocketError::DataFrameError("Expected unmasked data frame")); + ]; + + let opcode = Opcode::new(header.opcode).expect("Invalid header opcode!"); + + let data = match header.mask { + Some(mask) => { + if !should_be_masked { + return Err(WebSocketError::DataFrameError("Expected unmasked data frame")); + } + // TODO: move data to avoid copy? + mask::mask_data(mask, &body) } - let mut data: Vec = Vec::with_capacity(header.len as usize); - try!(reader.take(header.len).read_to_end(&mut data)); - mask::mask_data(mask, &data) - } - None => { - if should_be_masked { - return Err(WebSocketError::DataFrameError("Expected masked data frame")); + None => { + if should_be_masked { + return Err(WebSocketError::DataFrameError("Expected masked data frame")); + } + body } - let mut data: Vec = Vec::with_capacity(header.len as usize); - try!(reader.take(header.len).read_to_end(&mut data)); - data - } - }, + }; + + Ok(DataFrame { + finished: finished, + reserved: reserved, + opcode: opcode, + data: data, }) } + + /// Reads a DataFrame from a Reader. + pub fn read_dataframe(reader: &mut R, should_be_masked: bool) -> WebSocketResult + where R: Read + { + let header = try!(dfh::read_header(reader)); + + let mut data: Vec = Vec::with_capacity(header.len as usize); + reader.take(header.len).read_to_end(&mut data)?; + + DataFrame::read_dataframe_body(header, data, should_be_masked) + } } impl DataFrameable for DataFrame { diff --git a/src/lib.rs b/src/lib.rs index cbea7fb91d..15f4d54f72 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,6 +45,12 @@ extern crate sha1; #[cfg(feature="ssl")] extern crate openssl; extern crate base64; +#[cfg(feature="async")] +extern crate tokio_core; +#[cfg(feature="async")] +extern crate tokio_io; +#[cfg(feature="async")] +extern crate bytes; #[macro_use] extern crate bitflags; @@ -86,3 +92,5 @@ pub mod stream; pub mod header; pub mod receiver; pub mod sender; +#[cfg(feature="async")] +pub mod codec; diff --git a/src/message.rs b/src/message.rs index 58276dea5b..fc0ffb1f4e 100644 --- a/src/message.rs +++ b/src/message.rs @@ -25,6 +25,7 @@ pub enum Type { Close = 8, } +// TODO: split up message into owned received and borrowed send /// Represents a WebSocket message. /// /// This message also has the ability to not own its payload, and stores its entire payload in @@ -146,9 +147,7 @@ impl<'a> ws::dataframe::DataFrame for Message<'a> { self.payload.len() + if self.cd_status_code.is_some() { 2 } else { 0 } } - fn write_payload(&self, socket: &mut W) -> WebSocketResult<()> - where W: Write - { + fn write_payload(&self, socket: &mut Write) -> WebSocketResult<()> { if let Some(reason) = self.cd_status_code { try!(socket.write_u16::(reason)); } @@ -157,6 +156,8 @@ impl<'a> ws::dataframe::DataFrame for Message<'a> { } } +// TODO: separate reading writing here, or maybe just make an IntoMessage trait +// so you can support both owned and borrowed messages impl<'a, 'b> ws::Message<'b, &'b Message<'a>> for Message<'a> { type DataFrameIterator = Take>>; diff --git a/src/receiver.rs b/src/receiver.rs index 91aac7f381..52847fa1e7 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -136,6 +136,7 @@ impl ws::Receiver for Receiver { } } + // TODO: this is slow, easy fix let buffer = self.buffer.clone(); self.buffer.clear(); diff --git a/src/stream.rs b/src/stream.rs index abbb0d66e7..57c8f1830f 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -7,14 +7,22 @@ pub use std::net::TcpStream; pub use std::net::Shutdown; #[cfg(feature="ssl")] pub use openssl::ssl::{SslStream, SslContext}; +#[cfg(feature="async")] +pub use tokio_io::{AsyncWrite, AsyncRead}; +#[cfg(feature="async")] +pub use tokio_io::io::{ReadHalf, WriteHalf}; /// Represents a stream that can be read from, and written to. /// This is an abstraction around readable and writable things to be able /// to speak websockets over ssl, tcp, unix sockets, etc. pub trait Stream: Read + Write {} - impl Stream for S where S: Read + Write {} +/// TODO: docs +#[cfg(feature="async")] +pub trait AsyncStream: AsyncRead + AsyncWrite {} +impl AsyncStream for S where S: AsyncRead + AsyncWrite {} + /// a `Stream` that can also be used as a borrow to a `TcpStream` /// this is useful when you want to set `TcpStream` options on a /// `Stream` like `nonblocking`. @@ -49,6 +57,7 @@ impl Splittable for ReadWritePair } } +#[cfg(not(feature="async"))] impl Splittable for TcpStream { type Reader = TcpStream; type Writer = TcpStream; @@ -58,6 +67,18 @@ impl Splittable for TcpStream { } } +#[cfg(feature="async")] +impl Splittable for S + where S: AsyncStream +{ + type Reader = ReadHalf; + type Writer = WriteHalf; + + fn split(self) -> io::Result<(Self::Reader, Self::Writer)> { + Ok(self.split()) + } +} + /// The ability access a borrow to an underlying TcpStream, /// so one can set options on the stream such as `nonblocking`. pub trait AsTcpStream { diff --git a/src/ws/dataframe.rs b/src/ws/dataframe.rs index a82a712b2d..6c7271b17e 100644 --- a/src/ws/dataframe.rs +++ b/src/ws/dataframe.rs @@ -30,17 +30,14 @@ pub trait DataFrame { } /// Write the payload to a writer - fn write_payload(&self, socket: &mut W) -> WebSocketResult<()> - where W: Write - { + fn write_payload(&self, socket: &mut Write) -> WebSocketResult<()> { try!(socket.write_all(&*self.payload())); Ok(()) } + // TODO: the error can probably be changed to an io error /// Writes a DataFrame to a Writer. - fn write_to(&self, writer: &mut W, mask: bool) -> WebSocketResult<()> - where W: Write - { + fn write_to(&self, writer: &mut Write, mask: bool) -> WebSocketResult<()> { let mut flags = dfh::DataFrameFlags::empty(); if self.is_last() { flags.insert(dfh::FIN); @@ -110,16 +107,12 @@ impl<'a, D> DataFrame for &'a D } #[inline(always)] - fn write_payload(&self, socket: &mut W) -> WebSocketResult<()> - where W: Write - { + fn write_payload(&self, socket: &mut Write) -> WebSocketResult<()> { D::write_payload(self, socket) } #[inline(always)] - fn write_to(&self, writer: &mut W, mask: bool) -> WebSocketResult<()> - where W: Write - { + fn write_to(&self, writer: &mut Write, mask: bool) -> WebSocketResult<()> { D::write_to(self, writer, mask) } } diff --git a/src/ws/util/header.rs b/src/ws/util/header.rs index 89f3de10a4..a69cc62b4e 100644 --- a/src/ws/util/header.rs +++ b/src/ws/util/header.rs @@ -32,9 +32,7 @@ pub struct DataFrameHeader { } /// Writes a data frame header. -pub fn write_header(writer: &mut W, header: DataFrameHeader) -> WebSocketResult<()> - where W: Write -{ +pub fn write_header(writer: &mut Write, header: DataFrameHeader) -> WebSocketResult<()> { if header.opcode > 0xF { return Err(WebSocketError::DataFrameError("Invalid data frame opcode")); diff --git a/src/ws/util/mask.rs b/src/ws/util/mask.rs index 114727881a..66c552a29d 100644 --- a/src/ws/util/mask.rs +++ b/src/ws/util/mask.rs @@ -6,20 +6,16 @@ use std::mem; /// Struct to pipe data into another writer, /// while masking the data being written -pub struct Masker<'w, W> - where W: Write + 'w -{ +pub struct Masker<'w> { key: [u8; 4], pos: usize, - end: &'w mut W, + end: &'w mut Write, } -impl<'w, W> Masker<'w, W> - where W: Write + 'w -{ +impl<'w> Masker<'w> { /// Create a new Masker with the key and the endpoint /// to be writter to. - pub fn new(key: [u8; 4], endpoint: &'w mut W) -> Self { + pub fn new(key: [u8; 4], endpoint: &'w mut Write) -> Self { Masker { key: key, pos: 0, @@ -28,9 +24,7 @@ impl<'w, W> Masker<'w, W> } } -impl<'w, W> Write for Masker<'w, W> - where W: Write + 'w -{ +impl<'w> Write for Masker<'w> { fn write(&mut self, data: &[u8]) -> IoResult { let mut buf = Vec::with_capacity(data.len()); for &byte in data.iter() { From 09ad0a385bb93e592c19a6b362e932a2a1762c63 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Fri, 19 May 2017 03:10:07 -0400 Subject: [PATCH 02/52] Added futures as an async dep. --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d4a378b97d..7a31071bd7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ bitflags = "^0.8" rand = "^0.3" byteorder = "^1.0" sha1 = "^0.2" +futures = { version = "^0.1", optional = true } tokio-core = { version = "^0.1", optional = true } tokio-io = { version = "^0.1", optional = true } bytes = { version = "^0.4", optional = true } @@ -37,4 +38,4 @@ serde_json = "^1.0" default = ["ssl", "async"] ssl = ["openssl"] nightly = ["hyper/nightly"] -async = ["tokio-core", "tokio-io", "bytes"] +async = ["tokio-core", "tokio-io", "bytes", "futures"] From cd54128aeea1a825634be82bb5966d3e57ba8c97 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Fri, 19 May 2017 03:04:23 -0400 Subject: [PATCH 03/52] Fixed bugs in codec, corrected masks. --- src/codec.rs | 49 +++++++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/src/codec.rs b/src/codec.rs index 8505b8693f..0ea0f03547 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -1,5 +1,3 @@ -//! TODO: docs - use std::borrow::Borrow; use std::marker::PhantomData; use std::io::Cursor; @@ -22,22 +20,25 @@ use result::WebSocketError; * Dataframes * **************/ -/// TODO: docs +#[derive(Clone,PartialEq,Eq,Debug)] +pub enum Context { + Server, + Client, +} + pub struct DataFrameCodec { - masked: bool, + is_server: bool, frame_type: PhantomData, } impl DataFrameCodec { - /// TODO: docs - pub fn default(masked: bool) -> DataFrameCodec { - DataFrameCodec::new(masked) + pub fn default(context: Context) -> DataFrameCodec { + DataFrameCodec::new(context) } - /// TODO: docs - pub fn new(masked: bool) -> DataFrameCodec { + pub fn new(context: Context) -> DataFrameCodec { DataFrameCodec { - masked: masked, + is_server: context == Context::Server, frame_type: PhantomData, } } @@ -47,6 +48,7 @@ impl Decoder for DataFrameCodec { type Item = DataFrame; type Error = WebSocketError; + // TODO: do not retry to read the header on each new data (keep a buffer) fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { let (header, bytes_read) = { // we'll make a fake reader and keep track of the bytes read @@ -70,12 +72,12 @@ impl Decoder for DataFrameCodec { return Ok(None); } - let body = src.split_off(bytes_read as usize).to_vec(); - // use up the rest of the buffer since we already copied it to header - let _ = src.take(); + // TODO: using usize is not the right thing here (can be larger) + let _ = src.split_to(bytes_read as usize); + let body = src.split_to(header.len as usize).to_vec(); // construct a dataframe - Ok(Some(DataFrame::read_dataframe_body(header, body, self.masked)?)) + Ok(Some(DataFrame::read_dataframe_body(header, body, self.is_server)?)) } } @@ -88,7 +90,7 @@ impl Encoder for DataFrameCodec fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { // TODO: check size and grow dst accordingly - item.borrow().write_to(&mut dst.writer(), self.masked) + item.borrow().write_to(&mut dst.writer(), !self.is_server) } } @@ -96,7 +98,6 @@ impl Encoder for DataFrameCodec * Messages * ************/ -/// TODO: docs pub struct MessageCodec<'m, M> where M: 'm { @@ -106,19 +107,14 @@ pub struct MessageCodec<'m, M> } impl<'m, M> MessageCodec<'m, M> { - /// TODO: docs - pub fn default(masked: bool) -> MessageCodec<'m, Message<'m>> { - MessageCodec::new(masked) + pub fn default(context: Context) -> MessageCodec<'m, Message<'m>> { + MessageCodec::new(context) } - /// TODO: docs - pub fn new(masked: bool) -> MessageCodec<'m, M> { + pub fn new(context: Context) -> MessageCodec<'m, M> { MessageCodec { buffer: Vec::new(), - dataframe_codec: DataFrameCodec { - masked: masked, - frame_type: PhantomData, - }, + dataframe_codec: DataFrameCodec::new(context), message_type: PhantomData, } } @@ -156,6 +152,7 @@ impl<'m, M> Decoder for MessageCodec<'m, M> if finished { let buffer = mem::replace(&mut self.buffer, Vec::new()); + println!("Dataframes: {:?}", buffer); return Ok(Some(Message::from_dataframes(buffer)?)); } } @@ -173,7 +170,7 @@ impl<'m, M> Encoder for MessageCodec<'m, M> fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { for ref dataframe in item.borrow().dataframes() { // TODO: check size and grow dst accordingly - dataframe.write_to(&mut dst.writer(), self.dataframe_codec.masked)? + dataframe.write_to(&mut dst.writer(), !self.dataframe_codec.is_server)? } Ok(()) } From 31c31995c4384366f579be859c5e3c2e2979bd7d Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Fri, 19 May 2017 03:06:36 -0400 Subject: [PATCH 04/52] Remove some async code when turned off. --- src/client/mod.rs | 4 ++-- src/lib.rs | 3 +++ src/stream.rs | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/client/mod.rs b/src/client/mod.rs index 4f919df683..fc66896b7e 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -129,8 +129,8 @@ impl Client Client { headers: headers, stream: stream, - sender: Sender::new(out_mask), - receiver: Receiver::new(in_mask), + sender: Sender::new(out_mask), // true + receiver: Receiver::new(in_mask), // false } } diff --git a/src/lib.rs b/src/lib.rs index 15f4d54f72..8f137b7b24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,8 @@ extern crate tokio_core; extern crate tokio_io; #[cfg(feature="async")] extern crate bytes; +#[cfg(feature="async")] +extern crate futures; #[macro_use] extern crate bitflags; @@ -94,3 +96,4 @@ pub mod receiver; pub mod sender; #[cfg(feature="async")] pub mod codec; +pub mod buffer; diff --git a/src/stream.rs b/src/stream.rs index 57c8f1830f..8a964f3765 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -21,6 +21,7 @@ impl Stream for S where S: Read + Write {} /// TODO: docs #[cfg(feature="async")] pub trait AsyncStream: AsyncRead + AsyncWrite {} +#[cfg(feature="async")] impl AsyncStream for S where S: AsyncRead + AsyncWrite {} /// a `Stream` that can also be used as a borrow to a `TcpStream` From 65ba2470a50337ed44c3aafa363111973683b1c3 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Fri, 19 May 2017 03:07:03 -0400 Subject: [PATCH 05/52] Add custom buffer impl to be used with async. --- src/buffer.rs | 250 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 src/buffer.rs diff --git a/src/buffer.rs b/src/buffer.rs new file mode 100644 index 0000000000..0ba33bacda --- /dev/null +++ b/src/buffer.rs @@ -0,0 +1,250 @@ +use std::cmp; +use std::io::{self, Read, Write, BufRead}; +use std::fmt::Arguments; +use futures::Poll; + +#[cfg(feature="async")] +use tokio_io::{AsyncWrite, AsyncRead}; +#[cfg(feature="async")] +use bytes::{Buf, BufMut}; + +pub struct BufReader { + inner: R, + buf: Vec, + pos: usize, + cap: usize, +} + +const INIT_BUFFER_SIZE: usize = 4096; +const MAX_BUFFER_SIZE: usize = 8192 + 4096 * 100; + +impl BufReader { + #[inline] + pub fn new(rdr: R) -> BufReader { + BufReader::with_capacity(rdr, INIT_BUFFER_SIZE) + } + + #[inline] + pub fn from_parts(rdr: R, buf: Vec, pos: usize, cap: usize) -> BufReader { + BufReader { + inner: rdr, + buf: buf, + pos: pos, + cap: cap, + } + } + + #[inline] + pub fn with_capacity(rdr: R, cap: usize) -> BufReader { + BufReader { + inner: rdr, + buf: vec![0; cap], + pos: 0, + cap: 0, + } + } + + #[inline] + pub fn get_ref(&self) -> &R { + &self.inner + } + + #[inline] + pub fn get_mut(&mut self) -> &mut R { + &mut self.inner + } + + #[inline] + pub fn get_buf(&self) -> &[u8] { + if self.pos < self.cap { + &self.buf[self.pos..self.cap] + } else { + &[] + } + } + + /// Extracts the buffer from this reader. Return the current cursor position + /// and the position of the last valid byte. + /// + /// This operation does not copy the buffer. Instead, it directly returns + /// the internal buffer. As a result, this reader will no longer have any + /// buffered contents and any subsequent read from this reader will not + /// include the returned buffered contents. + #[inline] + pub fn take_buf(&mut self) -> (Vec, usize, usize) { + let (pos, cap) = (self.pos, self.cap); + self.pos = 0; + self.cap = 0; + + let mut output = vec![0; INIT_BUFFER_SIZE]; + ::std::mem::swap(&mut self.buf, &mut output); + (output, pos, cap) + } + + #[inline] + pub fn into_inner(self) -> R { + self.inner + } + + #[inline] + pub fn into_parts(self) -> (R, Vec, usize, usize) { + (self.inner, self.buf, self.pos, self.cap) + } + + #[inline] + pub fn read_into_buf(&mut self) -> io::Result { + self.maybe_reserve(); + let v = &mut self.buf; + if self.cap < v.capacity() { + let nread = try!(self.inner.read(&mut v[self.cap..])); + self.cap += nread; + Ok(nread) + } else { + Ok(0) + } + } + + #[inline] + fn maybe_reserve(&mut self) { + let cap = self.buf.capacity(); + if self.cap == cap && cap < MAX_BUFFER_SIZE { + self.buf.reserve(cmp::min(cap * 4, MAX_BUFFER_SIZE) - cap); + let new = self.buf.capacity() - self.buf.len(); + unsafe { grow_zerofill(&mut self.buf, new) } + } + } +} + +#[inline] +unsafe fn grow_zerofill(buf: &mut Vec, additional: usize) { + use std::ptr; + let len = buf.len(); + buf.set_len(len + additional); + ptr::write_bytes(buf.as_mut_ptr().offset(len as isize), 0, additional); +} + +impl Read for BufReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + if self.cap == self.pos && buf.len() >= self.buf.len() { + return self.inner.read(buf); + } + let nread = { + let mut rem = try!(self.fill_buf()); + try!(rem.read(buf)) + }; + self.consume(nread); + Ok(nread) + } +} + +impl Write for BufReader { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + self.get_mut().write(buf) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + self.get_mut().flush() + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + self.get_mut().write_all(buf) + } + + #[inline] + fn write_fmt(&mut self, fmt: Arguments) -> io::Result<()> { + self.get_mut().write_fmt(fmt) + } +} + +impl BufRead for BufReader { + fn fill_buf(&mut self) -> io::Result<&[u8]> { + if self.pos == self.cap { + self.cap = try!(self.inner.read(&mut self.buf)); + self.pos = 0; + } + Ok(&self.buf[self.pos..self.cap]) + } + + #[inline] + fn consume(&mut self, amt: usize) { + self.pos = cmp::min(self.pos + amt, self.cap); + if self.pos == self.cap { + self.pos = 0; + self.cap = 0; + } + } +} + +#[cfg(feature="async")] +impl AsyncRead for BufReader { + #[inline] + unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { + self.get_ref().prepare_uninitialized_buffer(buf) + } +} + +#[cfg(feature="async")] +impl AsyncWrite for BufReader { + #[inline] + fn shutdown(&mut self) -> Poll<(), io::Error> { + self.get_mut().shutdown() + } + + #[inline] + fn write_buf(&mut self, buf: &mut B) -> Poll { + self.get_mut().write_buf(buf) + } +} + +#[cfg(test)] +mod tests { + + use std::io::{self, Read, BufRead}; + use super::BufReader; + + struct SlowRead(u8); + + impl Read for SlowRead { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let state = self.0; + self.0 += 1; + (&match state % 3 { + 0 => b"foo", + 1 => b"bar", + _ => b"baz", + } + [..]) + .read(buf) + } + } + + #[test] + fn test_consume_and_get_buf() { + let mut rdr = BufReader::new(SlowRead(0)); + rdr.read_into_buf().unwrap(); + rdr.consume(1); + assert_eq!(rdr.get_buf(), b"oo"); + rdr.read_into_buf().unwrap(); + rdr.read_into_buf().unwrap(); + assert_eq!(rdr.get_buf(), b"oobarbaz"); + rdr.consume(5); + assert_eq!(rdr.get_buf(), b"baz"); + rdr.consume(3); + assert_eq!(rdr.get_buf(), b""); + assert_eq!(rdr.pos, 0); + assert_eq!(rdr.cap, 0); + } + + #[test] + fn test_resize() { + let raw = b"hello world"; + let mut rdr = BufReader::with_capacity(&raw[..], 5); + rdr.read_into_buf().unwrap(); + assert_eq!(rdr.get_buf(), b"hello"); + rdr.read_into_buf().unwrap(); + assert_eq!(rdr.get_buf(), b"hello world"); + } +} From df1f87cd4dfca77a5ccf6d5318736ff4ef5d881b Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Fri, 19 May 2017 13:16:14 -0400 Subject: [PATCH 06/52] Fix writing large frames & fragmentation in codec. --- src/codec.rs | 25 +++++++++++++------------ src/ws/dataframe.rs | 21 +++++++++++++++++++++ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/codec.rs b/src/codec.rs index 0ea0f03547..6be59c56b9 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -57,10 +57,7 @@ impl Decoder for DataFrameCodec { // read header to get the size, bail if not enough let header = match read_header(&mut reader) { Ok(head) => head, - // TODO: check if this is the correct error - Err(WebSocketError::IoError(ref e)) if e.kind() == io::ErrorKind::UnexpectedEof => { - return Ok(None) - } + Err(WebSocketError::NoDataAvailable) => return Ok(None), Err(e) => return Err(e), }; @@ -89,8 +86,12 @@ impl Encoder for DataFrameCodec type Error = WebSocketError; fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { - // TODO: check size and grow dst accordingly - item.borrow().write_to(&mut dst.writer(), !self.is_server) + let masked = !self.is_server; + let frame_size = item.borrow().frame_size(masked); + if frame_size > dst.remaining_mut() { + dst.reserve(frame_size); + } + item.borrow().write_to(&mut dst.writer(), masked) } } @@ -138,7 +139,7 @@ impl<'m, M> Decoder for MessageCodec<'m, M> } // control frame 8...15 => { - mem::replace(&mut self.buffer, vec![frame]); + return Ok(Some(Message::from_dataframes(vec![frame])?)); } // data frame 1...7 if !is_first => { @@ -152,7 +153,6 @@ impl<'m, M> Decoder for MessageCodec<'m, M> if finished { let buffer = mem::replace(&mut self.buffer, Vec::new()); - println!("Dataframes: {:?}", buffer); return Ok(Some(Message::from_dataframes(buffer)?)); } } @@ -168,11 +168,12 @@ impl<'m, M> Encoder for MessageCodec<'m, M> type Error = WebSocketError; fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { - for ref dataframe in item.borrow().dataframes() { - // TODO: check size and grow dst accordingly - dataframe.write_to(&mut dst.writer(), !self.dataframe_codec.is_server)? + let masked = !self.dataframe_codec.is_server; + let frame_size = item.borrow().frame_size(masked); + if frame_size > dst.remaining_mut() { + dst.reserve(frame_size); } - Ok(()) + item.borrow().write_to(&mut dst.writer(), masked) } } diff --git a/src/ws/dataframe.rs b/src/ws/dataframe.rs index 6c7271b17e..e078a2e5b6 100644 --- a/src/ws/dataframe.rs +++ b/src/ws/dataframe.rs @@ -29,6 +29,27 @@ pub trait DataFrame { self.payload().len() } + /// Get's the size of the entire dataframe in bytes, + /// i.e. header and payload. + fn frame_size(&self, masked: bool) -> usize { + // one byte for the opcode & reserved & fin + 1 + // depending on the size of the payload, add the right payload len bytes + + match self.size() { + s if s <= 125 => 1, + s if s <= 65535 => 3, + _ => 9, + } + // add the mask size if there is one + + if masked { + 4 + } else { + 0 + } + // finally add the payload len + + self.size() + } + /// Write the payload to a writer fn write_payload(&self, socket: &mut Write) -> WebSocketResult<()> { try!(socket.write_all(&*self.payload())); From aff620e6dd02efac08c6c70498cc91deb6ecb312 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Fri, 19 May 2017 13:20:52 -0400 Subject: [PATCH 07/52] Autobahn tests for client passes with tokio async. --- examples/autobahn-async-client.rs | 155 ++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 examples/autobahn-async-client.rs diff --git a/examples/autobahn-async-client.rs b/examples/autobahn-async-client.rs new file mode 100644 index 0000000000..13cebf9108 --- /dev/null +++ b/examples/autobahn-async-client.rs @@ -0,0 +1,155 @@ +extern crate websocket; +extern crate rustc_serialize as serialize; +extern crate tokio_core; +extern crate tokio_io; +extern crate futures; + +use std::io; +use std::str::from_utf8; +use websocket::ClientBuilder; +use websocket::Message; +use websocket::message::Type; +use websocket::result::WebSocketError; +use websocket::buffer::BufReader; +use serialize::json; + +use tokio_core::net::TcpStream; +use tokio_core::reactor::Core; +use tokio_core::reactor::Handle; +use tokio_io::AsyncRead; +use tokio_io::AsyncWrite; +use tokio_io::codec::Framed; +use tokio_io::codec::FramedRead; +use tokio_io::codec::FramedWrite; +use tokio_io::io::WriteHalf; +use tokio_io::io::ReadHalf; +use futures::sink::Sink; +use futures::stream::Stream; +use futures::Future; +use futures::future; +use futures::future::Loop; +use websocket::codec::MessageCodec; +use websocket::codec::Context; + +fn connect_ws<'m>( + url: &str, + handle: &Handle, +) -> Framed, MessageCodec<'m, Message<'m>>> { + let client = ClientBuilder::new(url) + .unwrap() + .connect_insecure() + .unwrap(); + + let (stream, buf_data) = client.into_stream(); + + let stream = TcpStream::from_stream(stream, handle).unwrap(); + + let buf_stream = match buf_data { + Some((buf, pos, cap)) => BufReader::from_parts(stream, buf, pos, cap), + None => BufReader::new(stream), + }; + + buf_stream.framed(>::default(Context::Client)) +} + +fn main() { + let addr = "ws://127.0.0.1:9001".to_string(); + let agent = "rust-websocket"; + let mut core = Core::new().unwrap(); + let handle = core.handle(); + + println!("Using fuzzingserver {}", addr); + println!("Using agent {}", agent); + let case_count = get_case_count(addr.clone(), &handle); + + println!("Running test suite..."); + for case_id in 1..case_count { + let url = addr.clone() + "/runCase?case=" + &case_id.to_string()[..] + "&agent=" + agent; + let handle = core.handle(); + + let duplex = connect_ws(&url, &handle); + + println!("Executing test case: {}/{}", case_id, case_count); + + core.run(future::loop_fn(duplex, |stream| { + stream.into_future() + .or_else(|(err, stream)| { + println!("message transmission was an error: {:?}", err); + stream.send(Message::close()).map(|s| (None, s)) + }) + .and_then(|(msg, stream)| match msg.map(|m| (m.opcode, m)) { + Some((Type::Text, m)) => { + if let Ok(txt) = String::from_utf8(m.payload.into_owned()) { + stream.send(Message::text(txt)) + .map(|s| Loop::Continue(s)) + .boxed() + } else { + stream.send(Message::close()) + .map(|s| Loop::Break(())) + .boxed() + } + } + Some((Type::Binary, m)) => { + println!("payload length: {}", m.payload.len()); + stream.send(Message::binary(m.payload.into_owned())) + .map(|s| Loop::Continue(s)) + .boxed() + } + Some((Type::Ping, m)) => { + stream.send(Message::pong(m.payload)) + .map(|s| Loop::Continue(s)) + .boxed() + } + Some((Type::Close, _)) => { + stream.send(Message::close()) + .map(|s| Loop::Break(())) + .boxed() + } + Some((Type::Pong, _)) => future::ok(Loop::Continue(stream)).boxed(), + None => future::ok(Loop::Break(())).boxed(), + }) + .map_err(|e| { + println!("Error sending or other: {:?}", e); + e + }) + })); + } + + update_reports(addr.clone(), agent, &handle); +} + +fn get_case_count(addr: String, handle: &Handle) -> usize { + let url = addr + "/getCaseCount"; + + connect_ws(&url, &handle) + .into_future() + .map_err(|e| e.0) + .and_then(|(msg, stream)| match msg.map(|m| (m.opcode, m)) { + Some((Type::Text, message)) => { + let count = String::from_utf8(message.payload.into_owned()).unwrap(); + let count: usize = json::decode(&count).unwrap(); + println!("We will be running {} test cases!", count); + Ok(count) + } + _ => { + let error = WebSocketError::ProtocolError("Unsupported message in /getCaseCount"); + Err(error) + } + }) + .wait() + .unwrap() +} + +fn update_reports(addr: String, agent: &str, handle: &Handle) { + let url = addr + "/updateReports?agent=" + agent; + let (sink, stream) = connect_ws(&url, &handle).split(); + + println!("Updating reports..."); + + sink.send_all(stream.filter(|m| m.opcode == Type::Close) + .take(1) + .map(|_| Message::close())); + + println!("Reports updated."); + println!("Test suite finished!"); +} From c8b5e07a72f935e45708e7b6c1ab2375eb66a05c Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Fri, 19 May 2017 17:03:58 -0400 Subject: [PATCH 08/52] Added async transformer for client. --- src/buffer.rs | 1 + src/client/mod.rs | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/buffer.rs b/src/buffer.rs index 0ba33bacda..99170fac91 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,6 +1,7 @@ use std::cmp; use std::io::{self, Read, Write, BufRead}; use std::fmt::Arguments; +#[cfg(feature="async")] use futures::Poll; #[cfg(feature="async")] diff --git a/src/client/mod.rs b/src/client/mod.rs index fc66896b7e..ce12eab84b 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,6 +1,7 @@ //! Contains the WebSocket client. extern crate url; +use std::io; use std::net::TcpStream; use std::net::SocketAddr; use std::io::Result as IoResult; @@ -27,6 +28,21 @@ pub use receiver::Reader; pub mod builder; pub use self::builder::{ClientBuilder, Url, ParseError}; +#[cfg(feature="async")] +pub use tokio_core::reactor::Handle; +#[cfg(feature="async")] +pub use tokio_io::codec::Framed; +#[cfg(feature="async")] +use tokio_io::AsyncRead; +#[cfg(feature="async")] +use codec::MessageCodec; +#[cfg(feature="async")] +use codec::Context; +#[cfg(feature="async")] +use message::Message; +#[cfg(feature="async")] +use buffer::BufReader as AsyncBufReader; + /// Represents a WebSocket client, which can send and receive messages/data frames. /// /// The client just wraps around a `Stream` (which is something that can be read from @@ -65,6 +81,12 @@ pub struct Client receiver: Receiver, } +#[cfg(feature="async")] +pub type AsyncClient<'m, S> = Framed, MessageCodec<'m, Message<'m>>>; + +#[cfg(feature="async")] +pub type AsyncTcpStream = ::tokio_core::net::TcpStream; + impl Client { /// Shuts down the sending half of the client connection, will cause all pending /// and future IO to return immediately with an appropriate value. @@ -77,6 +99,20 @@ impl Client { pub fn shutdown_receiver(&self) -> IoResult<()> { self.stream.get_ref().as_tcp().shutdown(Shutdown::Read) } + + #[cfg(feature="async")] + pub fn async<'m>(self, handle: &Handle) -> io::Result> { + let (stream, buf_data) = self.into_stream(); + + let stream = AsyncTcpStream::from_stream(stream, handle)?; + + let buf_stream = match buf_data { + Some((buf, pos, cap)) => AsyncBufReader::from_parts(stream, buf, pos, cap), + None => AsyncBufReader::new(stream), + }; + + Ok(buf_stream.framed(>::default(Context::Client))) + } } impl Client From 8ef9969415809cb859105843abb1e207b8477383 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Fri, 19 May 2017 17:04:22 -0400 Subject: [PATCH 09/52] Added async client example. --- examples/async-client.rs | 82 +++++++++++++++++++++++++++++++ examples/autobahn-async-client.rs | 25 +++++----- 2 files changed, 93 insertions(+), 14 deletions(-) create mode 100644 examples/async-client.rs diff --git a/examples/async-client.rs b/examples/async-client.rs new file mode 100644 index 0000000000..786b6cc70e --- /dev/null +++ b/examples/async-client.rs @@ -0,0 +1,82 @@ +extern crate websocket; +extern crate tokio_core; +extern crate tokio_io; +extern crate futures; + +const CONNECTION: &'static str = "ws://127.0.0.1:2794"; + +use std::thread; +use std::io::stdin; + +use tokio_core::reactor::Core; + +use futures::sink::Sink; +use futures::stream::Stream; +use futures::sync::mpsc; + +use websocket::result::WebSocketError; +use websocket::Message; +use websocket::message::Type; +use websocket::client::ClientBuilder; + +fn main() { + println!("Connecting to {}", CONNECTION); + + // standard in isn't supported in mio yet, so we use a thread + // see https://github.com/carllerche/mio/issues/321 + let (usr_msg, stdin_ch) = mpsc::channel(0); + thread::spawn(move || { + let mut input = String::new(); + let mut stdin_sink = usr_msg.wait(); + loop { + input.clear(); + stdin().read_line(&mut input).unwrap(); + let trimmed = input.trim(); + + let (close, msg) = match trimmed { + "/close" => (true, Message::close()), + "/ping" => (false, Message::ping(b"PING".to_vec())), + _ => (false, Message::text(trimmed.to_string())), + }; + + stdin_sink.send(msg) + .expect("Sending message across stdin channel."); + + if close { + break; + } + } + }); + + let mut core = Core::new().unwrap(); + let handle = core.handle(); + + let client = ClientBuilder::new(CONNECTION) + .unwrap() + .add_protocol("rust-websocket") + .connect_insecure() + .unwrap() + .async(&handle) + .unwrap(); + + println!("Successfully connected"); + + let (sink, stream) = client.split(); + + let runner = + stream.filter_map(|message| { + match message.opcode { + Type::Close => Some(Message::close()), + Type::Ping => Some(Message::pong(message.payload)), + _ => { + // Say what we received + println!("Received Message: {:?}", message); + None + } + } + }) + .select(stdin_ch.map_err(|_| WebSocketError::NoDataAvailable)) + .forward(sink); + + core.run(runner).unwrap(); +} diff --git a/examples/autobahn-async-client.rs b/examples/autobahn-async-client.rs index 13cebf9108..ed43ca137d 100644 --- a/examples/autobahn-async-client.rs +++ b/examples/autobahn-async-client.rs @@ -4,8 +4,6 @@ extern crate tokio_core; extern crate tokio_io; extern crate futures; -use std::io; -use std::str::from_utf8; use websocket::ClientBuilder; use websocket::Message; use websocket::message::Type; @@ -17,12 +15,7 @@ use tokio_core::net::TcpStream; use tokio_core::reactor::Core; use tokio_core::reactor::Handle; use tokio_io::AsyncRead; -use tokio_io::AsyncWrite; use tokio_io::codec::Framed; -use tokio_io::codec::FramedRead; -use tokio_io::codec::FramedWrite; -use tokio_io::io::WriteHalf; -use tokio_io::io::ReadHalf; use futures::sink::Sink; use futures::stream::Stream; use futures::Future; @@ -85,7 +78,7 @@ fn main() { .boxed() } else { stream.send(Message::close()) - .map(|s| Loop::Break(())) + .map(|_| Loop::Break(())) .boxed() } } @@ -102,7 +95,7 @@ fn main() { } Some((Type::Close, _)) => { stream.send(Message::close()) - .map(|s| Loop::Break(())) + .map(|_| Loop::Break(())) .boxed() } Some((Type::Pong, _)) => future::ok(Loop::Continue(stream)).boxed(), @@ -112,7 +105,8 @@ fn main() { println!("Error sending or other: {:?}", e); e }) - })); + })) + .unwrap(); } update_reports(addr.clone(), agent, &handle); @@ -124,7 +118,7 @@ fn get_case_count(addr: String, handle: &Handle) -> usize { connect_ws(&url, &handle) .into_future() .map_err(|e| e.0) - .and_then(|(msg, stream)| match msg.map(|m| (m.opcode, m)) { + .and_then(|(msg, _)| match msg.map(|m| (m.opcode, m)) { Some((Type::Text, message)) => { let count = String::from_utf8(message.payload.into_owned()).unwrap(); let count: usize = json::decode(&count).unwrap(); @@ -146,9 +140,12 @@ fn update_reports(addr: String, agent: &str, handle: &Handle) { println!("Updating reports..."); - sink.send_all(stream.filter(|m| m.opcode == Type::Close) - .take(1) - .map(|_| Message::close())); + stream.filter(|m| m.opcode == Type::Close) + .take(1) + .map(|_| Message::close()) + .forward(sink) + .wait() + .unwrap(); println!("Reports updated."); println!("Test suite finished!"); From 6a14d1984b84bca9fd6115516a1bac8cbd44f499 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Sat, 20 May 2017 01:02:10 -0400 Subject: [PATCH 10/52] Removed Splittable for async, already exists. --- src/stream.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/stream.rs b/src/stream.rs index 8a964f3765..3dee0bb080 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -58,7 +58,6 @@ impl Splittable for ReadWritePair } } -#[cfg(not(feature="async"))] impl Splittable for TcpStream { type Reader = TcpStream; type Writer = TcpStream; @@ -68,18 +67,6 @@ impl Splittable for TcpStream { } } -#[cfg(feature="async")] -impl Splittable for S - where S: AsyncStream -{ - type Reader = ReadHalf; - type Writer = WriteHalf; - - fn split(self) -> io::Result<(Self::Reader, Self::Writer)> { - Ok(self.split()) - } -} - /// The ability access a borrow to an underlying TcpStream, /// so one can set options on the stream such as `nonblocking`. pub trait AsTcpStream { From 6295e7d486e9297c4ee0939eef5b00308c54716c Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Sat, 20 May 2017 01:03:21 -0400 Subject: [PATCH 11/52] Added handshake codec and started on async builder. --- src/client/builder.rs | 207 ++++++++++++++++++++++++++++++++---------- src/client/mod.rs | 53 +++++------ 2 files changed, 185 insertions(+), 75 deletions(-) diff --git a/src/client/builder.rs b/src/client/builder.rs index 3807ae8217..aa8976e1b6 100644 --- a/src/client/builder.rs +++ b/src/client/builder.rs @@ -2,9 +2,14 @@ use std::borrow::Cow; use std::net::TcpStream; +use std::marker::PhantomData; pub use url::{Url, ParseError}; use url::Position; use hyper::version::HttpVersion; +use hyper::http::h1::Incoming; +use hyper::method::Method; +use hyper::uri::RequestUri; +use hyper::http::RawStatus; use hyper::status::StatusCode; use hyper::buffer::BufReader; use hyper::http::h1::parse_response; @@ -19,9 +24,24 @@ use header::{WebSocketAccept, WebSocketKey, WebSocketVersion, WebSocketProtocol, use result::{WSUrlErrorKind, WebSocketResult, WebSocketError}; #[cfg(feature="ssl")] use stream::NetworkStream; -use stream::Stream; +use stream::{self, Stream}; +use message::Message; + use super::Client; +#[cfg(feature="async")] +use super::async; +#[cfg(feature="async")] +use tokio_io::{AsyncRead, AsyncWrite}; +#[cfg(feature="async")] +use tokio_io::codec::Framed; +#[cfg(feature="async")] +use futures::{Future, Sink}; +#[cfg(feature="async")] +use futures::Stream as FutureStream; +#[cfg(feature="async")] +pub use codec::{MessageCodec, Context}; + /// Build clients with a builder-style API /// This makes it easy to create and configure a websocket /// connection: @@ -441,41 +461,7 @@ impl<'u> ClientBuilder<'u> { self.connect_on(ssl_stream) } - // TODO: similar ability for server? - /// Connects to a websocket server on any stream you would like. - /// Possible streams: - /// - Unix Sockets - /// - Logging Middle-ware - /// - SSH - /// - /// ```rust - /// # use websocket::ClientBuilder; - /// use websocket::stream::ReadWritePair; - /// use std::io::Cursor; - /// - /// let accept = b"HTTP/1.1 101 Switching Protocols\r - /// Upgrade: websocket\r - /// Connection: Upgrade\r - /// Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r - /// \r\n"; - /// - /// let input = Cursor::new(&accept[..]); - /// let output = Cursor::new(Vec::new()); - /// - /// let client = ClientBuilder::new("wss://test.ws").unwrap() - /// .key(b"the sample nonce".clone()) - /// .connect_on(ReadWritePair(input, output)) - /// .unwrap(); - /// - /// let text = (client.into_stream().0).1.into_inner(); - /// let text = String::from_utf8(text).unwrap(); - /// assert!(text.contains("dGhlIHNhbXBsZSBub25jZQ=="), "{}", text); - /// ``` - pub fn connect_on(&mut self, mut stream: S) -> WebSocketResult> - where S: Stream - { - let resource = self.url[Position::BeforePath..Position::AfterQuery].to_owned(); - + fn build_request(&mut self) -> String { // enter host if available (unix sockets don't have hosts) if let Some(host) = self.url.host_str() { self.headers @@ -487,8 +473,8 @@ impl<'u> ClientBuilder<'u> { self.headers .set(Connection(vec![ - ConnectionOption::ConnectionHeader(UniCase("Upgrade".to_string())) - ])); + ConnectionOption::ConnectionHeader(UniCase("Upgrade".to_string())) + ])); self.headers .set(Upgrade(vec![ @@ -507,15 +493,13 @@ impl<'u> ClientBuilder<'u> { } // send request - try!(write!(stream, "GET {} {}\r\n", resource, self.version)); - try!(write!(stream, "{}\r\n", self.headers)); + let resource = self.url[Position::BeforePath..Position::AfterQuery].to_owned(); + format!("GET {} {}\r\n{}\r\n", resource, self.version, self.headers) + } - // wait for a response - let mut reader = BufReader::new(stream); - let response = try!(parse_response(&mut reader)); + fn validate(&self, response: &Incoming) -> WebSocketResult<()> { let status = StatusCode::from_u16(response.subject.0); - // validate if status != StatusCode::SwitchingProtocols { return Err(WebSocketError::ResponseError("Status code must be Switching Protocols")); } @@ -540,13 +524,144 @@ impl<'u> ClientBuilder<'u> { if self.headers.get() != Some(&(Connection(vec![ - ConnectionOption::ConnectionHeader(UniCase("Upgrade".to_string())), - ]))) { + ConnectionOption::ConnectionHeader(UniCase("Upgrade".to_string())), + ]))) { return Err(WebSocketError::ResponseError("Connection field must be 'Upgrade'")); } + Ok(()) + } + + // TODO: similar ability for server? + /// Connects to a websocket server on any stream you would like. + /// Possible streams: + /// - Unix Sockets + /// - Logging Middle-ware + /// - SSH + /// + /// ```rust + /// # use websocket::ClientBuilder; + /// use websocket::stream::ReadWritePair; + /// use std::io::Cursor; + /// + /// let accept = b"HTTP/1.1 101 Switching Protocols\r + /// Upgrade: websocket\r + /// Connection: Upgrade\r + /// Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r + /// \r\n"; + /// + /// let input = Cursor::new(&accept[..]); + /// let output = Cursor::new(Vec::new()); + /// + /// let client = ClientBuilder::new("wss://test.ws").unwrap() + /// .key(b"the sample nonce".clone()) + /// .connect_on(ReadWritePair(input, output)) + /// .unwrap(); + /// + /// let text = (client.into_stream().0).1.into_inner(); + /// let text = String::from_utf8(text).unwrap(); + /// assert!(text.contains("dGhlIHNhbXBsZSBub25jZQ=="), "{}", text); + /// ``` + pub fn connect_on(&mut self, mut stream: S) -> WebSocketResult> + where S: Stream + { + // send request + stream.write_all(self.build_request().as_bytes())?; + + // wait for a response + let mut reader = BufReader::new(stream); + let response = try!(parse_response(&mut reader)); + + // validate + self.validate(&response); + Ok(Client::unchecked(reader, response.headers, true, false)) } + + #[cfg(feature="async")] + pub fn async_connect_on<'m, S> + ( + &mut self, + stream: S, + ) -> Box>, Headers), Error = WebSocketError>> + where S: stream::AsyncStream + Send + { + let close_early = "Connection closed before handshake could complete."; + let request = self.build_request(); + let framed = stream.framed(async_builder::WsHandshakeCodec); + + Box::new(framed + // send request + .send(request).map_err(::std::convert::Into::into) + + // wait for a response + .and_then(|stream| stream.into_future().map_err(|e| e.0)) + + // validate + .and_then(|(message, stream)| { + message + .ok_or(WebSocketError::ProtocolError(close_early)) + .and_then(|message| self.validate(&message).map(|_| (message, stream))) + }) + + // output the final client and metadata + .map(|(message, stream)| { + let codec = >::default(Context::Client); + let client = stream.into_inner().framed(codec); + (client, message.headers) + })); + unimplemented!(); + } +} + +#[cfg(feature="async")] +mod async_builder { + use super::*; + use hyper; + use std::io::{self, Write}; + use tokio_io::codec::{Decoder, Encoder}; + use bytes::BytesMut; + use bytes::BufMut; + + pub struct WsHandshakeCodec; + + impl Encoder for WsHandshakeCodec { + type Item = String; + type Error = io::Error; + + fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { + let byte_len = item.as_bytes().len(); + if byte_len > dst.remaining_mut() { + dst.reserve(byte_len); + } + dst.writer().write(item.as_bytes()).map(|_| ()) + } + } + + impl Decoder for WsHandshakeCodec { + type Item = Incoming; + type Error = WebSocketError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + // check if we get a request from hyper + // TODO: this is ineffecient, but hyper does not give us a better way to parse + let (response, bytes_read) = { + let mut reader = BufReader::new(&*src as &[u8]); + let res = match parse_response(&mut reader) { + Ok(r) => r, + Err(hyper::Error::Io(ref err)) if err.kind() == + io::ErrorKind::UnexpectedEof => return Ok(None), + Err(e) => return Err(e.into()), + }; + let (_, _, pos, _) = reader.into_parts(); + (res, pos) + }; + + // TODO: check if data get's lost this way + src.split_to(bytes_read); + Ok(Some(response)) + } + } } mod tests { diff --git a/src/client/mod.rs b/src/client/mod.rs index ce12eab84b..03be5522aa 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -29,19 +29,19 @@ pub mod builder; pub use self::builder::{ClientBuilder, Url, ParseError}; #[cfg(feature="async")] -pub use tokio_core::reactor::Handle; -#[cfg(feature="async")] -pub use tokio_io::codec::Framed; -#[cfg(feature="async")] -use tokio_io::AsyncRead; -#[cfg(feature="async")] -use codec::MessageCodec; -#[cfg(feature="async")] -use codec::Context; -#[cfg(feature="async")] -use message::Message; -#[cfg(feature="async")] -use buffer::BufReader as AsyncBufReader; +pub mod async { + pub use tokio_core::reactor::Handle; + pub use tokio_io::codec::Framed; + pub use tokio_core::net::TcpStream; + use tokio_io::AsyncRead; + use codec::MessageCodec; + use codec::Context; + use message::Message; + use buffer::BufReader as AsyncBufReader; + + pub type Client<'m, S> = Framed, MessageCodec<'m, Message<'m>>>; + +} /// Represents a WebSocket client, which can send and receive messages/data frames. /// @@ -81,12 +81,6 @@ pub struct Client receiver: Receiver, } -#[cfg(feature="async")] -pub type AsyncClient<'m, S> = Framed, MessageCodec<'m, Message<'m>>>; - -#[cfg(feature="async")] -pub type AsyncTcpStream = ::tokio_core::net::TcpStream; - impl Client { /// Shuts down the sending half of the client connection, will cause all pending /// and future IO to return immediately with an appropriate value. @@ -100,19 +94,20 @@ impl Client { self.stream.get_ref().as_tcp().shutdown(Shutdown::Read) } - #[cfg(feature="async")] - pub fn async<'m>(self, handle: &Handle) -> io::Result> { - let (stream, buf_data) = self.into_stream(); + // TODO + // #[cfg(feature="async")] + // pub fn async<'m>(self, handle: &Handle) -> io::Result> { + // let (stream, buf_data) = self.into_stream(); - let stream = AsyncTcpStream::from_stream(stream, handle)?; + // let stream = AsyncTcpStream::from_stream(stream, handle)?; - let buf_stream = match buf_data { - Some((buf, pos, cap)) => AsyncBufReader::from_parts(stream, buf, pos, cap), - None => AsyncBufReader::new(stream), - }; + // let buf_stream = match buf_data { + // Some((buf, pos, cap)) => AsyncBufReader::from_parts(stream, buf, pos, cap), + // None => AsyncBufReader::new(stream), + // }; - Ok(buf_stream.framed(>::default(Context::Client))) - } + // Ok(buf_stream.framed(>::default(Context::Client))) + // } } impl Client From 55f0081c09335b0c6120e2a57a7edc6e3c01f651 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Sun, 21 May 2017 03:20:22 -0400 Subject: [PATCH 12/52] Added semantic owned message type to deserialize to. Fixes #108 --- src/dataframe.rs | 19 ++- src/lib.rs | 2 +- src/message.rs | 327 +++++++++++++++++++++++++++++++++++++++----- src/ws/dataframe.rs | 59 +------- src/ws/message.rs | 21 +-- 5 files changed, 322 insertions(+), 106 deletions(-) diff --git a/src/dataframe.rs b/src/dataframe.rs index e2c0ebd043..a2f71531e9 100644 --- a/src/dataframe.rs +++ b/src/dataframe.rs @@ -1,6 +1,6 @@ //! Module containing the default implementation of data frames. -use std::io::Read; -use std::borrow::Cow; +use std::str::from_utf8; +use std::io::{Read, Write}; use result::{WebSocketResult, WebSocketError}; use ws::dataframe::DataFrame as DataFrameable; use ws::util::header::DataFrameHeader; @@ -108,8 +108,19 @@ impl DataFrameable for DataFrame { } #[inline(always)] - fn payload(&self) -> Cow<[u8]> { - Cow::Borrowed(&self.data) + fn size(&self) -> usize { + self.data.len() + } + + #[inline(always)] + fn write_payload(&self, socket: &mut Write) -> WebSocketResult<()> { + socket.write_all(self.data.as_slice())?; + Ok(()) + } + + #[inline(always)] + fn take_payload(self) -> Vec { + self.data } } diff --git a/src/lib.rs b/src/lib.rs index 8f137b7b24..d995d355ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,7 +63,7 @@ extern crate test; pub use self::client::{Client, ClientBuilder}; pub use self::server::Server; pub use self::dataframe::DataFrame; -pub use self::message::Message; +pub use self::message::{Message, OwnedMessage}; pub use self::stream::Stream; pub use self::ws::Sender; pub use self::ws::Receiver; diff --git a/src/message.rs b/src/message.rs index fc0ffb1f4e..9795ce2e94 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,9 +1,11 @@ //! Module containing the default implementation for messages. +use std::str::from_utf8; +use std::io; use std::io::Write; use std::borrow::Cow; -use std::iter::{Take, Repeat, repeat}; use result::{WebSocketResult, WebSocketError}; use dataframe::Opcode; +use ws::dataframe::DataFrame as DataFrameTrait; use byteorder::{WriteBytesExt, ReadBytesExt, BigEndian}; use ws::util::bytes_to_string; use ws; @@ -11,7 +13,7 @@ use ws; const FALSE_RESERVED_BITS: &'static [bool; 3] = &[false; 3]; /// Valid types of messages (in the default implementation) -#[derive(Debug, PartialEq, Clone, Copy)] +#[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum Type { /// Message with UTF8 test Text = 1, @@ -25,7 +27,6 @@ pub enum Type { Close = 8, } -// TODO: split up message into owned received and borrowed send /// Represents a WebSocket message. /// /// This message also has the ability to not own its payload, and stores its entire payload in @@ -34,7 +35,7 @@ pub enum Type { /// /// Incidentally this (the default implementation of Message) implements the DataFrame trait /// because this message just gets sent as one single DataFrame. -#[derive(PartialEq, Clone, Debug)] +#[derive(Eq, PartialEq, Clone, Debug)] pub struct Message<'a> { /// Type of WebSocket message pub opcode: Type, @@ -137,12 +138,6 @@ impl<'a> ws::dataframe::DataFrame for Message<'a> { FALSE_RESERVED_BITS } - fn payload(&self) -> Cow<[u8]> { - let mut buf = Vec::with_capacity(self.size()); - self.write_payload(&mut buf).ok(); - Cow::Owned(buf) - } - fn size(&self) -> usize { self.payload.len() + if self.cd_status_code.is_some() { 2 } else { 0 } } @@ -154,53 +149,309 @@ impl<'a> ws::dataframe::DataFrame for Message<'a> { try!(socket.write_all(&*self.payload)); Ok(()) } + + fn take_payload(self) -> Vec { + if let Some(reason) = self.cd_status_code { + let mut buf = Vec::with_capacity(2 + self.payload.len()); + buf.write_u16::(reason); + buf.append(&mut self.payload.into_owned()); + buf + } else { + self.payload.into_owned() + } + } } -// TODO: separate reading writing here, or maybe just make an IntoMessage trait -// so you can support both owned and borrowed messages -impl<'a, 'b> ws::Message<'b, &'b Message<'a>> for Message<'a> { - type DataFrameIterator = Take>>; +impl<'a> ws::Message for Message<'a> { + /// Attempt to form a message from a series of data frames + fn serialize(&self, writer: &mut Write, masked: bool) -> WebSocketResult<()> { + self.write_to(writer, masked) + } - fn dataframes(&'b self) -> Self::DataFrameIterator { - repeat(self).take(1) + /// Returns how many bytes this message will take up + fn message_size(&self, masked: bool) -> usize { + self.frame_size(masked) } /// Attempt to form a message from a series of data frames fn from_dataframes(frames: Vec) -> WebSocketResult - where D: ws::dataframe::DataFrame + where D: DataFrameTrait { let opcode = try!(frames.first() .ok_or(WebSocketError::ProtocolError("No dataframes provided")) .map(|d| d.opcode())); + let opcode = Opcode::new(opcode); + + let payload_size = frames.iter().map(|d| d.size()).sum(); - let mut data = Vec::new(); + let mut data = Vec::with_capacity(payload_size); - for (i, dataframe) in frames.iter().enumerate() { + for (i, dataframe) in frames.into_iter().enumerate() { if i > 0 && dataframe.opcode() != Opcode::Continuation as u8 { return Err(WebSocketError::ProtocolError("Unexpected non-continuation data frame")); } if *dataframe.reserved() != [false; 3] { return Err(WebSocketError::ProtocolError("Unsupported reserved bits received")); } - data.extend(dataframe.payload().iter().cloned()); - } - - Ok(match Opcode::new(opcode) { - Some(Opcode::Text) => Message::text(try!(bytes_to_string(&data[..]))), - Some(Opcode::Binary) => Message::binary(data), - Some(Opcode::Close) => { - if !data.is_empty() { - let status_code = try!((&data[..]).read_u16::()); - let reason = try!(bytes_to_string(&data[2..])); - Message::close_because(status_code, reason) - } else { - Message::close() - } - } - Some(Opcode::Ping) => Message::ping(data), - Some(Opcode::Pong) => Message::pong(data), - _ => return Err(WebSocketError::ProtocolError("Unsupported opcode received")), - }) + data.append(&mut dataframe.take_payload()); + } + + if opcode == Some(Opcode::Text) { + if let Err(e) = from_utf8(data.as_slice()) { + return Err(e.into()); + } + } + + let msg = match opcode { + Some(Opcode::Text) => { + Message { + opcode: Type::Text, + cd_status_code: None, + payload: Cow::Owned(data), + } + } + Some(Opcode::Binary) => Message::binary(data), + Some(Opcode::Close) => { + if data.len() > 0 { + let status_code = try!((&data[..]).read_u16::()); + let reason = try!(bytes_to_string(&data[2..])); + Message::close_because(status_code, reason) + } else { + Message::close() + } + } + Some(Opcode::Ping) => Message::ping(data), + Some(Opcode::Pong) => Message::pong(data), + _ => return Err(WebSocketError::ProtocolError("Unsupported opcode received")), + }; + Ok(msg) + } +} + +/// Represents a WebSocket message. +#[derive(Eq, PartialEq, Clone, Debug)] +pub enum OwnedMessage { + /// A message containing UTF-8 text data + Text(String), + /// A message containing binary data + Binary(Vec), + /// A message which indicates closure of the WebSocket connection. + /// This message may or may not contain data. + Close(Option), + /// A ping message - should be responded to with a pong message. + /// Usually the pong message will be sent with the same data as the + /// received ping message. + Ping(Vec), + /// A pong message, sent in response to a Ping message, usually + /// containing the same data as the received ping message. + Pong(Vec), +} + +impl OwnedMessage { + pub fn is_close(&self) -> bool { + match *self { + OwnedMessage::Close(_) => true, + _ => false, + } + } + + pub fn is_control(&self) -> bool { + match *self { + OwnedMessage::Close(_) => true, + OwnedMessage::Ping(_) => true, + OwnedMessage::Pong(_) => true, + _ => false, + } + } + + pub fn is_data(&self) -> bool { + !self.is_control() + } + + pub fn is_ping(&self) -> bool { + match *self { + OwnedMessage::Ping(_) => true, + _ => false, + } + } + + pub fn is_pong(&self) -> bool { + match *self { + OwnedMessage::Pong(_) => true, + _ => false, + } + } +} + +impl ws::Message for OwnedMessage { + /// Attempt to form a message from a series of data frames + fn serialize(&self, writer: &mut Write, masked: bool) -> WebSocketResult<()> { + self.write_to(writer, masked) + } + + /// Returns how many bytes this message will take up + fn message_size(&self, masked: bool) -> usize { + self.frame_size(masked) + } + + /// Attempt to form a message from a series of data frames + fn from_dataframes(frames: Vec) -> WebSocketResult + where D: DataFrameTrait + { + Ok(Message::from_dataframes(frames)?.into()) + } +} + +impl ws::dataframe::DataFrame for OwnedMessage { + #[inline(always)] + fn is_last(&self) -> bool { + true + } + + #[inline(always)] + fn opcode(&self) -> u8 { + (match *self { + OwnedMessage::Text(_) => Type::Text, + OwnedMessage::Binary(_) => Type::Binary, + OwnedMessage::Close(_) => Type::Close, + OwnedMessage::Ping(_) => Type::Ping, + OwnedMessage::Pong(_) => Type::Pong, + }) as u8 + } + + #[inline(always)] + fn reserved<'b>(&'b self) -> &'b [bool; 3] { + FALSE_RESERVED_BITS + } + + fn size(&self) -> usize { + match *self { + OwnedMessage::Text(ref txt) => txt.len(), + OwnedMessage::Binary(ref bin) => bin.len(), + OwnedMessage::Ping(ref data) => data.len(), + OwnedMessage::Pong(ref data) => data.len(), + OwnedMessage::Close(ref data) => { + match data { + &Some(ref c) => c.reason.len() + 2, + &None => 0, + } + } + } + } + + fn write_payload(&self, socket: &mut Write) -> WebSocketResult<()> { + match *self { + OwnedMessage::Text(ref txt) => { + socket.write_all(txt.as_bytes())? + } + OwnedMessage::Binary(ref bin) => { + socket.write_all(bin.as_slice())? + } + OwnedMessage::Ping(ref data) => { + socket.write_all(data.as_slice())? + } + OwnedMessage::Pong(ref data) => { + socket.write_all(data.as_slice())? + } + OwnedMessage::Close(ref data) => { + match data { + &Some(ref c) => { + socket.write_u16::(c.status_code)?; + socket.write_all(c.reason.as_bytes())? + } + &None => (), + } + } + }; + Ok(()) + } + + fn take_payload(self) -> Vec { + match self { + OwnedMessage::Text(txt) => txt.into_bytes(), + OwnedMessage::Binary(bin) => bin, + OwnedMessage::Ping(data) => data, + OwnedMessage::Pong(data) => data, + OwnedMessage::Close(data) => { + match data { + Some(c) => { + let mut buf = Vec::with_capacity(2 + c.reason.len()); + buf.write_u16::(c.status_code); + buf.append(&mut c.reason.into_bytes()); + buf + } + None => vec![], + } + } + } + } +} + +impl<'m> From> for OwnedMessage { + fn from(message: Message<'m>) -> Self { + match message.opcode { + Type::Text => { + let convert = String::from_utf8_lossy(&message.payload).into_owned(); + OwnedMessage::Text(convert) + } + Type::Close => { + match message.cd_status_code { + Some(code) => OwnedMessage::Close(Some(CloseData { + status_code: code, + reason: String::from_utf8_lossy(&message.payload).into_owned(), + })), + None => OwnedMessage::Close(None), + } + } + Type::Binary => OwnedMessage::Binary(message.payload.into_owned()), + Type::Ping => OwnedMessage::Ping(message.payload.into_owned()), + Type::Pong => OwnedMessage::Pong(message.payload.into_owned()), + } + } +} + +impl<'m> From for Message<'m> { + fn from(message: OwnedMessage) -> Self { + match message { + OwnedMessage::Text(txt) => Message::text(txt), + OwnedMessage::Binary(bin) => Message::binary(bin), + OwnedMessage::Close(because) => { + match because { + Some(c) => Message::close_because(c.status_code, c.reason), + None => Message::close(), + } + } + OwnedMessage::Ping(data) => Message::ping(data), + OwnedMessage::Pong(data) => Message::pong(data), + } + } +} + +/// Represents data contained in a Close message +#[derive(Eq, PartialEq, Clone, Debug)] +pub struct CloseData { + /// The status-code of the CloseData + pub status_code: u16, + /// The reason-phrase of the CloseData + pub reason: String, +} + +impl CloseData { + /// Create a new CloseData object + pub fn new(status_code: u16, reason: String) -> CloseData { + CloseData { + status_code: status_code, + reason: reason, + } + } + /// Convert this into a vector of bytes + pub fn into_bytes(self) -> io::Result> { + let mut buf = Vec::new(); + try!(buf.write_u16::(self.status_code)); + for i in self.reason.as_bytes().iter() { + buf.push(*i); + } + Ok(buf) } } diff --git a/src/ws/dataframe.rs b/src/ws/dataframe.rs index e078a2e5b6..3dd6c2bce5 100644 --- a/src/ws/dataframe.rs +++ b/src/ws/dataframe.rs @@ -3,7 +3,6 @@ //! optomize the memory footprint of a dataframe for their //! own needs, and be able to use custom dataframes quickly use std::io::Write; -use std::borrow::Cow; use result::WebSocketResult; use ws::util::header as dfh; use ws::util::mask::Masker; @@ -18,16 +17,10 @@ pub trait DataFrame { /// What type of data does this dataframe contain? fn opcode(&self) -> u8; /// Reserved bits of this dataframe - fn reserved(&self) -> &[bool; 3]; - /// Entire payload of the dataframe. If not known then implement - /// write_payload as that is the actual method used when sending the - /// dataframe over the wire. - fn payload(&self) -> Cow<[u8]>; + fn reserved<'a>(&'a self) -> &'a [bool; 3]; /// How long (in bytes) is this dataframe's payload - fn size(&self) -> usize { - self.payload().len() - } + fn size(&self) -> usize; /// Get's the size of the entire dataframe in bytes, /// i.e. header and payload. @@ -51,12 +44,11 @@ pub trait DataFrame { } /// Write the payload to a writer - fn write_payload(&self, socket: &mut Write) -> WebSocketResult<()> { - try!(socket.write_all(&*self.payload())); - Ok(()) - } + fn write_payload(&self, socket: &mut Write) -> WebSocketResult<()>; + + /// Takes the payload out into a vec + fn take_payload(self) -> Vec; - // TODO: the error can probably be changed to an io error /// Writes a DataFrame to a Writer. fn write_to(&self, writer: &mut Write, mask: bool) -> WebSocketResult<()> { let mut flags = dfh::DataFrameFlags::empty(); @@ -98,42 +90,3 @@ pub trait DataFrame { Ok(()) } } - -impl<'a, D> DataFrame for &'a D - where D: DataFrame -{ - #[inline(always)] - fn is_last(&self) -> bool { - D::is_last(self) - } - - #[inline(always)] - fn opcode(&self) -> u8 { - D::opcode(self) - } - - #[inline(always)] - fn reserved(&self) -> &[bool; 3] { - D::reserved(self) - } - - #[inline(always)] - fn payload(&self) -> Cow<[u8]> { - D::payload(self) - } - - #[inline(always)] - fn size(&self) -> usize { - D::size(self) - } - - #[inline(always)] - fn write_payload(&self, socket: &mut Write) -> WebSocketResult<()> { - D::write_payload(self, socket) - } - - #[inline(always)] - fn write_to(&self, writer: &mut Write, mask: bool) -> WebSocketResult<()> { - D::write_to(self, writer, mask) - } -} diff --git a/src/ws/message.rs b/src/ws/message.rs index dc02aa1a63..1ab299b424 100644 --- a/src/ws/message.rs +++ b/src/ws/message.rs @@ -2,17 +2,18 @@ //! //! See the `ws` module documentation for more information. +use std::io::Write; +use ws::dataframe::DataFrame as DataFrameable; use result::WebSocketResult; -use ws::dataframe::DataFrame; /// A trait for WebSocket messages -pub trait Message<'a, F>: Sized - where F: DataFrame -{ - /// The iterator type returned by dataframes - type DataFrameIterator: Iterator; - /// Attempt to form a message from a slice of data frames. - fn from_dataframes(frames: Vec) -> WebSocketResult where D: DataFrame; - /// Turns this message into an iterator over data frames - fn dataframes(&'a self) -> Self::DataFrameIterator; +pub trait Message: Sized { + /// Writes this message to the writer + fn serialize(&self, &mut Write, masked: bool) -> WebSocketResult<()>; + + /// Returns how many bytes this message will take up + fn message_size(&self, masked: bool) -> usize; + + /// Attempt to form a message from a series of data frames + fn from_dataframes(frames: Vec) -> WebSocketResult; } From 004ddf7dd2bebfd06ef645bc107bb4f1b8e79c12 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Sun, 21 May 2017 03:21:11 -0400 Subject: [PATCH 13/52] Codec now has no lifetimes, better API. --- src/codec.rs | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/codec.rs b/src/codec.rs index 6be59c56b9..6ee190d8d6 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -2,7 +2,6 @@ use std::borrow::Borrow; use std::marker::PhantomData; use std::io::Cursor; use std::mem; -use std::io; use tokio_io::codec::Decoder; use tokio_io::codec::Encoder; @@ -10,7 +9,7 @@ use bytes::BytesMut; use bytes::BufMut; use dataframe::DataFrame; -use message::Message; +use message::OwnedMessage; use ws::dataframe::DataFrame as DataFrameTrait; use ws::message::Message as MessageTrait; use ws::util::header::read_header; @@ -78,7 +77,6 @@ impl Decoder for DataFrameCodec { } } -// TODO: try to allow encoding of many kinds of dataframes impl Encoder for DataFrameCodec where D: Borrow { @@ -99,20 +97,18 @@ impl Encoder for DataFrameCodec * Messages * ************/ -pub struct MessageCodec<'m, M> - where M: 'm +pub struct MessageCodec + where M: MessageTrait { buffer: Vec, dataframe_codec: DataFrameCodec, - message_type: PhantomData, + message_type: PhantomData, } -impl<'m, M> MessageCodec<'m, M> { - pub fn default(context: Context) -> MessageCodec<'m, Message<'m>> { - MessageCodec::new(context) - } - - pub fn new(context: Context) -> MessageCodec<'m, M> { +impl MessageCodec + where M: MessageTrait +{ + pub fn new(context: Context) -> MessageCodec { MessageCodec { buffer: Vec::new(), dataframe_codec: DataFrameCodec::new(context), @@ -121,10 +117,10 @@ impl<'m, M> MessageCodec<'m, M> { } } -impl<'m, M> Decoder for MessageCodec<'m, M> - where M: 'm +impl Decoder for MessageCodec + where M: MessageTrait { - type Item = Message<'m>; + type Item = OwnedMessage; type Error = WebSocketError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { @@ -139,7 +135,7 @@ impl<'m, M> Decoder for MessageCodec<'m, M> } // control frame 8...15 => { - return Ok(Some(Message::from_dataframes(vec![frame])?)); + return Ok(Some(OwnedMessage::from_dataframes(vec![frame])?)); } // data frame 1...7 if !is_first => { @@ -153,7 +149,7 @@ impl<'m, M> Decoder for MessageCodec<'m, M> if finished { let buffer = mem::replace(&mut self.buffer, Vec::new()); - return Ok(Some(Message::from_dataframes(buffer)?)); + return Ok(Some(OwnedMessage::from_dataframes(buffer)?)); } } @@ -161,19 +157,19 @@ impl<'m, M> Decoder for MessageCodec<'m, M> } } -impl<'m, M> Encoder for MessageCodec<'m, M> - where M: Borrow> +impl Encoder for MessageCodec + where M: MessageTrait, { type Item = M; type Error = WebSocketError; fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { let masked = !self.dataframe_codec.is_server; - let frame_size = item.borrow().frame_size(masked); + let frame_size = item.message_size(masked); if frame_size > dst.remaining_mut() { dst.reserve(frame_size); } - item.borrow().write_to(&mut dst.writer(), masked) + item.serialize(&mut dst.writer(), masked) } } From 407954725bedba5ff5e55b2f9d998313161e6c56 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Sun, 21 May 2017 03:21:40 -0400 Subject: [PATCH 14/52] Simplified sender and receiver, received OwnedMessage now. --- src/receiver.rs | 16 ++++++---------- src/sender.rs | 13 ++++--------- src/ws/receiver.rs | 41 +++++++++++++---------------------------- src/ws/sender.rs | 17 ++++++++++------- 4 files changed, 33 insertions(+), 54 deletions(-) diff --git a/src/receiver.rs b/src/receiver.rs index 52847fa1e7..03cc63376f 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -8,9 +8,9 @@ use hyper::buffer::BufReader; use dataframe::{DataFrame, Opcode}; use result::{WebSocketResult, WebSocketError}; use ws; -use ws::dataframe::DataFrame as DataFrameable; use ws::receiver::Receiver as ReceiverTrait; use ws::receiver::{MessageIterator, DataFrameIterator}; +use message::OwnedMessage; use stream::{AsTcpStream, Stream}; pub use stream::Shutdown; @@ -39,21 +39,15 @@ impl Reader } /// Reads a single message from this receiver. - pub fn recv_message<'m, M, D, I>(&mut self) -> WebSocketResult - where M: ws::Message<'m, D, DataFrameIterator = I>, - I: Iterator, - D: DataFrameable + pub fn recv_message<'m, I>(&mut self) -> WebSocketResult + where I: Iterator { self.receiver.recv_message(&mut self.stream) } /// An iterator over incoming messsages. /// This iterator will block until new messages arrive and will never halt. - pub fn incoming_messages<'a, M, D>(&'a mut self,) - -> MessageIterator<'a, Receiver, D, M, BufReader> - where M: ws::Message<'a, D>, - D: DataFrameable - { + pub fn incoming_messages<'a>(&'a mut self) -> MessageIterator<'a, Receiver, BufReader> { self.receiver.incoming_messages(&mut self.stream) } } @@ -95,6 +89,8 @@ impl Receiver { impl ws::Receiver for Receiver { type F = DataFrame; + type M = OwnedMessage; + /// Reads a single data frame from the remote endpoint. fn recv_dataframe(&mut self, reader: &mut R) -> WebSocketResult where R: Read diff --git a/src/sender.rs b/src/sender.rs index 398520f18d..bf84ef2174 100644 --- a/src/sender.rs +++ b/src/sender.rs @@ -32,9 +32,8 @@ impl Writer } /// Sends a single message to the remote endpoint. - pub fn send_message<'m, M, D>(&mut self, message: &'m M) -> WebSocketResult<()> - where M: ws::Message<'m, D>, - D: DataFrame + pub fn send_message<'m, M>(&mut self, message: &'m M) -> WebSocketResult<()> + where M: ws::Message { self.sender.send_message(&mut self.stream, message) } @@ -70,11 +69,7 @@ impl Sender { } impl ws::Sender for Sender { - /// Sends a single data frame to the remote endpoint. - fn send_dataframe(&mut self, writer: &mut W, dataframe: &D) -> WebSocketResult<()> - where D: DataFrame, - W: Write - { - dataframe.write_to(writer, self.mask) + fn is_masked(&self) -> bool { + self.mask } } diff --git a/src/ws/receiver.rs b/src/ws/receiver.rs index 3e237166da..4f291f99d9 100644 --- a/src/ws/receiver.rs +++ b/src/ws/receiver.rs @@ -4,7 +4,6 @@ //! See the `ws` module documentation for more information. use std::io::Read; -use std::marker::PhantomData; use ws::Message; use ws::dataframe::DataFrame; use result::WebSocketResult; @@ -14,6 +13,9 @@ pub trait Receiver: Sized { /// The type of dataframe that incoming messages will be serialized to. type F: DataFrame; + /// The type of message that incoming messages will be serialized to. + type M: Message; + /// Reads a single data frame from this receiver. fn recv_dataframe(&mut self, reader: &mut R) -> WebSocketResult where R: Read; @@ -32,30 +34,20 @@ pub trait Receiver: Sized { } /// Reads a single message from this receiver. - fn recv_message<'m, D, M, I, R>(&mut self, reader: &mut R) -> WebSocketResult - where M: Message<'m, D, DataFrameIterator = I>, - I: Iterator, - D: DataFrame, - R: Read + fn recv_message<'m, R>(&mut self, reader: &mut R) -> WebSocketResult + where R: Read { - let dataframes = try!(self.recv_message_dataframes(reader)); - Message::from_dataframes(dataframes) + let dataframes = self.recv_message_dataframes(reader)?; + Self::M::from_dataframes(dataframes) } /// Returns an iterator over incoming messages. - fn incoming_messages<'a, M, D, R>( - &'a mut self, - reader: &'a mut R, - ) -> MessageIterator<'a, Self, D, M, R> - where M: Message<'a, D>, - D: DataFrame, - R: Read + fn incoming_messages<'a, R>(&'a mut self, reader: &'a mut R) -> MessageIterator<'a, Self, R> + where R: Read { MessageIterator { reader: reader, inner: self, - _dataframe: PhantomData, - _message: PhantomData, } } } @@ -82,29 +74,22 @@ impl<'a, Recv, R> Iterator for DataFrameIterator<'a, Recv, R> } /// An iterator over messages from a Receiver. -pub struct MessageIterator<'a, Recv, D, M, R> +pub struct MessageIterator<'a, Recv, R> where Recv: 'a + Receiver, - M: Message<'a, D>, - D: DataFrame, R: 'a + Read { reader: &'a mut R, inner: &'a mut Recv, - _dataframe: PhantomData, - _message: PhantomData, } -impl<'a, Recv, D, M, I, R> Iterator for MessageIterator<'a, Recv, D, M, R> +impl<'a, Recv, R> Iterator for MessageIterator<'a, Recv, R> where Recv: 'a + Receiver, - M: Message<'a, D, DataFrameIterator = I>, - I: Iterator, - D: DataFrame, R: Read { - type Item = WebSocketResult; + type Item = WebSocketResult; /// Get the next message from the receiver. Always returns `Some`. - fn next(&mut self) -> Option> { + fn next(&mut self) -> Option> { Some(self.inner.recv_message(self.reader)) } } diff --git a/src/ws/sender.rs b/src/ws/sender.rs index c179fd275d..5ccbb04546 100644 --- a/src/ws/sender.rs +++ b/src/ws/sender.rs @@ -9,20 +9,23 @@ use result::WebSocketResult; /// A trait for sending data frames and messages. pub trait Sender { + fn is_masked(&self) -> bool; + /// Sends a single data frame using this sender. fn send_dataframe(&mut self, writer: &mut W, dataframe: &D) -> WebSocketResult<()> where D: DataFrame, - W: Write; + W: Write + { + dataframe.write_to(writer, self.is_masked())?; + Ok(()) + } /// Sends a single message using this sender. - fn send_message<'m, M, D, W>(&mut self, writer: &mut W, message: &'m M) -> WebSocketResult<()> - where M: Message<'m, D>, - D: DataFrame, + fn send_message<'m, M, W>(&mut self, writer: &mut W, message: &'m M) -> WebSocketResult<()> + where M: Message, W: Write { - for ref dataframe in message.dataframes() { - try!(self.send_dataframe(writer, dataframe)); - } + message.serialize(writer, self.is_masked())?; Ok(()) } } From a8e9b5373cc54231059220ecd18338d66a4654cb Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Sun, 21 May 2017 03:22:16 -0400 Subject: [PATCH 15/52] Added insecure async option to client builder. --- src/client/builder.rs | 92 ++++++++++++++++++++++++++++++++----------- src/client/mod.rs | 41 ++++--------------- 2 files changed, 76 insertions(+), 57 deletions(-) diff --git a/src/client/builder.rs b/src/client/builder.rs index aa8976e1b6..bea8e5ddf5 100644 --- a/src/client/builder.rs +++ b/src/client/builder.rs @@ -2,13 +2,11 @@ use std::borrow::Cow; use std::net::TcpStream; -use std::marker::PhantomData; +use std::net::ToSocketAddrs; pub use url::{Url, ParseError}; use url::Position; use hyper::version::HttpVersion; use hyper::http::h1::Incoming; -use hyper::method::Method; -use hyper::uri::RequestUri; use hyper::http::RawStatus; use hyper::status::StatusCode; use hyper::buffer::BufReader; @@ -25,23 +23,28 @@ use result::{WSUrlErrorKind, WebSocketResult, WebSocketError}; #[cfg(feature="ssl")] use stream::NetworkStream; use stream::{self, Stream}; -use message::Message; +use message::OwnedMessage; use super::Client; #[cfg(feature="async")] use super::async; #[cfg(feature="async")] -use tokio_io::{AsyncRead, AsyncWrite}; +use tokio_core::net::TcpStream as AsyncTcpStream; #[cfg(feature="async")] -use tokio_io::codec::Framed; +use tokio_core::reactor::Handle; #[cfg(feature="async")] use futures::{Future, Sink}; #[cfg(feature="async")] +use futures::future; +#[cfg(feature="async")] use futures::Stream as FutureStream; #[cfg(feature="async")] pub use codec::{MessageCodec, Context}; +pub type AsyncClientNew = Box, Headers), + Error = WebSocketError>>; + /// Build clients with a builder-style API /// This makes it easy to create and configure a websocket /// connection: @@ -353,7 +356,7 @@ impl<'u> ClientBuilder<'u> { self.headers.get::() } - fn establish_tcp(&mut self, secure: Option) -> WebSocketResult { + fn extract_host_port(&self, secure: Option) -> WebSocketResult<(&str, u16)> { let port = match (self.url.port(), secure) { (Some(port), _) => port, (None, None) if self.url.scheme() == "wss" => 443, @@ -366,7 +369,11 @@ impl<'u> ClientBuilder<'u> { None => return Err(WebSocketError::WebSocketUrlError(WSUrlErrorKind::NoHostName)), }; - let tcp_stream = try!(TcpStream::connect((host, port))); + Ok((host, port)) + } + + fn establish_tcp(&mut self, secure: Option) -> WebSocketResult { + let tcp_stream = TcpStream::connect(self.extract_host_port(secure)?)?; Ok(tcp_stream) } @@ -573,24 +580,59 @@ impl<'u> ClientBuilder<'u> { let response = try!(parse_response(&mut reader)); // validate - self.validate(&response); + self.validate(&response)?; Ok(Client::unchecked(reader, response.headers, true, false)) } #[cfg(feature="async")] - pub fn async_connect_on<'m, S> - ( - &mut self, - stream: S, - ) -> Box>, Headers), Error = WebSocketError>> - where S: stream::AsyncStream + Send + pub fn async_connect_insecure(self, handle: &Handle) -> AsyncClientNew { + // get the address to connect to, return an error future if ther's a problem + let address = match self.extract_host_port(None).and_then(|p| Ok(p.to_socket_addrs()?)) { + Ok(mut s) => { + match s.next() { + Some(a) => a, + None => { + let err = WebSocketError::WebSocketUrlError(WSUrlErrorKind::NoHostName); + return future::err(err).boxed(); + } + } + } + Err(e) => return future::err(e).boxed(), + }; + + // connect a tcp stream + let tcp_stream = async::TcpStream::connect(&address, handle); + + let builder = ClientBuilder { + url: Cow::Owned(self.url.into_owned()), + version: self.version, + headers: self.headers, + version_set: self.version_set, + key_set: self.key_set, + }; + + Box::new(tcp_stream.map_err(|e| e.into()) + .and_then(move |stream| { + builder.async_connect_on(stream) + })) + } + + #[cfg(feature="async")] + pub fn async_connect_on(self, stream: S) -> AsyncClientNew + where S: stream::AsyncStream + Send + 'static { - let close_early = "Connection closed before handshake could complete."; - let request = self.build_request(); + let mut builder = ClientBuilder { + url: Cow::Owned(self.url.into_owned()), + version: self.version, + headers: self.headers, + version_set: self.version_set, + key_set: self.key_set, + }; + let request = builder.build_request(); let framed = stream.framed(async_builder::WsHandshakeCodec); - Box::new(framed + let future = framed // send request .send(request).map_err(::std::convert::Into::into) @@ -598,19 +640,21 @@ impl<'u> ClientBuilder<'u> { .and_then(|stream| stream.into_future().map_err(|e| e.0)) // validate - .and_then(|(message, stream)| { + .and_then(move |(message, stream)| { message - .ok_or(WebSocketError::ProtocolError(close_early)) - .and_then(|message| self.validate(&message).map(|_| (message, stream))) + .ok_or(WebSocketError::ProtocolError( + "Connection closed before handshake could complete.")) + .and_then(|message| builder.validate(&message).map(|_| (message, stream))) }) // output the final client and metadata .map(|(message, stream)| { - let codec = >::default(Context::Client); + let codec = >::new(Context::Client); let client = stream.into_inner().framed(codec); (client, message.headers) - })); - unimplemented!(); + }); + + Box::new(future) } } diff --git a/src/client/mod.rs b/src/client/mod.rs index 03be5522aa..504545d9cf 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,7 +1,6 @@ //! Contains the WebSocket client. extern crate url; -use std::io; use std::net::TcpStream; use std::net::SocketAddr; use std::io::Result as IoResult; @@ -13,6 +12,7 @@ use ws; use ws::sender::Sender as SenderTrait; use ws::receiver::{DataFrameIterator, MessageIterator}; use ws::receiver::Receiver as ReceiverTrait; +use message::OwnedMessage; use result::WebSocketResult; use stream::{AsTcpStream, Stream, Splittable, Shutdown}; use dataframe::DataFrame; @@ -33,13 +33,10 @@ pub mod async { pub use tokio_core::reactor::Handle; pub use tokio_io::codec::Framed; pub use tokio_core::net::TcpStream; - use tokio_io::AsyncRead; use codec::MessageCodec; - use codec::Context; - use message::Message; - use buffer::BufReader as AsyncBufReader; + use message::OwnedMessage; - pub type Client<'m, S> = Framed, MessageCodec<'m, Message<'m>>>; + pub type Client = Framed>; } @@ -93,21 +90,6 @@ impl Client { pub fn shutdown_receiver(&self) -> IoResult<()> { self.stream.get_ref().as_tcp().shutdown(Shutdown::Read) } - - // TODO - // #[cfg(feature="async")] - // pub fn async<'m>(self, handle: &Handle) -> io::Result> { - // let (stream, buf_data) = self.into_stream(); - - // let stream = AsyncTcpStream::from_stream(stream, handle)?; - - // let buf_stream = match buf_data { - // Some((buf, pos, cap)) => AsyncBufReader::from_parts(stream, buf, pos, cap), - // None => AsyncBufReader::new(stream), - // }; - - // Ok(buf_stream.framed(>::default(Context::Client))) - // } } impl Client @@ -173,9 +155,8 @@ impl Client } /// Sends a single message to the remote endpoint. - pub fn send_message<'m, M, D>(&mut self, message: &'m M) -> WebSocketResult<()> - where M: ws::Message<'m, D>, - D: DataFrameable + pub fn send_message<'m, M>(&mut self, message: &'m M) -> WebSocketResult<()> + where M: ws::Message { self.sender.send_message(self.stream.get_mut(), message) } @@ -203,10 +184,8 @@ impl Client /// /// let message: Message = client.recv_message().unwrap(); /// ``` - pub fn recv_message<'m, M, I, D>(&mut self) -> WebSocketResult - where M: ws::Message<'m, D, DataFrameIterator = I>, - I: Iterator, - D: DataFrameable + pub fn recv_message<'m, I>(&mut self) -> WebSocketResult + where I: Iterator { self.receiver.recv_message(&mut self.stream) } @@ -370,11 +349,7 @@ impl Client ///} ///# } ///``` - pub fn incoming_messages<'a, M, D>(&'a mut self,) - -> MessageIterator<'a, Receiver, D, M, BufReader> - where M: ws::Message<'a, D>, - D: DataFrameable - { + pub fn incoming_messages<'a>(&'a mut self) -> MessageIterator<'a, Receiver, BufReader> { self.receiver.incoming_messages(&mut self.stream) } } From 2f04ca9547edab296b764d1d3b0925b294ad912d Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Sun, 21 May 2017 03:22:36 -0400 Subject: [PATCH 16/52] Get rid of unused import warnings. --- src/buffer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/buffer.rs b/src/buffer.rs index 99170fac91..b10e0f3ffe 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -7,7 +7,7 @@ use futures::Poll; #[cfg(feature="async")] use tokio_io::{AsyncWrite, AsyncRead}; #[cfg(feature="async")] -use bytes::{Buf, BufMut}; +use bytes::Buf; pub struct BufReader { inner: R, From 94f69a2227cd2972e94f17f178bdd25ae2ddd9f8 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Sun, 21 May 2017 03:23:43 -0400 Subject: [PATCH 17/52] Redid autobahn client test using new API. --- examples/autobahn-async-client.rs | 183 ++++++++++++------------------ 1 file changed, 74 insertions(+), 109 deletions(-) diff --git a/examples/autobahn-async-client.rs b/examples/autobahn-async-client.rs index ed43ca137d..978301f3ed 100644 --- a/examples/autobahn-async-client.rs +++ b/examples/autobahn-async-client.rs @@ -1,49 +1,14 @@ extern crate websocket; -extern crate rustc_serialize as serialize; extern crate tokio_core; -extern crate tokio_io; extern crate futures; -use websocket::ClientBuilder; -use websocket::Message; -use websocket::message::Type; +use websocket::{ClientBuilder, OwnedMessage}; use websocket::result::WebSocketError; -use websocket::buffer::BufReader; -use serialize::json; - -use tokio_core::net::TcpStream; -use tokio_core::reactor::Core; -use tokio_core::reactor::Handle; -use tokio_io::AsyncRead; -use tokio_io::codec::Framed; +use tokio_core::reactor::{Core, Handle}; use futures::sink::Sink; use futures::stream::Stream; -use futures::Future; -use futures::future; +use futures::{future, Future}; use futures::future::Loop; -use websocket::codec::MessageCodec; -use websocket::codec::Context; - -fn connect_ws<'m>( - url: &str, - handle: &Handle, -) -> Framed, MessageCodec<'m, Message<'m>>> { - let client = ClientBuilder::new(url) - .unwrap() - .connect_insecure() - .unwrap(); - - let (stream, buf_data) = client.into_stream(); - - let stream = TcpStream::from_stream(stream, handle).unwrap(); - - let buf_stream = match buf_data { - Some((buf, pos, cap)) => BufReader::from_parts(stream, buf, pos, cap), - None => BufReader::new(stream), - }; - - buf_stream.framed(>::default(Context::Client)) -} fn main() { let addr = "ws://127.0.0.1:9001".to_string(); @@ -53,99 +18,99 @@ fn main() { println!("Using fuzzingserver {}", addr); println!("Using agent {}", agent); + let case_count = get_case_count(addr.clone(), &handle); + println!("We will be running {} test cases!", case_count); + + // let mut all_tests = Vec::with_capacity(case_count); println!("Running test suite..."); - for case_id in 1..case_count { + for case_id in 1..(case_count + 1) { let url = addr.clone() + "/runCase?case=" + &case_id.to_string()[..] + "&agent=" + agent; let handle = core.handle(); - let duplex = connect_ws(&url, &handle); - - println!("Executing test case: {}/{}", case_id, case_count); - - core.run(future::loop_fn(duplex, |stream| { - stream.into_future() - .or_else(|(err, stream)| { - println!("message transmission was an error: {:?}", err); - stream.send(Message::close()).map(|s| (None, s)) - }) - .and_then(|(msg, stream)| match msg.map(|m| (m.opcode, m)) { - Some((Type::Text, m)) => { - if let Ok(txt) = String::from_utf8(m.payload.into_owned()) { - stream.send(Message::text(txt)) - .map(|s| Loop::Continue(s)) - .boxed() - } else { - stream.send(Message::close()) - .map(|_| Loop::Break(())) - .boxed() - } - } - Some((Type::Binary, m)) => { - println!("payload length: {}", m.payload.len()); - stream.send(Message::binary(m.payload.into_owned())) - .map(|s| Loop::Continue(s)) - .boxed() - } - Some((Type::Ping, m)) => { - stream.send(Message::pong(m.payload)) - .map(|s| Loop::Continue(s)) - .boxed() - } - Some((Type::Close, _)) => { - stream.send(Message::close()) - .map(|_| Loop::Break(())) - .boxed() - } - Some((Type::Pong, _)) => future::ok(Loop::Continue(stream)).boxed(), - None => future::ok(Loop::Break(())).boxed(), - }) - .map_err(|e| { - println!("Error sending or other: {:?}", e); - e - }) - })) - .unwrap(); + let test_case = ClientBuilder::new(&url) + .unwrap() + .async_connect_insecure(&handle) + .and_then(move |(duplex, _)| { + println!("Executing test case: {}/{}", case_id, case_count); + future::loop_fn(duplex, |stream| { + stream.into_future() + .or_else(|(err, stream)| { + println!("message transmission was an error: {:?}", err); + stream.send(OwnedMessage::Close(None)).map(|s| (None, s)) + }) + .and_then(|(msg, stream)| match msg { + Some(OwnedMessage::Text(txt)) => { + stream.send(OwnedMessage::Text(txt)) + .map(|s| Loop::Continue(s)) + .boxed() + } + Some(OwnedMessage::Binary(bin)) => { + stream.send(OwnedMessage::Binary(bin)) + .map(|s| Loop::Continue(s)) + .boxed() + } + Some(OwnedMessage::Ping(data)) => { + stream.send(OwnedMessage::Pong(data)) + .map(|s| Loop::Continue(s)) + .boxed() + } + Some(OwnedMessage::Close(_)) => { + stream.send(OwnedMessage::Close(None)) + .map(|_| Loop::Break(())) + .boxed() + } + Some(OwnedMessage::Pong(_)) => { + future::ok(Loop::Continue(stream)).boxed() + } + None => future::ok(Loop::Break(())).boxed(), + }) + .map_err(|e| { + println!("Error sending or other: {:?}", e); + e + }) + }) + }) + .or_else(move |err| { + println!("Test case {} ended with an error: {:?}", case_id, err); + Ok(()) as Result<(), ()> + }); + + // all_tests.push(test_case); + core.run(test_case).ok(); } + // core.run(future::join_all(all_tests)).ok(); + update_reports(addr.clone(), agent, &handle); } fn get_case_count(addr: String, handle: &Handle) -> usize { let url = addr + "/getCaseCount"; - connect_ws(&url, &handle) - .into_future() - .map_err(|e| e.0) - .and_then(|(msg, _)| match msg.map(|m| (m.opcode, m)) { - Some((Type::Text, message)) => { - let count = String::from_utf8(message.payload.into_owned()).unwrap(); - let count: usize = json::decode(&count).unwrap(); - println!("We will be running {} test cases!", count); - Ok(count) - } - _ => { - let error = WebSocketError::ProtocolError("Unsupported message in /getCaseCount"); - Err(error) - } + ClientBuilder::new(&url) + .unwrap() + .async_connect_insecure(&handle) + .and_then(|(stream, _)| stream.into_future().map_err(|e| e.0)) + .and_then(|(msg, _)| match msg { + Some(OwnedMessage::Text(txt)) => Ok(txt.parse().unwrap()), + _ => Err(WebSocketError::ProtocolError("Unsupported message in /getCaseCount")), }) .wait() .unwrap() } fn update_reports(addr: String, agent: &str, handle: &Handle) { - let url = addr + "/updateReports?agent=" + agent; - let (sink, stream) = connect_ws(&url, &handle).split(); - println!("Updating reports..."); + let url = addr + "/updateReports?agent=" + agent; - stream.filter(|m| m.opcode == Type::Close) - .take(1) - .map(|_| Message::close()) - .forward(sink) - .wait() - .unwrap(); + ClientBuilder::new(&url) + .unwrap() + .async_connect_insecure(&handle) + .and_then(|(sink, _)| sink.send(OwnedMessage::Close(None))) + .wait() + .unwrap(); println!("Reports updated."); println!("Test suite finished!"); From 570d860834d34deb694aeb8345c2da6a48314be0 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Sun, 21 May 2017 20:01:01 -0400 Subject: [PATCH 18/52] Added default constructors for codecs. --- src/codec.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/codec.rs b/src/codec.rs index 6ee190d8d6..2f8da04854 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -30,11 +30,13 @@ pub struct DataFrameCodec { frame_type: PhantomData, } -impl DataFrameCodec { - pub fn default(context: Context) -> DataFrameCodec { +impl DataFrameCodec { + pub fn default(context: Context) -> Self { DataFrameCodec::new(context) } +} +impl DataFrameCodec { pub fn new(context: Context) -> DataFrameCodec { DataFrameCodec { is_server: context == Context::Server, @@ -98,13 +100,19 @@ impl Encoder for DataFrameCodec ************/ pub struct MessageCodec - where M: MessageTrait + where M: MessageTrait { buffer: Vec, dataframe_codec: DataFrameCodec, message_type: PhantomData, } +impl MessageCodec { + pub fn default(context: Context) -> Self { + Self::new(context) + } +} + impl MessageCodec where M: MessageTrait { @@ -135,7 +143,7 @@ impl Decoder for MessageCodec } // control frame 8...15 => { - return Ok(Some(OwnedMessage::from_dataframes(vec![frame])?)); + return Ok(Some(OwnedMessage::from_dataframes(vec![frame])?)); } // data frame 1...7 if !is_first => { @@ -149,7 +157,7 @@ impl Decoder for MessageCodec if finished { let buffer = mem::replace(&mut self.buffer, Vec::new()); - return Ok(Some(OwnedMessage::from_dataframes(buffer)?)); + return Ok(Some(OwnedMessage::from_dataframes(buffer)?)); } } @@ -158,7 +166,7 @@ impl Decoder for MessageCodec } impl Encoder for MessageCodec - where M: MessageTrait, + where M: MessageTrait { type Item = M; type Error = WebSocketError; From 08fff2e41779e7553f1e335f93dbde1d1e1441cb Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Sun, 21 May 2017 20:04:40 -0400 Subject: [PATCH 19/52] Use tokio-io fork that allows acces to internal buffers. --- Cargo.toml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7a31071bd7..2f9966e3cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,8 +25,8 @@ rand = "^0.3" byteorder = "^1.0" sha1 = "^0.2" futures = { version = "^0.1", optional = true } -tokio-core = { version = "^0.1", optional = true } -tokio-io = { version = "^0.1", optional = true } +tokio-core = { version = "0.1.7", optional = true } +tokio-io = { version = "0.1.1", optional = true } bytes = { version = "^0.4", optional = true } openssl = { version = "^0.9.10", optional = true } base64 = "^0.5" @@ -34,6 +34,10 @@ base64 = "^0.5" [dev-dependencies] serde_json = "^1.0" +[replace."tokio-io:0.1.1"] +git = "https://github.com/illegalprime/tokio-io.git" +rev = "18be60c56b0e932265a9e0301d14d5084577ca33" + [features] default = ["ssl", "async"] ssl = ["openssl"] From c6257d2e28b9a57095740e7d7171489b566fa33a Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Sun, 21 May 2017 20:07:53 -0400 Subject: [PATCH 20/52] Can build async insecure clients in Builder. --- src/client/builder.rs | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/client/builder.rs b/src/client/builder.rs index bea8e5ddf5..5da030b325 100644 --- a/src/client/builder.rs +++ b/src/client/builder.rs @@ -373,8 +373,7 @@ impl<'u> ClientBuilder<'u> { } fn establish_tcp(&mut self, secure: Option) -> WebSocketResult { - let tcp_stream = TcpStream::connect(self.extract_host_port(secure)?)?; - Ok(tcp_stream) + Ok(TcpStream::connect(self.extract_host_port(secure)?)?) } #[cfg(feature="ssl")] @@ -585,21 +584,24 @@ impl<'u> ClientBuilder<'u> { Ok(Client::unchecked(reader, response.headers, true, false)) } + // TODO: add timeout option for connecting + // TODO: add conveniences like .response_to_pings, .send_close, etc. #[cfg(feature="async")] pub fn async_connect_insecure(self, handle: &Handle) -> AsyncClientNew { // get the address to connect to, return an error future if ther's a problem - let address = match self.extract_host_port(None).and_then(|p| Ok(p.to_socket_addrs()?)) { - Ok(mut s) => { - match s.next() { - Some(a) => a, - None => { - let err = WebSocketError::WebSocketUrlError(WSUrlErrorKind::NoHostName); - return future::err(err).boxed(); + let address = + match self.extract_host_port(Some(false)).and_then(|p| Ok(p.to_socket_addrs()?)) { + Ok(mut s) => { + match s.next() { + Some(a) => a, + None => { + let err = WebSocketError::WebSocketUrlError(WSUrlErrorKind::NoHostName); + return future::err(err).boxed(); + } } } - } - Err(e) => return future::err(e).boxed(), - }; + Err(e) => return future::err(e).boxed(), + }; // connect a tcp stream let tcp_stream = async::TcpStream::connect(&address, handle); @@ -649,8 +651,9 @@ impl<'u> ClientBuilder<'u> { // output the final client and metadata .map(|(message, stream)| { - let codec = >::new(Context::Client); - let client = stream.into_inner().framed(codec); + let codec = MessageCodec::default(Context::Client); + let (client, buffer) = stream.into_parts(); + let client = ::tokio_io::codec::framed_with_buf(client, codec, buffer); (client, message.headers) }); @@ -701,7 +704,6 @@ mod async_builder { (res, pos) }; - // TODO: check if data get's lost this way src.split_to(bytes_read); Ok(Some(response)) } From 86e922995af1f2d157801bcf6c54e92684bde469 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Sun, 21 May 2017 20:08:37 -0400 Subject: [PATCH 21/52] Fully async-api-ed autobahn client harness. --- examples/autobahn-async-client.rs | 54 +++++++++++++------------------ 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/examples/autobahn-async-client.rs b/examples/autobahn-async-client.rs index 978301f3ed..703509c123 100644 --- a/examples/autobahn-async-client.rs +++ b/examples/autobahn-async-client.rs @@ -4,11 +4,11 @@ extern crate futures; use websocket::{ClientBuilder, OwnedMessage}; use websocket::result::WebSocketError; -use tokio_core::reactor::{Core, Handle}; +use tokio_core::reactor::Core; use futures::sink::Sink; use futures::stream::Stream; -use futures::{future, Future}; -use futures::future::Loop; +use futures::Future; +use futures::future::{self, Loop}; fn main() { let addr = "ws://127.0.0.1:9001".to_string(); @@ -19,15 +19,12 @@ fn main() { println!("Using fuzzingserver {}", addr); println!("Using agent {}", agent); - let case_count = get_case_count(addr.clone(), &handle); + let case_count = get_case_count(addr.clone(), &mut core); println!("We will be running {} test cases!", case_count); - // let mut all_tests = Vec::with_capacity(case_count); - println!("Running test suite..."); for case_id in 1..(case_count + 1) { let url = addr.clone() + "/runCase?case=" + &case_id.to_string()[..] + "&agent=" + agent; - let handle = core.handle(); let test_case = ClientBuilder::new(&url) .unwrap() @@ -37,7 +34,7 @@ fn main() { future::loop_fn(duplex, |stream| { stream.into_future() .or_else(|(err, stream)| { - println!("message transmission was an error: {:?}", err); + println!("Could not receive message: {:?}", err); stream.send(OwnedMessage::Close(None)).map(|s| (None, s)) }) .and_then(|(msg, stream)| match msg { @@ -66,52 +63,47 @@ fn main() { } None => future::ok(Loop::Break(())).boxed(), }) - .map_err(|e| { - println!("Error sending or other: {:?}", e); - e - }) }) }) + .map(move |_| { + println!("Test case {} is finished!", case_id); + }) .or_else(move |err| { println!("Test case {} ended with an error: {:?}", case_id, err); Ok(()) as Result<(), ()> }); - // all_tests.push(test_case); core.run(test_case).ok(); } - // core.run(future::join_all(all_tests)).ok(); - - update_reports(addr.clone(), agent, &handle); + update_reports(addr.clone(), agent, &mut core); + println!("Test suite finished!"); } -fn get_case_count(addr: String, handle: &Handle) -> usize { +fn get_case_count(addr: String, core: &mut Core) -> usize { let url = addr + "/getCaseCount"; + let err = "Unsupported message in /getCaseCount"; - ClientBuilder::new(&url) + let counter = ClientBuilder::new(&url) .unwrap() - .async_connect_insecure(&handle) - .and_then(|(stream, _)| stream.into_future().map_err(|e| e.0)) + .async_connect_insecure(&core.handle()) + .and_then(|(s, _)| s.into_future().map_err(|e| e.0)) .and_then(|(msg, _)| match msg { Some(OwnedMessage::Text(txt)) => Ok(txt.parse().unwrap()), - _ => Err(WebSocketError::ProtocolError("Unsupported message in /getCaseCount")), - }) - .wait() - .unwrap() + _ => Err(WebSocketError::ProtocolError(err)), + }); + core.run(counter).unwrap() } -fn update_reports(addr: String, agent: &str, handle: &Handle) { +fn update_reports(addr: String, agent: &str, core: &mut Core) { println!("Updating reports..."); let url = addr + "/updateReports?agent=" + agent; - ClientBuilder::new(&url) + let updater = ClientBuilder::new(&url) .unwrap() - .async_connect_insecure(&handle) - .and_then(|(sink, _)| sink.send(OwnedMessage::Close(None))) - .wait() - .unwrap(); + .async_connect_insecure(&core.handle()) + .and_then(|(sink, _)| sink.send(OwnedMessage::Close(None))); + core.run(updater).unwrap(); println!("Reports updated."); - println!("Test suite finished!"); } From 312c5a1e379c68975f273088f908676bc15ac228 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Mon, 22 May 2017 00:04:33 -0400 Subject: [PATCH 22/52] Updated async client example. --- ...ync-client.rs => async-autobahn-client.rs} | 0 examples/async-client.rs | 65 +++++++------------ 2 files changed, 24 insertions(+), 41 deletions(-) rename examples/{autobahn-async-client.rs => async-autobahn-client.rs} (100%) diff --git a/examples/autobahn-async-client.rs b/examples/async-autobahn-client.rs similarity index 100% rename from examples/autobahn-async-client.rs rename to examples/async-autobahn-client.rs diff --git a/examples/async-client.rs b/examples/async-client.rs index 786b6cc70e..999f5906f3 100644 --- a/examples/async-client.rs +++ b/examples/async-client.rs @@ -1,26 +1,22 @@ extern crate websocket; -extern crate tokio_core; -extern crate tokio_io; extern crate futures; - -const CONNECTION: &'static str = "ws://127.0.0.1:2794"; +extern crate tokio_core; use std::thread; use std::io::stdin; - use tokio_core::reactor::Core; - +use futures::future::Future; use futures::sink::Sink; use futures::stream::Stream; use futures::sync::mpsc; - use websocket::result::WebSocketError; -use websocket::Message; -use websocket::message::Type; -use websocket::client::ClientBuilder; +use websocket::{ClientBuilder, OwnedMessage}; + +const CONNECTION: &'static str = "ws://127.0.0.1:2794"; fn main() { println!("Connecting to {}", CONNECTION); + let mut core = Core::new().unwrap(); // standard in isn't supported in mio yet, so we use a thread // see https://github.com/carllerche/mio/issues/321 @@ -34,9 +30,9 @@ fn main() { let trimmed = input.trim(); let (close, msg) = match trimmed { - "/close" => (true, Message::close()), - "/ping" => (false, Message::ping(b"PING".to_vec())), - _ => (false, Message::text(trimmed.to_string())), + "/close" => (true, OwnedMessage::Close(None)), + "/ping" => (false, OwnedMessage::Ping(b"PING".to_vec())), + _ => (false, OwnedMessage::Text(trimmed.to_string())), }; stdin_sink.send(msg) @@ -48,35 +44,22 @@ fn main() { } }); - let mut core = Core::new().unwrap(); - let handle = core.handle(); - - let client = ClientBuilder::new(CONNECTION) + let runner = ClientBuilder::new(CONNECTION) .unwrap() .add_protocol("rust-websocket") - .connect_insecure() - .unwrap() - .async(&handle) - .unwrap(); - - println!("Successfully connected"); - - let (sink, stream) = client.split(); - - let runner = - stream.filter_map(|message| { - match message.opcode { - Type::Close => Some(Message::close()), - Type::Ping => Some(Message::pong(message.payload)), - _ => { - // Say what we received - println!("Received Message: {:?}", message); - None - } - } - }) - .select(stdin_ch.map_err(|_| WebSocketError::NoDataAvailable)) - .forward(sink); - + .async_connect_insecure(&core.handle()) + .and_then(|(duplex, _)| { + let (sink, stream) = duplex.split(); + stream.filter_map(|message| { + println!("Received Message: {:?}", message); + match message { + OwnedMessage::Close(e) => Some(OwnedMessage::Close(e)), + OwnedMessage::Ping(d) => Some(OwnedMessage::Pong(d)), + _ => None, + } + }) + .select(stdin_ch.map_err(|_| WebSocketError::NoDataAvailable)) + .forward(sink) + }); core.run(runner).unwrap(); } From 5e6c03a612a481fe3a138793d6011b0c1fffac85 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Mon, 22 May 2017 00:07:48 -0400 Subject: [PATCH 23/52] Fixed parsing bug in hyper codec. --- src/client/builder.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/client/builder.rs b/src/client/builder.rs index 5da030b325..b5e07622d3 100644 --- a/src/client/builder.rs +++ b/src/client/builder.rs @@ -695,10 +695,12 @@ mod async_builder { let (response, bytes_read) = { let mut reader = BufReader::new(&*src as &[u8]); let res = match parse_response(&mut reader) { - Ok(r) => r, - Err(hyper::Error::Io(ref err)) if err.kind() == - io::ErrorKind::UnexpectedEof => return Ok(None), + Err(hyper::Error::Io(ref e)) if e.kind() == io::ErrorKind::UnexpectedEof => { + return Ok(None) + } + Err(hyper::Error::TooLarge) => return Ok(None), Err(e) => return Err(e.into()), + Ok(r) => r, }; let (_, _, pos, _) = reader.into_parts(); (res, pos) From 4077486bb76da0974023d5b1d81735c9b239b74c Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Mon, 22 May 2017 00:41:53 -0400 Subject: [PATCH 24/52] Reorganization into much cleaner structure. --- src/buffer.rs | 251 ---------------------------- src/client/builder.rs | 302 ++++++++++++++-------------------- src/client/mod.rs | 8 +- src/codec/http.rs | 60 +++++++ src/codec/mod.rs | 2 + src/{codec.rs => codec/ws.rs} | 0 src/dataframe.rs | 1 - src/lib.rs | 1 - src/message.rs | 6 +- 9 files changed, 198 insertions(+), 433 deletions(-) delete mode 100644 src/buffer.rs create mode 100644 src/codec/http.rs create mode 100644 src/codec/mod.rs rename src/{codec.rs => codec/ws.rs} (100%) diff --git a/src/buffer.rs b/src/buffer.rs deleted file mode 100644 index b10e0f3ffe..0000000000 --- a/src/buffer.rs +++ /dev/null @@ -1,251 +0,0 @@ -use std::cmp; -use std::io::{self, Read, Write, BufRead}; -use std::fmt::Arguments; -#[cfg(feature="async")] -use futures::Poll; - -#[cfg(feature="async")] -use tokio_io::{AsyncWrite, AsyncRead}; -#[cfg(feature="async")] -use bytes::Buf; - -pub struct BufReader { - inner: R, - buf: Vec, - pos: usize, - cap: usize, -} - -const INIT_BUFFER_SIZE: usize = 4096; -const MAX_BUFFER_SIZE: usize = 8192 + 4096 * 100; - -impl BufReader { - #[inline] - pub fn new(rdr: R) -> BufReader { - BufReader::with_capacity(rdr, INIT_BUFFER_SIZE) - } - - #[inline] - pub fn from_parts(rdr: R, buf: Vec, pos: usize, cap: usize) -> BufReader { - BufReader { - inner: rdr, - buf: buf, - pos: pos, - cap: cap, - } - } - - #[inline] - pub fn with_capacity(rdr: R, cap: usize) -> BufReader { - BufReader { - inner: rdr, - buf: vec![0; cap], - pos: 0, - cap: 0, - } - } - - #[inline] - pub fn get_ref(&self) -> &R { - &self.inner - } - - #[inline] - pub fn get_mut(&mut self) -> &mut R { - &mut self.inner - } - - #[inline] - pub fn get_buf(&self) -> &[u8] { - if self.pos < self.cap { - &self.buf[self.pos..self.cap] - } else { - &[] - } - } - - /// Extracts the buffer from this reader. Return the current cursor position - /// and the position of the last valid byte. - /// - /// This operation does not copy the buffer. Instead, it directly returns - /// the internal buffer. As a result, this reader will no longer have any - /// buffered contents and any subsequent read from this reader will not - /// include the returned buffered contents. - #[inline] - pub fn take_buf(&mut self) -> (Vec, usize, usize) { - let (pos, cap) = (self.pos, self.cap); - self.pos = 0; - self.cap = 0; - - let mut output = vec![0; INIT_BUFFER_SIZE]; - ::std::mem::swap(&mut self.buf, &mut output); - (output, pos, cap) - } - - #[inline] - pub fn into_inner(self) -> R { - self.inner - } - - #[inline] - pub fn into_parts(self) -> (R, Vec, usize, usize) { - (self.inner, self.buf, self.pos, self.cap) - } - - #[inline] - pub fn read_into_buf(&mut self) -> io::Result { - self.maybe_reserve(); - let v = &mut self.buf; - if self.cap < v.capacity() { - let nread = try!(self.inner.read(&mut v[self.cap..])); - self.cap += nread; - Ok(nread) - } else { - Ok(0) - } - } - - #[inline] - fn maybe_reserve(&mut self) { - let cap = self.buf.capacity(); - if self.cap == cap && cap < MAX_BUFFER_SIZE { - self.buf.reserve(cmp::min(cap * 4, MAX_BUFFER_SIZE) - cap); - let new = self.buf.capacity() - self.buf.len(); - unsafe { grow_zerofill(&mut self.buf, new) } - } - } -} - -#[inline] -unsafe fn grow_zerofill(buf: &mut Vec, additional: usize) { - use std::ptr; - let len = buf.len(); - buf.set_len(len + additional); - ptr::write_bytes(buf.as_mut_ptr().offset(len as isize), 0, additional); -} - -impl Read for BufReader { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - if self.cap == self.pos && buf.len() >= self.buf.len() { - return self.inner.read(buf); - } - let nread = { - let mut rem = try!(self.fill_buf()); - try!(rem.read(buf)) - }; - self.consume(nread); - Ok(nread) - } -} - -impl Write for BufReader { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - self.get_mut().write(buf) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - self.get_mut().flush() - } - - #[inline] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - self.get_mut().write_all(buf) - } - - #[inline] - fn write_fmt(&mut self, fmt: Arguments) -> io::Result<()> { - self.get_mut().write_fmt(fmt) - } -} - -impl BufRead for BufReader { - fn fill_buf(&mut self) -> io::Result<&[u8]> { - if self.pos == self.cap { - self.cap = try!(self.inner.read(&mut self.buf)); - self.pos = 0; - } - Ok(&self.buf[self.pos..self.cap]) - } - - #[inline] - fn consume(&mut self, amt: usize) { - self.pos = cmp::min(self.pos + amt, self.cap); - if self.pos == self.cap { - self.pos = 0; - self.cap = 0; - } - } -} - -#[cfg(feature="async")] -impl AsyncRead for BufReader { - #[inline] - unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { - self.get_ref().prepare_uninitialized_buffer(buf) - } -} - -#[cfg(feature="async")] -impl AsyncWrite for BufReader { - #[inline] - fn shutdown(&mut self) -> Poll<(), io::Error> { - self.get_mut().shutdown() - } - - #[inline] - fn write_buf(&mut self, buf: &mut B) -> Poll { - self.get_mut().write_buf(buf) - } -} - -#[cfg(test)] -mod tests { - - use std::io::{self, Read, BufRead}; - use super::BufReader; - - struct SlowRead(u8); - - impl Read for SlowRead { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - let state = self.0; - self.0 += 1; - (&match state % 3 { - 0 => b"foo", - 1 => b"bar", - _ => b"baz", - } - [..]) - .read(buf) - } - } - - #[test] - fn test_consume_and_get_buf() { - let mut rdr = BufReader::new(SlowRead(0)); - rdr.read_into_buf().unwrap(); - rdr.consume(1); - assert_eq!(rdr.get_buf(), b"oo"); - rdr.read_into_buf().unwrap(); - rdr.read_into_buf().unwrap(); - assert_eq!(rdr.get_buf(), b"oobarbaz"); - rdr.consume(5); - assert_eq!(rdr.get_buf(), b"baz"); - rdr.consume(3); - assert_eq!(rdr.get_buf(), b""); - assert_eq!(rdr.pos, 0); - assert_eq!(rdr.cap, 0); - } - - #[test] - fn test_resize() { - let raw = b"hello world"; - let mut rdr = BufReader::with_capacity(&raw[..], 5); - rdr.read_into_buf().unwrap(); - assert_eq!(rdr.get_buf(), b"hello"); - rdr.read_into_buf().unwrap(); - assert_eq!(rdr.get_buf(), b"hello world"); - } -} diff --git a/src/client/builder.rs b/src/client/builder.rs index b5e07622d3..5f95a94b9d 100644 --- a/src/client/builder.rs +++ b/src/client/builder.rs @@ -10,6 +10,8 @@ use hyper::http::h1::Incoming; use hyper::http::RawStatus; use hyper::status::StatusCode; use hyper::buffer::BufReader; +use hyper::method::Method; +use hyper::uri::RequestUri; use hyper::http::h1::parse_response; use hyper::header::{Headers, Header, HeaderFormat, Host, Connection, ConnectionOption, Upgrade, Protocol, ProtocolName}; @@ -23,27 +25,21 @@ use result::{WSUrlErrorKind, WebSocketResult, WebSocketError}; #[cfg(feature="ssl")] use stream::NetworkStream; use stream::{self, Stream}; -use message::OwnedMessage; use super::Client; #[cfg(feature="async")] -use super::async; -#[cfg(feature="async")] -use tokio_core::net::TcpStream as AsyncTcpStream; -#[cfg(feature="async")] -use tokio_core::reactor::Handle; -#[cfg(feature="async")] -use futures::{Future, Sink}; -#[cfg(feature="async")] -use futures::future; -#[cfg(feature="async")] -use futures::Stream as FutureStream; -#[cfg(feature="async")] -pub use codec::{MessageCodec, Context}; +mod async_imports { + pub use super::super::async; + pub use tokio_core::net::TcpStream as AsyncTcpStream; + pub use tokio_core::reactor::Handle; + pub use futures::{Future, Sink}; + pub use futures::future; + pub use futures::Stream as FutureStream; + pub use codec::ws::{MessageCodec, Context}; +} +use self::async_imports::*; -pub type AsyncClientNew = Box, Headers), - Error = WebSocketError>>; /// Build clients with a builder-style API /// This makes it easy to create and configure a websocket @@ -356,45 +352,6 @@ impl<'u> ClientBuilder<'u> { self.headers.get::() } - fn extract_host_port(&self, secure: Option) -> WebSocketResult<(&str, u16)> { - let port = match (self.url.port(), secure) { - (Some(port), _) => port, - (None, None) if self.url.scheme() == "wss" => 443, - (None, None) => 80, - (None, Some(true)) => 443, - (None, Some(false)) => 80, - }; - let host = match self.url.host_str() { - Some(h) => h, - None => return Err(WebSocketError::WebSocketUrlError(WSUrlErrorKind::NoHostName)), - }; - - Ok((host, port)) - } - - fn establish_tcp(&mut self, secure: Option) -> WebSocketResult { - Ok(TcpStream::connect(self.extract_host_port(secure)?)?) - } - - #[cfg(feature="ssl")] - fn wrap_ssl( - &self, - tcp_stream: TcpStream, - connector: Option, - ) -> WebSocketResult> { - let host = match self.url.host_str() { - Some(h) => h, - None => return Err(WebSocketError::WebSocketUrlError(WSUrlErrorKind::NoHostName)), - }; - let connector = match connector { - Some(c) => c, - None => try!(SslConnectorBuilder::new(SslMethod::tls())).build(), - }; - - let ssl_stream = try!(connector.connect(host, tcp_stream)); - Ok(ssl_stream) - } - /// Connect to a server (finally)! /// This will use a `Box` to represent either an SSL /// connection or a normal TCP connection, what to use will be decided @@ -467,77 +424,6 @@ impl<'u> ClientBuilder<'u> { self.connect_on(ssl_stream) } - fn build_request(&mut self) -> String { - // enter host if available (unix sockets don't have hosts) - if let Some(host) = self.url.host_str() { - self.headers - .set(Host { - hostname: host.to_string(), - port: self.url.port(), - }); - } - - self.headers - .set(Connection(vec![ - ConnectionOption::ConnectionHeader(UniCase("Upgrade".to_string())) - ])); - - self.headers - .set(Upgrade(vec![ - Protocol { - name: ProtocolName::WebSocket, - version: None, - }, - ])); - - if !self.version_set { - self.headers.set(WebSocketVersion::WebSocket13); - } - - if !self.key_set { - self.headers.set(WebSocketKey::new()); - } - - // send request - let resource = self.url[Position::BeforePath..Position::AfterQuery].to_owned(); - format!("GET {} {}\r\n{}\r\n", resource, self.version, self.headers) - } - - fn validate(&self, response: &Incoming) -> WebSocketResult<()> { - let status = StatusCode::from_u16(response.subject.0); - - if status != StatusCode::SwitchingProtocols { - return Err(WebSocketError::ResponseError("Status code must be Switching Protocols")); - } - - let key = try!(self.headers - .get::() - .ok_or(WebSocketError::RequestError("Request Sec-WebSocket-Key was invalid"))); - - if response.headers.get() != Some(&(WebSocketAccept::new(key))) { - return Err(WebSocketError::ResponseError("Sec-WebSocket-Accept is invalid")); - } - - if response.headers.get() != - Some(&(Upgrade(vec![ - Protocol { - name: ProtocolName::WebSocket, - version: None, - }, - ]))) { - return Err(WebSocketError::ResponseError("Upgrade field must be WebSocket")); - } - - if self.headers.get() != - Some(&(Connection(vec![ - ConnectionOption::ConnectionHeader(UniCase("Upgrade".to_string())), - ]))) { - return Err(WebSocketError::ResponseError("Connection field must be 'Upgrade'")); - } - - Ok(()) - } - // TODO: similar ability for server? /// Connects to a websocket server on any stream you would like. /// Possible streams: @@ -572,7 +458,9 @@ impl<'u> ClientBuilder<'u> { where S: Stream { // send request - stream.write_all(self.build_request().as_bytes())?; + let resource = self.build_request(); + try!(write!(stream, "GET {} {}\r\n", resource, self.version)); + try!(write!(stream, "{}\r\n", self.headers)); // wait for a response let mut reader = BufReader::new(stream); @@ -587,7 +475,7 @@ impl<'u> ClientBuilder<'u> { // TODO: add timeout option for connecting // TODO: add conveniences like .response_to_pings, .send_close, etc. #[cfg(feature="async")] - pub fn async_connect_insecure(self, handle: &Handle) -> AsyncClientNew { + pub fn async_connect_insecure(self, handle: &Handle) -> async::ClientNew { // get the address to connect to, return an error future if ther's a problem let address = match self.extract_host_port(Some(false)).and_then(|p| Ok(p.to_socket_addrs()?)) { @@ -621,7 +509,7 @@ impl<'u> ClientBuilder<'u> { } #[cfg(feature="async")] - pub fn async_connect_on(self, stream: S) -> AsyncClientNew + pub fn async_connect_on(self, stream: S) -> async::ClientNew where S: stream::AsyncStream + Send + 'static { let mut builder = ClientBuilder { @@ -631,8 +519,13 @@ impl<'u> ClientBuilder<'u> { version_set: self.version_set, key_set: self.key_set, }; - let request = builder.build_request(); - let framed = stream.framed(async_builder::WsHandshakeCodec); + let resource = builder.build_request(); + let framed = stream.framed(::codec::http::HttpCodec); + let request = Incoming { + version: builder.version, + headers: builder.headers.clone(), + subject: (Method::Get, RequestUri::AbsolutePath(resource)), + }; let future = framed // send request @@ -659,56 +552,115 @@ impl<'u> ClientBuilder<'u> { Box::new(future) } -} -#[cfg(feature="async")] -mod async_builder { - use super::*; - use hyper; - use std::io::{self, Write}; - use tokio_io::codec::{Decoder, Encoder}; - use bytes::BytesMut; - use bytes::BufMut; - - pub struct WsHandshakeCodec; - - impl Encoder for WsHandshakeCodec { - type Item = String; - type Error = io::Error; - - fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { - let byte_len = item.as_bytes().len(); - if byte_len > dst.remaining_mut() { - dst.reserve(byte_len); - } - dst.writer().write(item.as_bytes()).map(|_| ()) + fn build_request(&mut self) -> String { + // enter host if available (unix sockets don't have hosts) + if let Some(host) = self.url.host_str() { + self.headers + .set(Host { + hostname: host.to_string(), + port: self.url.port(), + }); + } + + self.headers + .set(Connection(vec![ + ConnectionOption::ConnectionHeader(UniCase("Upgrade".to_string())) + ])); + + self.headers + .set(Upgrade(vec![ + Protocol { + name: ProtocolName::WebSocket, + version: None, + }, + ])); + + if !self.version_set { + self.headers.set(WebSocketVersion::WebSocket13); + } + + if !self.key_set { + self.headers.set(WebSocketKey::new()); } + + // send request + let resource = self.url[Position::BeforePath..Position::AfterQuery].to_owned(); + resource } - impl Decoder for WsHandshakeCodec { - type Item = Incoming; - type Error = WebSocketError; - - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - // check if we get a request from hyper - // TODO: this is ineffecient, but hyper does not give us a better way to parse - let (response, bytes_read) = { - let mut reader = BufReader::new(&*src as &[u8]); - let res = match parse_response(&mut reader) { - Err(hyper::Error::Io(ref e)) if e.kind() == io::ErrorKind::UnexpectedEof => { - return Ok(None) - } - Err(hyper::Error::TooLarge) => return Ok(None), - Err(e) => return Err(e.into()), - Ok(r) => r, - }; - let (_, _, pos, _) = reader.into_parts(); - (res, pos) - }; + fn validate(&self, response: &Incoming) -> WebSocketResult<()> { + let status = StatusCode::from_u16(response.subject.0); + + if status != StatusCode::SwitchingProtocols { + return Err(WebSocketError::ResponseError("Status code must be Switching Protocols")); + } + + let key = try!(self.headers + .get::() + .ok_or(WebSocketError::RequestError("Request Sec-WebSocket-Key was invalid"))); - src.split_to(bytes_read); - Ok(Some(response)) + if response.headers.get() != Some(&(WebSocketAccept::new(key))) { + return Err(WebSocketError::ResponseError("Sec-WebSocket-Accept is invalid")); } + + if response.headers.get() != + Some(&(Upgrade(vec![ + Protocol { + name: ProtocolName::WebSocket, + version: None, + }, + ]))) { + return Err(WebSocketError::ResponseError("Upgrade field must be WebSocket")); + } + + if self.headers.get() != + Some(&(Connection(vec![ + ConnectionOption::ConnectionHeader(UniCase("Upgrade".to_string())), + ]))) { + return Err(WebSocketError::ResponseError("Connection field must be 'Upgrade'")); + } + + Ok(()) + } + + fn extract_host_port(&self, secure: Option) -> WebSocketResult<(&str, u16)> { + let port = match (self.url.port(), secure) { + (Some(port), _) => port, + (None, None) if self.url.scheme() == "wss" => 443, + (None, None) => 80, + (None, Some(true)) => 443, + (None, Some(false)) => 80, + }; + let host = match self.url.host_str() { + Some(h) => h, + None => return Err(WebSocketError::WebSocketUrlError(WSUrlErrorKind::NoHostName)), + }; + + Ok((host, port)) + } + + fn establish_tcp(&mut self, secure: Option) -> WebSocketResult { + Ok(TcpStream::connect(self.extract_host_port(secure)?)?) + } + + #[cfg(feature="ssl")] + fn wrap_ssl( + &self, + tcp_stream: TcpStream, + connector: Option, + ) -> WebSocketResult> { + let host = match self.url.host_str() { + Some(h) => h, + None => return Err(WebSocketError::WebSocketUrlError(WSUrlErrorKind::NoHostName)), + }; + let connector = match connector { + Some(c) => c, + None => try!(SslConnectorBuilder::new(SslMethod::tls())).build(), + }; + + let ssl_stream = try!(connector.connect(host, tcp_stream)); + Ok(ssl_stream) } } diff --git a/src/client/mod.rs b/src/client/mod.rs index 504545d9cf..8841d688c8 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -13,7 +13,7 @@ use ws::sender::Sender as SenderTrait; use ws::receiver::{DataFrameIterator, MessageIterator}; use ws::receiver::Receiver as ReceiverTrait; use message::OwnedMessage; -use result::WebSocketResult; +use result::{WebSocketResult, WebSocketError}; use stream::{AsTcpStream, Stream, Splittable, Shutdown}; use dataframe::DataFrame; use header::{WebSocketProtocol, WebSocketExtensions}; @@ -30,14 +30,16 @@ pub use self::builder::{ClientBuilder, Url, ParseError}; #[cfg(feature="async")] pub mod async { + use super::*; pub use tokio_core::reactor::Handle; pub use tokio_io::codec::Framed; pub use tokio_core::net::TcpStream; - use codec::MessageCodec; - use message::OwnedMessage; + pub use futures::Future; + use codec::ws::MessageCodec; pub type Client = Framed>; + pub type ClientNew = Box, Headers), Error = WebSocketError>>; } /// Represents a WebSocket client, which can send and receive messages/data frames. diff --git a/src/codec/http.rs b/src/codec/http.rs new file mode 100644 index 0000000000..8f2cfa8db1 --- /dev/null +++ b/src/codec/http.rs @@ -0,0 +1,60 @@ +use hyper; +use hyper::http::h1::Incoming; +use hyper::http::h1::parse_response; +use hyper::http::RawStatus; +use hyper::method::Method; +use hyper::uri::RequestUri; +use hyper::buffer::BufReader; +use std::io::{self, Write}; +use tokio_io::codec::{Decoder, Encoder}; +use bytes::BytesMut; +use bytes::BufMut; +use result::WebSocketError; + +#[derive(Copy, Clone, Debug)] +pub struct HttpCodec; + +impl Encoder for HttpCodec { + type Item = Incoming<(Method, RequestUri)>; + type Error = io::Error; + + fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { + // TODO: optomize this! + let request = format!("{} {} {}\r\n{}\r\n", + item.subject.0, + item.subject.1, + item.version, + item.headers); + let byte_len = request.as_bytes().len(); + if byte_len > dst.remaining_mut() { + dst.reserve(byte_len); + } + dst.writer().write(request.as_bytes()).map(|_| ()) + } +} + +impl Decoder for HttpCodec { + type Item = Incoming; + type Error = WebSocketError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + // check if we get a request from hyper + // TODO: this is ineffecient, but hyper does not give us a better way to parse + let (response, bytes_read) = { + let mut reader = BufReader::new(&*src as &[u8]); + let res = match parse_response(&mut reader) { + Err(hyper::Error::Io(ref e)) if e.kind() == io::ErrorKind::UnexpectedEof => { + return Ok(None) + } + Err(hyper::Error::TooLarge) => return Ok(None), + Err(e) => return Err(e.into()), + Ok(r) => r, + }; + let (_, _, pos, _) = reader.into_parts(); + (res, pos) + }; + + src.split_to(bytes_read); + Ok(Some(response)) + } +} diff --git a/src/codec/mod.rs b/src/codec/mod.rs new file mode 100644 index 0000000000..f2066e6c07 --- /dev/null +++ b/src/codec/mod.rs @@ -0,0 +1,2 @@ +pub mod http; +pub mod ws; diff --git a/src/codec.rs b/src/codec/ws.rs similarity index 100% rename from src/codec.rs rename to src/codec/ws.rs diff --git a/src/dataframe.rs b/src/dataframe.rs index a2f71531e9..0bc5ac0603 100644 --- a/src/dataframe.rs +++ b/src/dataframe.rs @@ -1,5 +1,4 @@ //! Module containing the default implementation of data frames. -use std::str::from_utf8; use std::io::{Read, Write}; use result::{WebSocketResult, WebSocketError}; use ws::dataframe::DataFrame as DataFrameable; diff --git a/src/lib.rs b/src/lib.rs index d995d355ea..bd32af0034 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,4 +96,3 @@ pub mod receiver; pub mod sender; #[cfg(feature="async")] pub mod codec; -pub mod buffer; diff --git a/src/message.rs b/src/message.rs index 9795ce2e94..56746321ae 100644 --- a/src/message.rs +++ b/src/message.rs @@ -153,7 +153,8 @@ impl<'a> ws::dataframe::DataFrame for Message<'a> { fn take_payload(self) -> Vec { if let Some(reason) = self.cd_status_code { let mut buf = Vec::with_capacity(2 + self.payload.len()); - buf.write_u16::(reason); + buf.write_u16::(reason) + .expect("failed to write close code in take_payload"); buf.append(&mut self.payload.into_owned()); buf } else { @@ -376,7 +377,8 @@ impl ws::dataframe::DataFrame for OwnedMessage { match data { Some(c) => { let mut buf = Vec::with_capacity(2 + c.reason.len()); - buf.write_u16::(c.status_code); + buf.write_u16::(c.status_code) + .expect("failed to write close code in take_payload"); buf.append(&mut c.reason.into_bytes()); buf } From faf93509fe959e1ca521174b88f51172969b3ff4 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Mon, 22 May 2017 00:47:27 -0400 Subject: [PATCH 25/52] Updated server example. --- examples/server.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/examples/server.rs b/examples/server.rs index 3dd9799979..01ccdbc91d 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -1,8 +1,7 @@ extern crate websocket; use std::thread; -use websocket::{Server, Message}; -use websocket::message::Type; +use websocket::{Server, OwnedMessage}; fn main() { let server = Server::bind("127.0.0.1:2794").unwrap(); @@ -21,23 +20,23 @@ fn main() { println!("Connection from {}", ip); - let message: Message = Message::text("Hello".to_string()); + let message = OwnedMessage::Text("Hello".to_string()); client.send_message(&message).unwrap(); let (mut receiver, mut sender) = client.split().unwrap(); for message in receiver.incoming_messages() { - let message: Message = message.unwrap(); + let message = message.unwrap(); - match message.opcode { - Type::Close => { - let message = Message::close(); + match message { + OwnedMessage::Close(_) => { + let message = OwnedMessage::Close(None); sender.send_message(&message).unwrap(); println!("Client {} disconnected", ip); return; } - Type::Ping => { - let message = Message::pong(message.payload); + OwnedMessage::Ping(ping) => { + let message = OwnedMessage::Pong(ping); sender.send_message(&message).unwrap(); } _ => sender.send_message(&message).unwrap(), From 2cd13813064161a04ed12c55ca7f3de4d5274778 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Mon, 22 May 2017 01:13:47 -0400 Subject: [PATCH 26/52] Switched from openssl to native-tls. --- Cargo.toml | 4 ++-- src/client/builder.rs | 14 ++++++------- src/lib.rs | 4 ++-- src/result.rs | 33 +++++++++++++++-------------- src/server/mod.rs | 48 +++++++++++++++++++++---------------------- src/stream.rs | 4 ++-- 6 files changed, 53 insertions(+), 54 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2f9966e3cf..01ca3ea036 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ futures = { version = "^0.1", optional = true } tokio-core = { version = "0.1.7", optional = true } tokio-io = { version = "0.1.1", optional = true } bytes = { version = "^0.4", optional = true } -openssl = { version = "^0.9.10", optional = true } +native-tls = { version = "^0.1.2", optional = true } base64 = "^0.5" [dev-dependencies] @@ -40,6 +40,6 @@ rev = "18be60c56b0e932265a9e0301d14d5084577ca33" [features] default = ["ssl", "async"] -ssl = ["openssl"] +ssl = ["native-tls"] nightly = ["hyper/nightly"] async = ["tokio-core", "tokio-io", "bytes", "futures"] diff --git a/src/client/builder.rs b/src/client/builder.rs index 5f95a94b9d..09f4f2a230 100644 --- a/src/client/builder.rs +++ b/src/client/builder.rs @@ -17,7 +17,7 @@ use hyper::header::{Headers, Header, HeaderFormat, Host, Connection, ConnectionO Protocol, ProtocolName}; use unicase::UniCase; #[cfg(feature="ssl")] -use openssl::ssl::{SslMethod, SslStream, SslConnector, SslConnectorBuilder}; +use native_tls::{TlsStream, TlsConnector}; use header::extensions::Extension; use header::{WebSocketAccept, WebSocketKey, WebSocketVersion, WebSocketProtocol, WebSocketExtensions, Origin}; @@ -374,7 +374,7 @@ impl<'u> ClientBuilder<'u> { #[cfg(feature="ssl")] pub fn connect( &mut self, - ssl_config: Option, + ssl_config: Option, ) -> WebSocketResult>> { let tcp_stream = try!(self.establish_tcp(None)); @@ -415,8 +415,8 @@ impl<'u> ClientBuilder<'u> { #[cfg(feature="ssl")] pub fn connect_secure( &mut self, - ssl_config: Option, - ) -> WebSocketResult>> { + ssl_config: Option, + ) -> WebSocketResult>> { let tcp_stream = try!(self.establish_tcp(Some(true))); let ssl_stream = try!(self.wrap_ssl(tcp_stream, ssl_config)); @@ -648,15 +648,15 @@ impl<'u> ClientBuilder<'u> { fn wrap_ssl( &self, tcp_stream: TcpStream, - connector: Option, - ) -> WebSocketResult> { + connector: Option, + ) -> WebSocketResult> { let host = match self.url.host_str() { Some(h) => h, None => return Err(WebSocketError::WebSocketUrlError(WSUrlErrorKind::NoHostName)), }; let connector = match connector { Some(c) => c, - None => try!(SslConnectorBuilder::new(SslMethod::tls())).build(), + None => TlsConnector::builder()?.build()?, }; let ssl_stream = try!(connector.connect(host, tcp_stream)); diff --git a/src/lib.rs b/src/lib.rs index bd32af0034..bb0992ec20 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,9 +42,9 @@ pub extern crate url; extern crate rand; extern crate byteorder; extern crate sha1; -#[cfg(feature="ssl")] -extern crate openssl; extern crate base64; +#[cfg(feature="ssl")] +extern crate native_tls; #[cfg(feature="async")] extern crate tokio_core; #[cfg(feature="async")] diff --git a/src/result.rs b/src/result.rs index f87b2ce5cc..47f4d4bbd3 100644 --- a/src/result.rs +++ b/src/result.rs @@ -9,9 +9,9 @@ use hyper::Error as HttpError; use url::ParseError; #[cfg(feature="ssl")] -use openssl::error::ErrorStack as SslError; +use native_tls::Error as TlsError; #[cfg(feature="ssl")] -use openssl::ssl::HandshakeError as SslHandshakeError; +use native_tls::HandshakeError as TlsHandshakeError; /// The type used for WebSocket results pub type WebSocketResult = Result; @@ -39,13 +39,13 @@ pub enum WebSocketError { WebSocketUrlError(WSUrlErrorKind), /// An SSL error #[cfg(feature="ssl")] - SslError(SslError), + TlsError(TlsError), /// an ssl handshake failure #[cfg(feature="ssl")] - SslHandshakeFailure, + TlsHandshakeFailure, /// an ssl handshake interruption #[cfg(feature="ssl")] - SslHandshakeInterruption, + TlsHandshakeInterruption, /// A UTF-8 error Utf8Error(Utf8Error), } @@ -70,11 +70,11 @@ impl Error for WebSocketError { WebSocketError::HttpError(_) => "HTTP failure", WebSocketError::UrlError(_) => "URL failure", #[cfg(feature="ssl")] - WebSocketError::SslError(_) => "SSL failure", + WebSocketError::TlsError(_) => "TLS failure", #[cfg(feature="ssl")] - WebSocketError::SslHandshakeFailure => "SSL Handshake failure", + WebSocketError::TlsHandshakeFailure => "TLS Handshake failure", #[cfg(feature="ssl")] - WebSocketError::SslHandshakeInterruption => "SSL Handshake interrupted", + WebSocketError::TlsHandshakeInterruption => "TLS Handshake interrupted", WebSocketError::Utf8Error(_) => "UTF-8 failure", WebSocketError::WebSocketUrlError(_) => "WebSocket URL failure", } @@ -86,7 +86,7 @@ impl Error for WebSocketError { WebSocketError::HttpError(ref error) => Some(error), WebSocketError::UrlError(ref error) => Some(error), #[cfg(feature="ssl")] - WebSocketError::SslError(ref error) => Some(error), + WebSocketError::TlsError(ref error) => Some(error), WebSocketError::Utf8Error(ref error) => Some(error), WebSocketError::WebSocketUrlError(ref error) => Some(error), _ => None, @@ -116,19 +116,18 @@ impl From for WebSocketError { } #[cfg(feature="ssl")] -impl From for WebSocketError { - fn from(err: SslError) -> WebSocketError { - WebSocketError::SslError(err) +impl From for WebSocketError { + fn from(err: TlsError) -> WebSocketError { + WebSocketError::TlsError(err) } } #[cfg(feature="ssl")] -impl From> for WebSocketError { - fn from(err: SslHandshakeError) -> WebSocketError { +impl From> for WebSocketError { + fn from(err: TlsHandshakeError) -> WebSocketError { match err { - SslHandshakeError::SetupFailure(err) => WebSocketError::SslError(err), - SslHandshakeError::Failure(_) => WebSocketError::SslHandshakeFailure, - SslHandshakeError::Interrupted(_) => WebSocketError::SslHandshakeInterruption, + TlsHandshakeError::Failure(_) => WebSocketError::TlsHandshakeFailure, + TlsHandshakeError::Interrupted(_) => WebSocketError::TlsHandshakeInterruption, } } } diff --git a/src/server/mod.rs b/src/server/mod.rs index 801ae6b502..444b309de3 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -3,7 +3,7 @@ use std::net::{SocketAddr, ToSocketAddrs, TcpListener, TcpStream}; use std::io; use std::convert::Into; #[cfg(feature="ssl")] -use openssl::ssl::{SslStream, SslAcceptor}; +use native_tls::{TlsStream, TlsAcceptor}; use stream::Stream; use self::upgrade::{WsUpgrade, IntoWs, Buffer}; pub use self::upgrade::{Request, HyperIntoWsError}; @@ -39,15 +39,15 @@ pub type AcceptResult = Result, InvalidConnection>; /// Marker struct for a struct not being secure #[derive(Clone)] -pub struct NoSslAcceptor; +pub struct NoTlsAcceptor; /// Trait that is implemented over NoSslAcceptor and SslAcceptor that /// serves as a generic bound to make a struct with. /// Used in the Server to specify impls based on wether the server /// is running over SSL or not. -pub trait OptionalSslAcceptor: Clone {} -impl OptionalSslAcceptor for NoSslAcceptor {} +pub trait OptionalTlsAcceptor {} +impl OptionalTlsAcceptor for NoTlsAcceptor {} #[cfg(feature="ssl")] -impl OptionalSslAcceptor for SslAcceptor {} +impl OptionalTlsAcceptor for TlsAcceptor {} /// Represents a WebSocket server which can work with either normal /// (non-secure) connections, or secure WebSocket connections. @@ -134,29 +134,20 @@ impl OptionalSslAcceptor for SslAcceptor {} /// then calling `.into_ws()` on them. /// check out the docs over at `websocket::server::upgrade` for more. pub struct Server - where S: OptionalSslAcceptor + where S: OptionalTlsAcceptor { listener: TcpListener, ssl_acceptor: S, } impl Server - where S: OptionalSslAcceptor + where S: OptionalTlsAcceptor { /// Get the socket address of this server pub fn local_addr(&self) -> io::Result { self.listener.local_addr() } - /// Create a new independently owned handle to the underlying socket. - pub fn try_clone(&self) -> io::Result> { - let inner = try!(self.listener.try_clone()); - Ok(Server { - listener: inner, - ssl_acceptor: self.ssl_acceptor.clone(), - }) - } - /// Changes whether the Server is in nonblocking mode. /// /// If it is in nonblocking mode, accept() will return an error instead of blocking when there @@ -200,9 +191,9 @@ impl Server } #[cfg(feature="ssl")] -impl Server { +impl Server { /// Bind this Server to this socket, utilising the given SslContext - pub fn bind_secure(addr: A, acceptor: SslAcceptor) -> io::Result + pub fn bind_secure(addr: A, acceptor: TlsAcceptor) -> io::Result where A: ToSocketAddrs { Ok(Server { @@ -212,7 +203,7 @@ impl Server { } /// Wait for and accept an incoming WebSocket connection, returning a WebSocketRequest - pub fn accept(&mut self) -> AcceptResult> { + pub fn accept(&mut self) -> AcceptResult> { let stream = match self.listener.accept() { Ok(s) => s.0, Err(e) => { @@ -252,20 +243,20 @@ impl Server { } #[cfg(feature="ssl")] -impl Iterator for Server { - type Item = AcceptResult>; +impl Iterator for Server { + type Item = AcceptResult>; fn next(&mut self) -> Option<::Item> { Some(self.accept()) } } -impl Server { +impl Server { /// Bind this Server to this socket pub fn bind(addr: A) -> io::Result { Ok(Server { listener: try!(TcpListener::bind(&addr)), - ssl_acceptor: NoSslAcceptor, + ssl_acceptor: NoTlsAcceptor, }) } @@ -295,9 +286,18 @@ impl Server { } } } + + /// Create a new independently owned handle to the underlying socket. + pub fn try_clone(&self) -> io::Result { + let inner = try!(self.listener.try_clone()); + Ok(Server { + listener: inner, + ssl_acceptor: self.ssl_acceptor.clone(), + }) + } } -impl Iterator for Server { +impl Iterator for Server { type Item = AcceptResult; fn next(&mut self) -> Option<::Item> { diff --git a/src/stream.rs b/src/stream.rs index 3dee0bb080..55d922b5f1 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -6,7 +6,7 @@ use std::io::{self, Read, Write}; pub use std::net::TcpStream; pub use std::net::Shutdown; #[cfg(feature="ssl")] -pub use openssl::ssl::{SslStream, SslContext}; +pub use native_tls::TlsStream; #[cfg(feature="async")] pub use tokio_io::{AsyncWrite, AsyncRead}; #[cfg(feature="async")] @@ -81,7 +81,7 @@ impl AsTcpStream for TcpStream { } #[cfg(feature="ssl")] -impl AsTcpStream for SslStream { +impl AsTcpStream for TlsStream { fn as_tcp(&self) -> &TcpStream { self.get_ref() } From f89ab461123ff94d38820cfa435611ac7b7e875f Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Mon, 22 May 2017 01:49:54 -0400 Subject: [PATCH 27/52] Added async-ssl feature. --- Cargo.toml | 4 +- src/client/builder.rs | 115 ++++++++++++++++++++++++++++++++---------- src/client/mod.rs | 2 + src/lib.rs | 4 +- src/result.rs | 22 ++++---- 5 files changed, 106 insertions(+), 41 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 01ca3ea036..7a6b404def 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ sha1 = "^0.2" futures = { version = "^0.1", optional = true } tokio-core = { version = "0.1.7", optional = true } tokio-io = { version = "0.1.1", optional = true } +tokio-tls = { version = "^0.1", optional = true } bytes = { version = "^0.4", optional = true } native-tls = { version = "^0.1.2", optional = true } base64 = "^0.5" @@ -39,7 +40,8 @@ git = "https://github.com/illegalprime/tokio-io.git" rev = "18be60c56b0e932265a9e0301d14d5084577ca33" [features] -default = ["ssl", "async"] +default = ["ssl", "async", "async-ssl"] ssl = ["native-tls"] nightly = ["hyper/nightly"] async = ["tokio-core", "tokio-io", "bytes", "futures"] +async-ssl = ["native-tls", "tokio-tls", "async"] diff --git a/src/client/builder.rs b/src/client/builder.rs index 09f4f2a230..4246e87476 100644 --- a/src/client/builder.rs +++ b/src/client/builder.rs @@ -16,7 +16,7 @@ use hyper::http::h1::parse_response; use hyper::header::{Headers, Header, HeaderFormat, Host, Connection, ConnectionOption, Upgrade, Protocol, ProtocolName}; use unicase::UniCase; -#[cfg(feature="ssl")] +#[cfg(any(feature="ssl", feature="async-ssl"))] use native_tls::{TlsStream, TlsConnector}; use header::extensions::Extension; use header::{WebSocketAccept, WebSocketKey, WebSocketVersion, WebSocketProtocol, @@ -31,13 +31,16 @@ use super::Client; #[cfg(feature="async")] mod async_imports { pub use super::super::async; - pub use tokio_core::net::TcpStream as AsyncTcpStream; + pub use tokio_core::net::TcpStreamNew; pub use tokio_core::reactor::Handle; pub use futures::{Future, Sink}; pub use futures::future; pub use futures::Stream as FutureStream; pub use codec::ws::{MessageCodec, Context}; + #[cfg(feature="async-ssl")] + pub use tokio_tls::TlsConnectorExt; } +#[cfg(feature="async")] use self::async_imports::*; @@ -472,27 +475,54 @@ impl<'u> ClientBuilder<'u> { Ok(Client::unchecked(reader, response.headers, true, false)) } + #[cfg(feature="async-ssl")] + pub fn async_connect_secure( + self, + ssl_config: Option, + handle: &Handle, + ) -> async::ClientNew> { + // connect to the tcp stream + let tcp_stream = match self.async_tcpstream(handle) { + Ok(t) => t, + Err(e) => return future::err(e).boxed(), + }; + + // configure the tls connection + let (host, connector) = { + match self.extract_host_ssl_conn(ssl_config) { + Ok((h, conn)) => (h.to_string(), conn), + Err(e) => return future::err(e).boxed(), + } + }; + + let builder = ClientBuilder { + url: Cow::Owned(self.url.into_owned()), + version: self.version, + headers: self.headers, + version_set: self.version_set, + key_set: self.key_set, + }; + + // put it all together + let future = + tcp_stream.map_err(|e| e.into()) + .and_then(move |s| { + connector.connect_async(&host, s).map_err(|e| e.into()) + }) + .and_then(move |stream| { + builder.async_connect_on(stream) + }); + Box::new(future) + } + // TODO: add timeout option for connecting // TODO: add conveniences like .response_to_pings, .send_close, etc. #[cfg(feature="async")] - pub fn async_connect_insecure(self, handle: &Handle) -> async::ClientNew { - // get the address to connect to, return an error future if ther's a problem - let address = - match self.extract_host_port(Some(false)).and_then(|p| Ok(p.to_socket_addrs()?)) { - Ok(mut s) => { - match s.next() { - Some(a) => a, - None => { - let err = WebSocketError::WebSocketUrlError(WSUrlErrorKind::NoHostName); - return future::err(err).boxed(); - } - } - } - Err(e) => return future::err(e).boxed(), - }; - - // connect a tcp stream - let tcp_stream = async::TcpStream::connect(&address, handle); + pub fn async_connect_insecure(self, handle: &Handle) -> async::ClientNew { + let tcp_stream = match self.async_tcpstream(handle) { + Ok(t) => t, + Err(e) => return future::err(e).boxed(), + }; let builder = ClientBuilder { url: Cow::Owned(self.url.into_owned()), @@ -502,10 +532,11 @@ impl<'u> ClientBuilder<'u> { key_set: self.key_set, }; - Box::new(tcp_stream.map_err(|e| e.into()) - .and_then(move |stream| { - builder.async_connect_on(stream) - })) + let future = tcp_stream.map_err(|e| e.into()) + .and_then(move |stream| { + builder.async_connect_on(stream) + }); + Box::new(future) } #[cfg(feature="async")] @@ -553,6 +584,26 @@ impl<'u> ClientBuilder<'u> { Box::new(future) } + #[cfg(feature="async")] + fn async_tcpstream(&self, handle: &Handle) -> WebSocketResult { + // get the address to connect to, return an error future if ther's a problem + let address = + match self.extract_host_port(Some(false)).and_then(|p| Ok(p.to_socket_addrs()?)) { + Ok(mut s) => { + match s.next() { + Some(a) => a, + None => { + return Err(WebSocketError::WebSocketUrlError(WSUrlErrorKind::NoHostName)); + } + } + } + Err(e) => return Err(e.into()), + }; + + // connect a tcp stream + Ok(async::TcpStream::connect(&address, handle)) + } + fn build_request(&mut self) -> String { // enter host if available (unix sockets don't have hosts) if let Some(host) = self.url.host_str() { @@ -644,12 +695,11 @@ impl<'u> ClientBuilder<'u> { Ok(TcpStream::connect(self.extract_host_port(secure)?)?) } - #[cfg(feature="ssl")] - fn wrap_ssl( + #[cfg(any(feature="ssl", feature="async-ssl"))] + fn extract_host_ssl_conn( &self, - tcp_stream: TcpStream, connector: Option, - ) -> WebSocketResult> { + ) -> WebSocketResult<(&str, TlsConnector)> { let host = match self.url.host_str() { Some(h) => h, None => return Err(WebSocketError::WebSocketUrlError(WSUrlErrorKind::NoHostName)), @@ -658,7 +708,16 @@ impl<'u> ClientBuilder<'u> { Some(c) => c, None => TlsConnector::builder()?.build()?, }; + Ok((host, connector)) + } + #[cfg(feature="ssl")] + fn wrap_ssl( + &self, + tcp_stream: TcpStream, + connector: Option, + ) -> WebSocketResult> { + let (host, connector) = self.extract_host_ssl_conn(connector)?; let ssl_stream = try!(connector.connect(host, tcp_stream)); Ok(ssl_stream) } diff --git a/src/client/mod.rs b/src/client/mod.rs index 8841d688c8..205f662923 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -36,6 +36,8 @@ pub mod async { pub use tokio_core::net::TcpStream; pub use futures::Future; use codec::ws::MessageCodec; + #[cfg(feature="async-ssl")] + pub use tokio_tls::TlsStream; pub type Client = Framed>; diff --git a/src/lib.rs b/src/lib.rs index bb0992ec20..8cc25dfad6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,7 +43,7 @@ extern crate rand; extern crate byteorder; extern crate sha1; extern crate base64; -#[cfg(feature="ssl")] +#[cfg(any(feature="ssl", feature="async-ssl"))] extern crate native_tls; #[cfg(feature="async")] extern crate tokio_core; @@ -53,6 +53,8 @@ extern crate tokio_io; extern crate bytes; #[cfg(feature="async")] extern crate futures; +#[cfg(feature="async-ssl")] +extern crate tokio_tls; #[macro_use] extern crate bitflags; diff --git a/src/result.rs b/src/result.rs index 47f4d4bbd3..5003f6a58c 100644 --- a/src/result.rs +++ b/src/result.rs @@ -8,9 +8,9 @@ use std::fmt; use hyper::Error as HttpError; use url::ParseError; -#[cfg(feature="ssl")] +#[cfg(any(feature="ssl", feature="async-ssl"))] use native_tls::Error as TlsError; -#[cfg(feature="ssl")] +#[cfg(any(feature="ssl", feature="async-ssl"))] use native_tls::HandshakeError as TlsHandshakeError; /// The type used for WebSocket results @@ -38,13 +38,13 @@ pub enum WebSocketError { /// A WebSocket URL error WebSocketUrlError(WSUrlErrorKind), /// An SSL error - #[cfg(feature="ssl")] + #[cfg(any(feature="ssl", feature="async-ssl"))] TlsError(TlsError), /// an ssl handshake failure - #[cfg(feature="ssl")] + #[cfg(any(feature="ssl", feature="async-ssl"))] TlsHandshakeFailure, /// an ssl handshake interruption - #[cfg(feature="ssl")] + #[cfg(any(feature="ssl", feature="async-ssl"))] TlsHandshakeInterruption, /// A UTF-8 error Utf8Error(Utf8Error), @@ -69,11 +69,11 @@ impl Error for WebSocketError { WebSocketError::IoError(_) => "I/O failure", WebSocketError::HttpError(_) => "HTTP failure", WebSocketError::UrlError(_) => "URL failure", - #[cfg(feature="ssl")] + #[cfg(any(feature="ssl", feature="async-ssl"))] WebSocketError::TlsError(_) => "TLS failure", - #[cfg(feature="ssl")] + #[cfg(any(feature="ssl", feature="async-ssl"))] WebSocketError::TlsHandshakeFailure => "TLS Handshake failure", - #[cfg(feature="ssl")] + #[cfg(any(feature="ssl", feature="async-ssl"))] WebSocketError::TlsHandshakeInterruption => "TLS Handshake interrupted", WebSocketError::Utf8Error(_) => "UTF-8 failure", WebSocketError::WebSocketUrlError(_) => "WebSocket URL failure", @@ -85,7 +85,7 @@ impl Error for WebSocketError { WebSocketError::IoError(ref error) => Some(error), WebSocketError::HttpError(ref error) => Some(error), WebSocketError::UrlError(ref error) => Some(error), - #[cfg(feature="ssl")] + #[cfg(any(feature="ssl", feature="async-ssl"))] WebSocketError::TlsError(ref error) => Some(error), WebSocketError::Utf8Error(ref error) => Some(error), WebSocketError::WebSocketUrlError(ref error) => Some(error), @@ -115,14 +115,14 @@ impl From for WebSocketError { } } -#[cfg(feature="ssl")] +#[cfg(any(feature="ssl", feature="async-ssl"))] impl From for WebSocketError { fn from(err: TlsError) -> WebSocketError { WebSocketError::TlsError(err) } } -#[cfg(feature="ssl")] +#[cfg(any(feature="ssl", feature="async-ssl"))] impl From> for WebSocketError { fn from(err: TlsHandshakeError) -> WebSocketError { match err { From 1b58dde950b295c4c4893a3061d005d53cb59b0a Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Mon, 22 May 2017 02:16:20 -0400 Subject: [PATCH 28/52] Updated all examples with new API. --- Cargo.toml | 5 +--- examples/autobahn-client.rs | 49 +++++++++++++++++-------------------- examples/autobahn-server.rs | 26 +++++++++----------- examples/client.rs | 47 ++++++++++++++++++----------------- examples/hyper.rs | 15 ++++++------ 5 files changed, 67 insertions(+), 75 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7a6b404def..899cc9cfdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,16 +24,13 @@ bitflags = "^0.8" rand = "^0.3" byteorder = "^1.0" sha1 = "^0.2" +base64 = "^0.5" futures = { version = "^0.1", optional = true } tokio-core = { version = "0.1.7", optional = true } tokio-io = { version = "0.1.1", optional = true } tokio-tls = { version = "^0.1", optional = true } bytes = { version = "^0.4", optional = true } native-tls = { version = "^0.1.2", optional = true } -base64 = "^0.5" - -[dev-dependencies] -serde_json = "^1.0" [replace."tokio-io:0.1.1"] git = "https://github.com/illegalprime/tokio-io.git" diff --git a/examples/autobahn-client.rs b/examples/autobahn-client.rs index e56c49ae07..97fe567a8c 100644 --- a/examples/autobahn-client.rs +++ b/examples/autobahn-client.rs @@ -1,10 +1,8 @@ extern crate websocket; -extern crate serde_json; -use std::str::from_utf8; use websocket::ClientBuilder; +use websocket::OwnedMessage; use websocket::Message; -use websocket::message::Type; fn main() { let addr = "ws://127.0.0.1:9001".to_string(); @@ -33,7 +31,7 @@ fn main() { println!("Executing test case: {}/{}", case_id, case_count); for message in receiver.incoming_messages() { - let message: Message = match message { + let message = match message { Ok(message) => message, Err(e) => { println!("Error: {:?}", e); @@ -42,20 +40,19 @@ fn main() { } }; - match message.opcode { - Type::Text => { - let response = Message::text(from_utf8(&*message.payload).unwrap()); - sender.send_message(&response).unwrap(); + match message { + OwnedMessage::Text(txt) => { + sender.send_message(&OwnedMessage::Text(txt)).unwrap(); } - Type::Binary => { - sender.send_message(&Message::binary(message.payload)).unwrap(); + OwnedMessage::Binary(bin) => { + sender.send_message(&OwnedMessage::Binary(bin)).unwrap(); } - Type::Close => { - let _ = sender.send_message(&Message::close()); + OwnedMessage::Close(_) => { + let _ = sender.send_message(&OwnedMessage::Close(None)); break; } - Type::Ping => { - sender.send_message(&Message::pong(message.payload)).unwrap(); + OwnedMessage::Ping(data) => { + sender.send_message(&OwnedMessage::Pong(data)).unwrap(); } _ => (), } @@ -81,7 +78,7 @@ fn get_case_count(addr: String) -> usize { let mut count = 0; for message in receiver.incoming_messages() { - let message: Message = match message { + let message = match message { Ok(message) => message, Err(e) => { println!("Error: {:?}", e); @@ -90,17 +87,17 @@ fn get_case_count(addr: String) -> usize { break; } }; - match message.opcode { - Type::Text => { - count = serde_json::from_str(from_utf8(&*message.payload).unwrap()).unwrap(); + match message { + OwnedMessage::Text(txt) => { + count = txt.parse().unwrap(); println!("Will run {} cases...", count); } - Type::Close => { + OwnedMessage::Close(_) => { let _ = sender.send_message(&Message::close()); break; } - Type::Ping => { - sender.send_message(&Message::pong(message.payload)).unwrap(); + OwnedMessage::Ping(data) => { + sender.send_message(&OwnedMessage::Pong(data)).unwrap(); } _ => (), } @@ -125,7 +122,7 @@ fn update_reports(addr: String, agent: &str) { println!("Updating reports..."); for message in receiver.incoming_messages() { - let message: Message = match message { + let message = match message { Ok(message) => message, Err(e) => { println!("Error: {:?}", e); @@ -133,15 +130,15 @@ fn update_reports(addr: String, agent: &str) { return; } }; - match message.opcode { - Type::Close => { + match message { + OwnedMessage::Close(_) => { let _ = sender.send_message(&Message::close()); println!("Reports updated."); println!("Test suite finished!"); return; } - Type::Ping => { - sender.send_message(&Message::pong(message.payload)).unwrap(); + OwnedMessage::Ping(data) => { + sender.send_message(&OwnedMessage::Pong(data)).unwrap(); } _ => (), } diff --git a/examples/autobahn-server.rs b/examples/autobahn-server.rs index 3d27f1e647..f7bdf544f7 100644 --- a/examples/autobahn-server.rs +++ b/examples/autobahn-server.rs @@ -1,9 +1,7 @@ extern crate websocket; use std::thread; -use std::str::from_utf8; -use websocket::{Server, Message}; -use websocket::message::Type; +use websocket::{Server, Message, OwnedMessage}; fn main() { let server = Server::bind("127.0.0.1:9002").unwrap(); @@ -16,7 +14,7 @@ fn main() { let (mut receiver, mut sender) = client.split().unwrap(); for message in receiver.incoming_messages() { - let message: Message = match message { + let message = match message { Ok(message) => message, Err(e) => { println!("{:?}", e); @@ -25,21 +23,19 @@ fn main() { } }; - match message.opcode { - Type::Text => { - let response = Message::text(from_utf8(&*message.payload).unwrap()); - sender.send_message(&response).unwrap() + match message { + OwnedMessage::Text(txt) => { + sender.send_message(&OwnedMessage::Text(txt)).unwrap() } - Type::Binary => { - sender.send_message(&Message::binary(message.payload)).unwrap() + OwnedMessage::Binary(bin) => { + sender.send_message(&OwnedMessage::Binary(bin)).unwrap() } - Type::Close => { - let _ = sender.send_message(&Message::close()); + OwnedMessage::Close(_) => { + sender.send_message(&OwnedMessage::Close(None)).ok(); return; } - Type::Ping => { - let message = Message::pong(message.payload); - sender.send_message(&message).unwrap(); + OwnedMessage::Ping(data) => { + sender.send_message(&OwnedMessage::Pong(data)).unwrap(); } _ => (), } diff --git a/examples/client.rs b/examples/client.rs index bc4c1cb40a..19b185273b 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -1,15 +1,15 @@ extern crate websocket; +use std::thread; +use std::sync::mpsc::channel; +use std::io::stdin; + +use websocket::{Message, OwnedMessage}; +use websocket::client::ClientBuilder; + const CONNECTION: &'static str = "ws://127.0.0.1:2794"; fn main() { - use std::thread; - use std::sync::mpsc::channel; - use std::io::stdin; - - use websocket::Message; - use websocket::message::Type; - use websocket::client::ClientBuilder; println!("Connecting to {}", CONNECTION); @@ -30,17 +30,20 @@ fn main() { let send_loop = thread::spawn(move || { loop { // Send loop - let message: Message = match rx.recv() { + let message = match rx.recv() { Ok(m) => m, Err(e) => { println!("Send Loop: {:?}", e); return; } }; - if Type::Close == message.opcode { - let _ = sender.send_message(&message); - // If it's a close message, just send it and then return. - return; + match message { + OwnedMessage::Close(_) => { + let _ = sender.send_message(&message); + // If it's a close message, just send it and then return. + return; + } + _ => (), } // Send the message match sender.send_message(&message) { @@ -57,22 +60,22 @@ fn main() { let receive_loop = thread::spawn(move || { // Receive loop for message in receiver.incoming_messages() { - let message: Message = match message { + let message = match message { Ok(m) => m, Err(e) => { println!("Receive Loop: {:?}", e); - let _ = tx_1.send(Message::close()); + let _ = tx_1.send(OwnedMessage::Close(None)); return; } }; - match message.opcode { - Type::Close => { + match message { + OwnedMessage::Close(_) => { // Got a close message, so send a close message and return - let _ = tx_1.send(Message::close()); + let _ = tx_1.send(OwnedMessage::Close(None)); return; } - Type::Ping => { - match tx_1.send(Message::pong(message.payload)) { + OwnedMessage::Ping(data) => { + match tx_1.send(OwnedMessage::Pong(data)) { // Send a pong in response Ok(()) => (), Err(e) => { @@ -97,13 +100,13 @@ fn main() { let message = match trimmed { "/close" => { // Close the connection - let _ = tx.send(Message::close()); + let _ = tx.send(OwnedMessage::Close(None)); break; } // Send a ping - "/ping" => Message::ping(b"PING".to_vec()), + "/ping" => OwnedMessage::Ping(b"PING".to_vec()), // Otherwise, just send text - _ => Message::text(trimmed.to_string()), + _ => OwnedMessage::Text(trimmed.to_string()), }; match tx.send(message) { diff --git a/examples/hyper.rs b/examples/hyper.rs index de1880a024..6ccb48521c 100644 --- a/examples/hyper.rs +++ b/examples/hyper.rs @@ -3,8 +3,7 @@ extern crate hyper; use std::thread; use std::io::Write; -use websocket::{Server, Message}; -use websocket::message::Type; +use websocket::{Server, Message, OwnedMessage}; use hyper::Server as HttpServer; use hyper::net::Fresh; use hyper::server::request::Request; @@ -44,23 +43,23 @@ fn main() { println!("Connection from {}", ip); - let message = Message::text("Hello".to_string()); + let message = Message::text("Hello"); client.send_message(&message).unwrap(); let (mut receiver, mut sender) = client.split().unwrap(); for message in receiver.incoming_messages() { - let message: Message = message.unwrap(); + let message = message.unwrap(); - match message.opcode { - Type::Close => { + match message { + OwnedMessage::Close(_) => { let message = Message::close(); sender.send_message(&message).unwrap(); println!("Client {} disconnected", ip); return; } - Type::Ping => { - let message = Message::pong(message.payload); + OwnedMessage::Ping(data) => { + let message = Message::pong(data); sender.send_message(&message).unwrap(); } _ => sender.send_message(&message).unwrap(), From 5e79dea736b3d28f3e472d69a8fb232e2e6fcf8b Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Mon, 22 May 2017 11:31:55 -0400 Subject: [PATCH 29/52] Created client/server http codecs, added async intows trait. --- examples/client.rs | 2 +- src/client/builder.rs | 4 +- src/codec/http.rs | 99 +++++++++++++++++++++++++++++++++++-- src/result.rs | 10 ++++ src/server/upgrade/async.rs | 65 ++++++++++++++++++++++++ src/server/upgrade/mod.rs | 29 ++++++++--- 6 files changed, 193 insertions(+), 16 deletions(-) create mode 100644 src/server/upgrade/async.rs diff --git a/examples/client.rs b/examples/client.rs index 19b185273b..c1c3d2028a 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -74,7 +74,7 @@ fn main() { let _ = tx_1.send(OwnedMessage::Close(None)); return; } - OwnedMessage::Ping(data) => { + OwnedMessage::Ping(data) => { match tx_1.send(OwnedMessage::Pong(data)) { // Send a pong in response Ok(()) => (), diff --git a/src/client/builder.rs b/src/client/builder.rs index 4246e87476..371ecf8a1a 100644 --- a/src/client/builder.rs +++ b/src/client/builder.rs @@ -551,7 +551,7 @@ impl<'u> ClientBuilder<'u> { key_set: self.key_set, }; let resource = builder.build_request(); - let framed = stream.framed(::codec::http::HttpCodec); + let framed = stream.framed(::codec::http::HttpClientCodec); let request = Incoming { version: builder.version, headers: builder.headers.clone(), @@ -563,7 +563,7 @@ impl<'u> ClientBuilder<'u> { .send(request).map_err(::std::convert::Into::into) // wait for a response - .and_then(|stream| stream.into_future().map_err(|e| e.0)) + .and_then(|stream| stream.into_future().map_err(|e| e.0.into())) // validate .and_then(move |(message, stream)| { diff --git a/src/codec/http.rs b/src/codec/http.rs index 8f2cfa8db1..6f65a15f5d 100644 --- a/src/codec/http.rs +++ b/src/codec/http.rs @@ -1,20 +1,24 @@ +use std::io::{self, Write}; +use std::error::Error; +use std::fmt::{self, Formatter, Display}; use hyper; use hyper::http::h1::Incoming; use hyper::http::h1::parse_response; +use hyper::http::h1::parse_request; use hyper::http::RawStatus; +use hyper::status::StatusCode; use hyper::method::Method; use hyper::uri::RequestUri; use hyper::buffer::BufReader; -use std::io::{self, Write}; use tokio_io::codec::{Decoder, Encoder}; use bytes::BytesMut; use bytes::BufMut; use result::WebSocketError; #[derive(Copy, Clone, Debug)] -pub struct HttpCodec; +pub struct HttpClientCodec; -impl Encoder for HttpCodec { +impl Encoder for HttpClientCodec { type Item = Incoming<(Method, RequestUri)>; type Error = io::Error; @@ -33,9 +37,9 @@ impl Encoder for HttpCodec { } } -impl Decoder for HttpCodec { +impl Decoder for HttpClientCodec { type Item = Incoming; - type Error = WebSocketError; + type Error = HttpCodecError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { // check if we get a request from hyper @@ -58,3 +62,88 @@ impl Decoder for HttpCodec { Ok(Some(response)) } } + +#[derive(Copy, Clone, Debug)] +pub struct HttpServerCodec; + +impl Encoder for HttpServerCodec { + type Item = Incoming; + type Error = io::Error; + + fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { + // TODO: optomize this! + let status = StatusCode::from_u16(item.subject.0); + let response = format!("{} {}\r\n{}\r\n", item.version, status, item.headers); + let byte_len = response.as_bytes().len(); + if byte_len > dst.remaining_mut() { + dst.reserve(byte_len); + } + dst.writer().write(response.as_bytes()).map(|_| ()) + } +} + +impl Decoder for HttpServerCodec { + type Item = Incoming<(Method, RequestUri)>; + type Error = HttpCodecError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + // check if we get a request from hyper + // TODO: this is ineffecient, but hyper does not give us a better way to parse + let (response, bytes_read) = { + let mut reader = BufReader::new(&*src as &[u8]); + let res = match parse_request(&mut reader) { + Err(hyper::Error::Io(ref e)) if e.kind() == io::ErrorKind::UnexpectedEof => { + return Ok(None) + } + Err(hyper::Error::TooLarge) => return Ok(None), + Err(e) => return Err(e.into()), + Ok(r) => r, + }; + let (_, _, pos, _) = reader.into_parts(); + (res, pos) + }; + + src.split_to(bytes_read); + Ok(Some(response)) + } +} + +#[derive(Debug)] +pub enum HttpCodecError { + Io(io::Error), + Http(hyper::Error), +} + +impl Display for HttpCodecError { + fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { + fmt.write_str(self.description()) + } +} + +impl Error for HttpCodecError { + fn description(&self) -> &str { + match *self { + HttpCodecError::Io(ref e) => e.description(), + HttpCodecError::Http(ref e) => e.description(), + } + } + + fn cause(&self) -> Option<&Error> { + match *self { + HttpCodecError::Io(ref error) => Some(error), + HttpCodecError::Http(ref error) => Some(error), + } + } +} + +impl From for HttpCodecError { + fn from(err: io::Error) -> HttpCodecError { + HttpCodecError::Io(err) + } +} + +impl From for HttpCodecError { + fn from(err: hyper::Error) -> HttpCodecError { + HttpCodecError::Http(err) + } +} diff --git a/src/result.rs b/src/result.rs index 5003f6a58c..b5f7e4b9f3 100644 --- a/src/result.rs +++ b/src/result.rs @@ -138,6 +138,16 @@ impl From for WebSocketError { } } +#[cfg(feature="async")] +impl From<::codec::http::HttpCodecError> for WebSocketError { + fn from(src: ::codec::http::HttpCodecError) -> Self { + match src { + ::codec::http::HttpCodecError::Io(e) => WebSocketError::IoError(e), + ::codec::http::HttpCodecError::Http(e) => WebSocketError::HttpError(e), + } + } +} + impl From for WebSocketError { fn from(err: WSUrlErrorKind) -> WebSocketError { WebSocketError::WebSocketUrlError(err) diff --git a/src/server/upgrade/async.rs b/src/server/upgrade/async.rs new file mode 100644 index 0000000000..c1ba4a00e1 --- /dev/null +++ b/src/server/upgrade/async.rs @@ -0,0 +1,65 @@ +use super::{Buffer, HyperIntoWsError, WsUpgrade, Request, validate}; +use std::io::{self, ErrorKind}; +use hyper::header::Headers; +use stream::AsyncStream; +use futures::{Stream, Future}; +use codec::http::HttpServerCodec; +use bytes::BytesMut; + +pub trait AsyncIntoWs { + /// The type of stream this upgrade process is working with (TcpStream, etc.) + type Stream: AsyncStream; + /// An error value in case the stream is not asking for a websocket connection + /// or something went wrong. It is common to also include the stream here. + type Error; + /// Attempt to read and parse the start of a Websocket handshake, later + /// with the returned `WsUpgrade` struct, call `accept to start a + /// websocket client, and `reject` to send a handshake rejection response. + /// + /// Note: this is the asynchronous version, meaning it will not block when + /// trying to read a request. + fn into_ws(self) -> Box, Error = Self::Error>>; +} + +impl AsyncIntoWs for S + where S: AsyncStream + 'static +{ + type Stream = S; + type Error = (S, Option, Option, HyperIntoWsError); + + fn into_ws(self) -> Box, Error = Self::Error>> { + let future = self.framed(HttpServerCodec) + .into_future() + .map_err(|(e, s)| { + let (stream, buffer) = s.into_parts(); + (stream, None, Some(buffer), e.into()) + }) + .and_then(|(m, s)| { + let (stream, buffer) = s.into_parts(); + if let Some(msg) = m { + match validate(&msg.subject.0, &msg.version, &msg.headers) { + Ok(()) => Ok((msg, stream, buffer)), + Err(e) => Err((stream, None, Some(buffer), e)), + } + } else { + let err = HyperIntoWsError::Io(io::Error::new( + ErrorKind::ConnectionReset, + "Connection dropped before handshake could be read")); + Err((stream, None, Some(buffer), err)) + } + }) + .map(|(m, stream, buffer)| { + WsUpgrade { + headers: Headers::new(), + stream: stream, + request: m, + buffer: Some(Buffer { + buf: unimplemented!(), + pos: 0, + cap: buffer.capacity(), + }), + } + }); + Box::new(future) + } +} diff --git a/src/server/upgrade/mod.rs b/src/server/upgrade/mod.rs index d52f3957c9..301fca1d1a 100644 --- a/src/server/upgrade/mod.rs +++ b/src/server/upgrade/mod.rs @@ -24,6 +24,9 @@ use hyper::header::{Headers, Upgrade, Protocol, ProtocolName, Connection, Connec pub mod from_hyper; +#[cfg(feature="async")] +pub mod async; + /// This crate uses buffered readers to read in the handshake quickly, in order to /// interface with other use cases that don't use buffered readers the buffered readers /// is deconstructed when it is returned to the user and given as the underlying @@ -43,6 +46,14 @@ pub struct Buffer { pub cap: usize, } +/// A typical request from hyper +pub type Request = Incoming<(Method, RequestUri)>; + +/// If you have your requests separate from your stream you can use this struct +/// to upgrade the connection based on the request given +/// (the request should be a handshake). +pub struct RequestStreamPair(pub S, pub Request); + /// Intermediate representation of a half created websocket session. /// Should be used to examine the client's handshake /// accept the protocols requested, route the path, etc. @@ -246,14 +257,6 @@ pub trait IntoWs { fn into_ws(self) -> Result, Self::Error>; } - -/// A typical request from hyper -pub type Request = Incoming<(Method, RequestUri)>; -/// If you have your requests separate from your stream you can use this struct -/// to upgrade the connection based on the request given -/// (the request should be a handshake). -pub struct RequestStreamPair(pub S, pub Request); - impl IntoWs for S where S: Stream { @@ -381,6 +384,16 @@ impl From<::hyper::error::Error> for HyperIntoWsError { } } +#[cfg(feature="async")] +impl From<::codec::http::HttpCodecError> for HyperIntoWsError { + fn from(src: ::codec::http::HttpCodecError) -> Self { + match src { + ::codec::http::HttpCodecError::Io(e) => HyperIntoWsError::Io(e), + ::codec::http::HttpCodecError::Http(e) => HyperIntoWsError::Parsing(e), + } + } +} + fn validate( method: &Method, version: &HttpVersion, From 3eec4354085f4912c6c4ec5bb3647177f263282e Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Mon, 22 May 2017 11:55:12 -0400 Subject: [PATCH 30/52] Added skeleton of async WsUpgrade. --- src/server/upgrade/async.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/server/upgrade/async.rs b/src/server/upgrade/async.rs index c1ba4a00e1..579ae8b666 100644 --- a/src/server/upgrade/async.rs +++ b/src/server/upgrade/async.rs @@ -5,6 +5,31 @@ use stream::AsyncStream; use futures::{Stream, Future}; use codec::http::HttpServerCodec; use bytes::BytesMut; +use client::async::ClientNew; + +impl WsUpgrade + where S: AsyncStream +{ + pub fn async_accept(self) -> Result, (S, io::Error)> { + unimplemented!(); + } + + pub fn async_accept_with( + mut self, + custom_headers: &Headers, + ) -> Result, (S, io::Error)> { + unimplemented!(); + } + + pub fn async_reject(self) -> Result { + unimplemented!(); + } + + pub fn async_reject_with(mut self, headers: &Headers) -> Result { + unimplemented!(); + } +} + pub trait AsyncIntoWs { /// The type of stream this upgrade process is working with (TcpStream, etc.) From 79f5856e6a2f169c99476714737c9f519f03baa2 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Mon, 22 May 2017 12:18:57 -0400 Subject: [PATCH 31/52] Removed uneeded explicit lifetimes. --- src/client/mod.rs | 4 ++-- src/codec/http.rs | 1 - src/message.rs | 2 +- src/receiver.rs | 2 +- src/sender.rs | 2 +- src/server/upgrade/async.rs | 4 ++-- src/ws/dataframe.rs | 2 +- src/ws/sender.rs | 2 +- 8 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/client/mod.rs b/src/client/mod.rs index 205f662923..46a88201c2 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -159,7 +159,7 @@ impl Client } /// Sends a single message to the remote endpoint. - pub fn send_message<'m, M>(&mut self, message: &'m M) -> WebSocketResult<()> + pub fn send_message(&mut self, message: &M) -> WebSocketResult<()> where M: ws::Message { self.sender.send_message(self.stream.get_mut(), message) @@ -188,7 +188,7 @@ impl Client /// /// let message: Message = client.recv_message().unwrap(); /// ``` - pub fn recv_message<'m, I>(&mut self) -> WebSocketResult + pub fn recv_message(&mut self) -> WebSocketResult where I: Iterator { self.receiver.recv_message(&mut self.stream) diff --git a/src/codec/http.rs b/src/codec/http.rs index 6f65a15f5d..8a0c26a428 100644 --- a/src/codec/http.rs +++ b/src/codec/http.rs @@ -13,7 +13,6 @@ use hyper::buffer::BufReader; use tokio_io::codec::{Decoder, Encoder}; use bytes::BytesMut; use bytes::BufMut; -use result::WebSocketError; #[derive(Copy, Clone, Debug)] pub struct HttpClientCodec; diff --git a/src/message.rs b/src/message.rs index 56746321ae..a79cf8ea87 100644 --- a/src/message.rs +++ b/src/message.rs @@ -321,7 +321,7 @@ impl ws::dataframe::DataFrame for OwnedMessage { } #[inline(always)] - fn reserved<'b>(&'b self) -> &'b [bool; 3] { + fn reserved(&self) -> &[bool; 3] { FALSE_RESERVED_BITS } diff --git a/src/receiver.rs b/src/receiver.rs index 03cc63376f..98df5c909a 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -39,7 +39,7 @@ impl Reader } /// Reads a single message from this receiver. - pub fn recv_message<'m, I>(&mut self) -> WebSocketResult + pub fn recv_message(&mut self) -> WebSocketResult where I: Iterator { self.receiver.recv_message(&mut self.stream) diff --git a/src/sender.rs b/src/sender.rs index bf84ef2174..72a5e9526d 100644 --- a/src/sender.rs +++ b/src/sender.rs @@ -32,7 +32,7 @@ impl Writer } /// Sends a single message to the remote endpoint. - pub fn send_message<'m, M>(&mut self, message: &'m M) -> WebSocketResult<()> + pub fn send_message(&mut self, message: &M) -> WebSocketResult<()> where M: ws::Message { self.sender.send_message(&mut self.stream, message) diff --git a/src/server/upgrade/async.rs b/src/server/upgrade/async.rs index 579ae8b666..625d95342c 100644 --- a/src/server/upgrade/async.rs +++ b/src/server/upgrade/async.rs @@ -15,7 +15,7 @@ impl WsUpgrade } pub fn async_accept_with( - mut self, + self, custom_headers: &Headers, ) -> Result, (S, io::Error)> { unimplemented!(); @@ -25,7 +25,7 @@ impl WsUpgrade unimplemented!(); } - pub fn async_reject_with(mut self, headers: &Headers) -> Result { + pub fn async_reject_with(self, headers: &Headers) -> Result { unimplemented!(); } } diff --git a/src/ws/dataframe.rs b/src/ws/dataframe.rs index 3dd6c2bce5..b42e1ad862 100644 --- a/src/ws/dataframe.rs +++ b/src/ws/dataframe.rs @@ -17,7 +17,7 @@ pub trait DataFrame { /// What type of data does this dataframe contain? fn opcode(&self) -> u8; /// Reserved bits of this dataframe - fn reserved<'a>(&'a self) -> &'a [bool; 3]; + fn reserved(&self) -> &[bool; 3]; /// How long (in bytes) is this dataframe's payload fn size(&self) -> usize; diff --git a/src/ws/sender.rs b/src/ws/sender.rs index 5ccbb04546..2e0656cefc 100644 --- a/src/ws/sender.rs +++ b/src/ws/sender.rs @@ -21,7 +21,7 @@ pub trait Sender { } /// Sends a single message using this sender. - fn send_message<'m, M, W>(&mut self, writer: &mut W, message: &'m M) -> WebSocketResult<()> + fn send_message(&mut self, writer: &mut W, message: &M) -> WebSocketResult<()> where M: Message, W: Write { From db9ce9101592f7e2dff6b17453f365e0da7d3a4b Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Mon, 22 May 2017 16:29:45 -0400 Subject: [PATCH 32/52] Updated to use new upstream tokio-io. --- Cargo.toml | 3 +-- src/client/builder.rs | 4 ++-- src/server/upgrade/async.rs | 13 +++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 899cc9cfdb..bac73238c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,8 +33,7 @@ bytes = { version = "^0.4", optional = true } native-tls = { version = "^0.1.2", optional = true } [replace."tokio-io:0.1.1"] -git = "https://github.com/illegalprime/tokio-io.git" -rev = "18be60c56b0e932265a9e0301d14d5084577ca33" +git = "https://github.com/tokio-rs/tokio-io.git" [features] default = ["ssl", "async", "async-ssl"] diff --git a/src/client/builder.rs b/src/client/builder.rs index 371ecf8a1a..c5a694237c 100644 --- a/src/client/builder.rs +++ b/src/client/builder.rs @@ -31,6 +31,7 @@ use super::Client; #[cfg(feature="async")] mod async_imports { pub use super::super::async; + pub use tokio_io::codec::Framed; pub use tokio_core::net::TcpStreamNew; pub use tokio_core::reactor::Handle; pub use futures::{Future, Sink}; @@ -576,8 +577,7 @@ impl<'u> ClientBuilder<'u> { // output the final client and metadata .map(|(message, stream)| { let codec = MessageCodec::default(Context::Client); - let (client, buffer) = stream.into_parts(); - let client = ::tokio_io::codec::framed_with_buf(client, codec, buffer); + let client = Framed::from_parts(stream.into_parts(), codec); (client, message.headers) }); diff --git a/src/server/upgrade/async.rs b/src/server/upgrade/async.rs index 625d95342c..a1003ce261 100644 --- a/src/server/upgrade/async.rs +++ b/src/server/upgrade/async.rs @@ -1,5 +1,6 @@ use super::{Buffer, HyperIntoWsError, WsUpgrade, Request, validate}; use std::io::{self, ErrorKind}; +use tokio_io::codec::FramedParts; use hyper::header::Headers; use stream::AsyncStream; use futures::{Stream, Future}; @@ -56,21 +57,21 @@ impl AsyncIntoWs for S let future = self.framed(HttpServerCodec) .into_future() .map_err(|(e, s)| { - let (stream, buffer) = s.into_parts(); - (stream, None, Some(buffer), e.into()) + let FramedParts { inner, readbuf, .. } = s.into_parts(); + (inner, None, Some(readbuf), e.into()) }) .and_then(|(m, s)| { - let (stream, buffer) = s.into_parts(); + let FramedParts { inner, readbuf, .. } = s.into_parts(); if let Some(msg) = m { match validate(&msg.subject.0, &msg.version, &msg.headers) { - Ok(()) => Ok((msg, stream, buffer)), - Err(e) => Err((stream, None, Some(buffer), e)), + Ok(()) => Ok((msg, inner, readbuf)), + Err(e) => Err((inner, None, Some(readbuf), e)), } } else { let err = HyperIntoWsError::Io(io::Error::new( ErrorKind::ConnectionReset, "Connection dropped before handshake could be read")); - Err((stream, None, Some(buffer), err)) + Err((inner, None, Some(readbuf), err)) } }) .map(|(m, stream, buffer)| { From 39981184ade57de0f24b221a1b55168457452478 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Tue, 23 May 2017 00:02:13 -0400 Subject: [PATCH 33/52] Implemented async IntoWs and WsUpgrade, only server is left. --- src/codec/http.rs | 5 +- src/codec/ws.rs | 3 + src/server/mod.rs | 4 +- src/server/upgrade/async.rs | 98 ++++++++++++++++++-------- src/server/upgrade/from_hyper.rs | 6 +- src/server/upgrade/mod.rs | 114 +++++++++++++++++++------------ 6 files changed, 151 insertions(+), 79 deletions(-) diff --git a/src/codec/http.rs b/src/codec/http.rs index 8a0c26a428..5386b86a68 100644 --- a/src/codec/http.rs +++ b/src/codec/http.rs @@ -66,13 +66,12 @@ impl Decoder for HttpClientCodec { pub struct HttpServerCodec; impl Encoder for HttpServerCodec { - type Item = Incoming; + type Item = Incoming; type Error = io::Error; fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { // TODO: optomize this! - let status = StatusCode::from_u16(item.subject.0); - let response = format!("{} {}\r\n{}\r\n", item.version, status, item.headers); + let response = format!("{} {}\r\n{}\r\n", item.version, item.subject, item.headers); let byte_len = response.as_bytes().len(); if byte_len > dst.remaining_mut() { dst.reserve(byte_len); diff --git a/src/codec/ws.rs b/src/codec/ws.rs index 2f8da04854..14f707c908 100644 --- a/src/codec/ws.rs +++ b/src/codec/ws.rs @@ -15,6 +15,9 @@ use ws::message::Message as MessageTrait; use ws::util::header::read_header; use result::WebSocketError; +// TODO: IMPORTANT: check if frame_size is correct, +// do not call .reserve with the entire size + /************** * Dataframes * **************/ diff --git a/src/server/mod.rs b/src/server/mod.rs index 444b309de3..014678e5e9 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -5,7 +5,7 @@ use std::convert::Into; #[cfg(feature="ssl")] use native_tls::{TlsStream, TlsAcceptor}; use stream::Stream; -use self::upgrade::{WsUpgrade, IntoWs, Buffer}; +use self::upgrade::{SyncWsUpgrade, IntoWs, Buffer}; pub use self::upgrade::{Request, HyperIntoWsError}; pub mod upgrade; @@ -35,7 +35,7 @@ pub struct InvalidConnection /// Either the stream was established and it sent a websocket handshake /// which represents the `Ok` variant, or there was an error (this is the /// `Err` variant). -pub type AcceptResult = Result, InvalidConnection>; +pub type AcceptResult = Result, InvalidConnection>; /// Marker struct for a struct not being secure #[derive(Clone)] diff --git a/src/server/upgrade/async.rs b/src/server/upgrade/async.rs index a1003ce261..86f0a38dd0 100644 --- a/src/server/upgrade/async.rs +++ b/src/server/upgrade/async.rs @@ -1,33 +1,81 @@ -use super::{Buffer, HyperIntoWsError, WsUpgrade, Request, validate}; +use super::{HyperIntoWsError, WsUpgrade, Request, validate}; use std::io::{self, ErrorKind}; -use tokio_io::codec::FramedParts; +use tokio_io::codec::{Framed, FramedParts}; use hyper::header::Headers; +use hyper::http::h1::Incoming; +use hyper::status::StatusCode; use stream::AsyncStream; -use futures::{Stream, Future}; +use futures::{Stream, Sink, Future}; +use futures::sink::Send; use codec::http::HttpServerCodec; +use codec::ws::{MessageCodec, Context}; use bytes::BytesMut; use client::async::ClientNew; -impl WsUpgrade - where S: AsyncStream +pub type AsyncWsUpgrade = WsUpgrade; + +impl AsyncWsUpgrade + where S: AsyncStream + 'static { - pub fn async_accept(self) -> Result, (S, io::Error)> { - unimplemented!(); + pub fn async_accept(self) -> ClientNew { + self.internal_async_accept(None) + } + + pub fn async_accept_with(self, custom_headers: &Headers) -> ClientNew { + self.internal_async_accept(Some(custom_headers)) + } + + fn internal_async_accept(mut self, custom_headers: Option<&Headers>) -> ClientNew { + let status = self.prepare_headers(custom_headers); + let WsUpgrade { headers, stream, request, buffer } = self; + + let duplex = Framed::from_parts(FramedParts { + inner: stream, + readbuf: buffer, + writebuf: BytesMut::with_capacity(0), + }, + HttpServerCodec); + + let future = duplex.send(Incoming { + version: request.version, + subject: status, + headers: headers.clone(), + }) + .map(move |s| { + let codec = MessageCodec::default(Context::Client); + let client = Framed::from_parts(s.into_parts(), codec); + (client, headers) + }) + .map_err(|e| e.into()); + Box::new(future) } - pub fn async_accept_with( - self, - custom_headers: &Headers, - ) -> Result, (S, io::Error)> { - unimplemented!(); + pub fn async_reject(self) -> Send> { + self.internal_async_reject(None) } - pub fn async_reject(self) -> Result { - unimplemented!(); + pub fn async_reject_with(self, headers: &Headers) -> Send> { + self.internal_async_reject(Some(headers)) } - pub fn async_reject_with(self, headers: &Headers) -> Result { - unimplemented!(); + fn internal_async_reject( + mut self, + headers: Option<&Headers>, + ) -> Send> { + if let Some(custom) = headers { + self.headers.extend(custom.iter()); + } + let duplex = Framed::from_parts(FramedParts { + inner: self.stream, + readbuf: self.buffer, + writebuf: BytesMut::with_capacity(0), + }, + HttpServerCodec); + duplex.send(Incoming { + version: self.request.version, + subject: StatusCode::BadRequest, + headers: self.headers, + }) } } @@ -44,34 +92,34 @@ pub trait AsyncIntoWs { /// /// Note: this is the asynchronous version, meaning it will not block when /// trying to read a request. - fn into_ws(self) -> Box, Error = Self::Error>>; + fn into_ws(self) -> Box, Error = Self::Error>>; } impl AsyncIntoWs for S where S: AsyncStream + 'static { type Stream = S; - type Error = (S, Option, Option, HyperIntoWsError); + type Error = (S, Option, BytesMut, HyperIntoWsError); - fn into_ws(self) -> Box, Error = Self::Error>> { + fn into_ws(self) -> Box, Error = Self::Error>> { let future = self.framed(HttpServerCodec) .into_future() .map_err(|(e, s)| { let FramedParts { inner, readbuf, .. } = s.into_parts(); - (inner, None, Some(readbuf), e.into()) + (inner, None, readbuf, e.into()) }) .and_then(|(m, s)| { let FramedParts { inner, readbuf, .. } = s.into_parts(); if let Some(msg) = m { match validate(&msg.subject.0, &msg.version, &msg.headers) { Ok(()) => Ok((msg, inner, readbuf)), - Err(e) => Err((inner, None, Some(readbuf), e)), + Err(e) => Err((inner, None, readbuf, e)), } } else { let err = HyperIntoWsError::Io(io::Error::new( ErrorKind::ConnectionReset, "Connection dropped before handshake could be read")); - Err((inner, None, Some(readbuf), err)) + Err((inner, None, readbuf, err)) } }) .map(|(m, stream, buffer)| { @@ -79,11 +127,7 @@ impl AsyncIntoWs for S headers: Headers::new(), stream: stream, request: m, - buffer: Some(Buffer { - buf: unimplemented!(), - pos: 0, - cap: buffer.capacity(), - }), + buffer: buffer, } }); Box::new(future) diff --git a/src/server/upgrade/from_hyper.rs b/src/server/upgrade/from_hyper.rs index da6ae1e134..342f296d69 100644 --- a/src/server/upgrade/from_hyper.rs +++ b/src/server/upgrade/from_hyper.rs @@ -36,7 +36,7 @@ //! ``` use hyper::net::NetworkStream; -use super::{IntoWs, WsUpgrade, Buffer}; +use super::{IntoWs, SyncWsUpgrade, Buffer}; pub use hyper::http::h1::Incoming; pub use hyper::method::Method; @@ -58,7 +58,7 @@ impl<'a, 'b> IntoWs for HyperRequest<'a, 'b> { type Stream = &'a mut &'b mut NetworkStream; type Error = (Request<'a, 'b>, HyperIntoWsError); - fn into_ws(self) -> Result, Self::Error> { + fn into_ws(self) -> Result, Self::Error> { if let Err(e) = validate(&self.0.method, &self.0.version, &self.0.headers) { return Err((self.0, e)); } @@ -70,7 +70,7 @@ impl<'a, 'b> IntoWs for HyperRequest<'a, 'b> { let (buf, pos, cap) = reader.take_buf(); let stream = reader.get_mut(); - Ok(WsUpgrade { + Ok(SyncWsUpgrade { headers: Headers::new(), stream: stream, buffer: Some(Buffer { diff --git a/src/server/upgrade/mod.rs b/src/server/upgrade/mod.rs index 301fca1d1a..cc024646ae 100644 --- a/src/server/upgrade/mod.rs +++ b/src/server/upgrade/mod.rs @@ -60,7 +60,7 @@ pub struct RequestStreamPair(pub S, pub Request); /// /// Users should then call `accept` or `reject` to complete the handshake /// and start a session. -pub struct WsUpgrade +pub struct WsUpgrade where S: Stream { /// The headers that will be used in the handshake response. @@ -70,10 +70,12 @@ pub struct WsUpgrade /// The handshake request, filled with useful metadata. pub request: Request, /// Some buffered data from the stream, if it exists. - pub buffer: Option, + pub buffer: B, } -impl WsUpgrade +pub type SyncWsUpgrade = WsUpgrade>; + +impl WsUpgrade where S: Stream { /// Select a protocol to use in the handshake response. @@ -109,47 +111,21 @@ impl WsUpgrade self } - /// Accept the handshake request and send a response, - /// if nothing goes wrong a client will be created. - pub fn accept(self) -> Result, (S, IoError)> { - self.accept_with(&Headers::new()) - } - - /// Accept the handshake request and send a response while - /// adding on a few headers. These headers are added before the required - /// headers are, so some might be overwritten. - pub fn accept_with(mut self, custom_headers: &Headers) -> Result, (S, IoError)> { - self.headers.extend(custom_headers.iter()); - self.headers - .set(WebSocketAccept::new(// NOTE: we know there is a key because this is a valid request - // i.e. to construct this you must go through the validate function - self.request.headers.get::().unwrap())); - self.headers - .set(Connection(vec![ - ConnectionOption::ConnectionHeader(UniCase("Upgrade".to_string())) - ])); - self.headers.set(Upgrade(vec![Protocol::new(ProtocolName::WebSocket, None)])); - - if let Err(e) = self.send(StatusCode::SwitchingProtocols) { - return Err((self.stream, e)); - } - - let stream = match self.buffer { - Some(Buffer { buf, pos, cap }) => BufReader::from_parts(self.stream, buf, pos, cap), - None => BufReader::new(self.stream), - }; - - Ok(Client::unchecked(stream, self.headers, false, true)) - } - /// Reject the client's request to make a websocket connection. pub fn reject(self) -> Result { - self.reject_with(&Headers::new()) + self.internal_reject(None) } + /// Reject the client's request to make a websocket connection /// and send extra headers. - pub fn reject_with(mut self, headers: &Headers) -> Result { - self.headers.extend(headers.iter()); + pub fn reject_with(self, headers: &Headers) -> Result { + self.internal_reject(Some(headers)) + } + + fn internal_reject(mut self, headers: Option<&Headers>) -> Result { + if let Some(custom) = headers { + self.headers.extend(custom.iter()); + } match self.send(StatusCode::BadRequest) { Ok(()) => Ok(self.stream), Err(e) => Err((self.stream, e)), @@ -161,7 +137,7 @@ impl WsUpgrade ::std::mem::drop(self); } - /// A list of protocols requested from the client. + /// Aelist of protocols requested from the client. pub fn protocols(&self) -> &[String] { self.request .headers @@ -199,9 +175,59 @@ impl WsUpgrade try!(write!(&mut self.stream, "{}\r\n", self.headers)); Ok(()) } + + #[doc(hidden)] + pub fn prepare_headers(&mut self, custom: Option<&Headers>) -> StatusCode { + if let Some(headers) = custom { + self.headers.extend(headers.iter()); + } + // NOTE: we know there is a key because this is a valid request + // i.e. to construct this you must go through the validate function + let key = self.request.headers.get::().unwrap(); + self.headers.set(WebSocketAccept::new(key)); + self.headers + .set(Connection(vec![ + ConnectionOption::ConnectionHeader(UniCase("Upgrade".to_string())) + ])); + self.headers.set(Upgrade(vec![Protocol::new(ProtocolName::WebSocket, None)])); + + StatusCode::SwitchingProtocols + } +} + +impl SyncWsUpgrade + where S: Stream +{ + /// Accept the handshake request and send a response, + /// if nothing goes wrong a client will be created. + pub fn accept(self) -> Result, (S, IoError)> { + self.internal_accept(None) + } + + /// Accept the handshake request and send a response while + /// adding on a few headers. These headers are added before the required + /// headers are, so some might be overwritten. + pub fn accept_with(self, custom_headers: &Headers) -> Result, (S, IoError)> { + self.internal_accept(Some(custom_headers)) + } + + fn internal_accept(mut self, headers: Option<&Headers>) -> Result, (S, IoError)> { + let status = self.prepare_headers(headers); + + if let Err(e) = self.send(status) { + return Err((self.stream, e)); + } + + let stream = match self.buffer { + Some(Buffer { buf, pos, cap }) => BufReader::from_parts(self.stream, buf, pos, cap), + None => BufReader::new(self.stream), + }; + + Ok(Client::unchecked(stream, self.headers, false, true)) + } } -impl WsUpgrade +impl WsUpgrade where S: Stream + AsTcpStream { /// Get a handle to the underlying TCP stream, useful to be able to set @@ -254,7 +280,7 @@ pub trait IntoWs { /// Attempt to parse the start of a Websocket handshake, later with the returned /// `WsUpgrade` struct, call `accept to start a websocket client, and `reject` to /// send a handshake rejection response. - fn into_ws(self) -> Result, Self::Error>; + fn into_ws(self) -> Result, Self::Error>; } impl IntoWs for S @@ -263,7 +289,7 @@ impl IntoWs for S type Stream = S; type Error = (S, Option, Option, HyperIntoWsError); - fn into_ws(self) -> Result, Self::Error> { + fn into_ws(self) -> Result, Self::Error> { let mut reader = BufReader::new(self); let request = parse_request(&mut reader); @@ -299,7 +325,7 @@ impl IntoWs for RequestStreamPair type Stream = S; type Error = (S, Request, HyperIntoWsError); - fn into_ws(self) -> Result, Self::Error> { + fn into_ws(self) -> Result, Self::Error> { match validate(&self.1.subject.0, &self.1.version, &self.1.headers) { Ok(_) => { Ok(WsUpgrade { From 117c3f3f7fc339a31c1701ee194ed4f696139e0b Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Tue, 23 May 2017 12:01:17 -0400 Subject: [PATCH 34/52] Split server into async and sync, implemented some server async. --- src/lib.rs | 1 - src/server/async.rs | 38 +++++ src/server/mod.rs | 320 +---------------------------------------- src/server/sync.rs | 337 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 381 insertions(+), 315 deletions(-) create mode 100644 src/server/async.rs create mode 100644 src/server/sync.rs diff --git a/src/lib.rs b/src/lib.rs index 8cc25dfad6..955bc73143 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,7 +63,6 @@ extern crate bitflags; extern crate test; pub use self::client::{Client, ClientBuilder}; -pub use self::server::Server; pub use self::dataframe::DataFrame; pub use self::message::{Message, OwnedMessage}; pub use self::stream::Stream; diff --git a/src/server/async.rs b/src/server/async.rs new file mode 100644 index 0000000000..05b85c29fc --- /dev/null +++ b/src/server/async.rs @@ -0,0 +1,38 @@ +use std::io; +use std::net::SocketAddr; +use server::{WsServer, NoTlsAcceptor}; +use tokio_core::net::TcpListener; +use native_tls::{TlsStream, TlsAcceptor}; +pub use tokio_core::reactor::Handle; + +pub type Server = WsServer; + +impl Server { + /// Bind this Server to this socket + pub fn bind(addr: &SocketAddr, handle: &Handle) -> io::Result { + Ok(Server { + listener: TcpListener::bind(addr, handle)?, + ssl_acceptor: NoTlsAcceptor, + }) + } + + /// Wait for and accept an incoming WebSocket connection, returning a WebSocketRequest + pub fn incoming(&mut self) { + unimplemented!(); + } +} + +impl Server { + /// Bind this Server to this socket + pub fn bind_secure(addr: &SocketAddr, acceptor: TlsAcceptor, handle: &Handle) -> io::Result { + Ok(Server { + listener: TcpListener::bind(addr, handle)?, + ssl_acceptor: acceptor, + }) + } + + /// Wait for and accept an incoming WebSocket connection, returning a WebSocketRequest + pub fn incoming(&mut self) { + unimplemented!(); + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs index 014678e5e9..7c23899e25 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,41 +1,14 @@ //! Provides an implementation of a WebSocket server -use std::net::{SocketAddr, ToSocketAddrs, TcpListener, TcpStream}; -use std::io; -use std::convert::Into; #[cfg(feature="ssl")] -use native_tls::{TlsStream, TlsAcceptor}; -use stream::Stream; -use self::upgrade::{SyncWsUpgrade, IntoWs, Buffer}; +use native_tls::TlsAcceptor; pub use self::upgrade::{Request, HyperIntoWsError}; pub mod upgrade; -/// When a sever tries to accept a connection many things can go wrong. -/// -/// This struct is all the information that is recovered from a failed -/// websocket handshake, in case one wants to use the connection for something -/// else (such as HTTP). -pub struct InvalidConnection - where S: Stream -{ - /// if the stream was successfully setup it will be included here - /// on a failed connection. - pub stream: Option, - /// the parsed request. **This is a normal HTTP request** meaning you can - /// simply run this server and handle both HTTP and Websocket connections. - /// If you already have a server you want to use, checkout the - /// `server::upgrade` module to integrate this crate with your server. - pub parsed: Option, - /// the buffered data that was already taken from the stream - pub buffer: Option, - /// the cause of the failed websocket connection setup - pub error: HyperIntoWsError, -} +#[cfg(feature="async")] +pub mod async; -/// Either the stream was established and it sent a websocket handshake -/// which represents the `Ok` variant, or there was an error (this is the -/// `Err` variant). -pub type AcceptResult = Result, InvalidConnection>; +pub mod sync; /// Marker struct for a struct not being secure #[derive(Clone)] @@ -49,291 +22,10 @@ impl OptionalTlsAcceptor for NoTlsAcceptor {} #[cfg(feature="ssl")] impl OptionalTlsAcceptor for TlsAcceptor {} -/// Represents a WebSocket server which can work with either normal -/// (non-secure) connections, or secure WebSocket connections. -/// -/// This is a convenient way to implement WebSocket servers, however -/// it is possible to use any sendable Reader and Writer to obtain -/// a WebSocketClient, so if needed, an alternative server implementation can be used. -///# Non-secure Servers -/// -/// ```no_run -///extern crate websocket; -///# fn main() { -///use std::thread; -///use websocket::{Server, Message}; -/// -///let server = Server::bind("127.0.0.1:1234").unwrap(); -/// -///for connection in server.filter_map(Result::ok) { -/// // Spawn a new thread for each connection. -/// thread::spawn(move || { -/// let mut client = connection.accept().unwrap(); -/// -/// let message = Message::text("Hello, client!"); -/// let _ = client.send_message(&message); -/// -/// // ... -/// }); -///} -/// # } -/// ``` -/// -///# Secure Servers -/// ```no_run -///extern crate websocket; -///extern crate openssl; -///# fn main() { -///use std::thread; -///use std::io::Read; -///use std::fs::File; -///use websocket::{Server, Message}; -///use openssl::pkcs12::Pkcs12; -///use openssl::ssl::{SslMethod, SslAcceptorBuilder, SslStream}; -/// -///// In this example we retrieve our keypair and certificate chain from a PKCS #12 archive, -///// but but they can also be retrieved from, for example, individual PEM- or DER-formatted -///// files. See the documentation for the `PKey` and `X509` types for more details. -///let mut file = File::open("identity.pfx").unwrap(); -///let mut pkcs12 = vec![]; -///file.read_to_end(&mut pkcs12).unwrap(); -///let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap(); -///let identity = pkcs12.parse("password123").unwrap(); -/// -///let acceptor = SslAcceptorBuilder::mozilla_intermediate(SslMethod::tls(), -/// &identity.pkey, -/// &identity.cert, -/// &identity.chain) -/// .unwrap() -/// .build(); -/// -///let server = Server::bind_secure("127.0.0.1:1234", acceptor).unwrap(); -/// -///for connection in server.filter_map(Result::ok) { -/// // Spawn a new thread for each connection. -/// thread::spawn(move || { -/// let mut client = connection.accept().unwrap(); -/// -/// let message = Message::text("Hello, client!"); -/// let _ = client.send_message(&message); -/// -/// // ... -/// }); -///} -/// # } -/// ``` -/// -/// # A Hyper Server -/// This crates comes with hyper integration out of the box, you can create a hyper -/// server and serve websocket and HTTP **on the same port!** -/// check out the docs over at `websocket::server::upgrade::from_hyper` for an example. -/// -/// # A Custom Server -/// So you don't want to use any of our server implementations? That's O.K. -/// All it takes is implementing the `IntoWs` trait for your server's streams, -/// then calling `.into_ws()` on them. -/// check out the docs over at `websocket::server::upgrade` for more. -pub struct Server +pub struct WsServer where S: OptionalTlsAcceptor { - listener: TcpListener, + listener: L, ssl_acceptor: S, } -impl Server - where S: OptionalTlsAcceptor -{ - /// Get the socket address of this server - pub fn local_addr(&self) -> io::Result { - self.listener.local_addr() - } - - /// Changes whether the Server is in nonblocking mode. - /// - /// If it is in nonblocking mode, accept() will return an error instead of blocking when there - /// are no incoming connections. - /// - ///# Examples - ///```no_run - /// # extern crate websocket; - /// # use websocket::Server; - /// # fn main() { - /// // Suppose we have to work in a single thread, but want to - /// // accomplish two unrelated things: - /// // (1) Once in a while we want to check if anybody tried to connect to - /// // our websocket server, and if so, handle the TcpStream. - /// // (2) In between we need to something else, possibly unrelated to networking. - /// - /// let mut server = Server::bind("127.0.0.1:0").unwrap(); - /// - /// // Set the server to non-blocking. - /// server.set_nonblocking(true); - /// - /// for i in 1..3 { - /// let result = match server.accept() { - /// Ok(wsupgrade) => { - /// // Do something with the established TcpStream. - /// } - /// _ => { - /// // Nobody tried to connect, move on. - /// } - /// }; - /// // Perform another task. Because we have a non-blocking server, - /// // this will execute independent of whether someone tried to - /// // establish a connection. - /// let two = 1+1; - /// } - /// # } - ///``` - pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { - self.listener.set_nonblocking(nonblocking) - } -} - -#[cfg(feature="ssl")] -impl Server { - /// Bind this Server to this socket, utilising the given SslContext - pub fn bind_secure(addr: A, acceptor: TlsAcceptor) -> io::Result - where A: ToSocketAddrs - { - Ok(Server { - listener: try!(TcpListener::bind(&addr)), - ssl_acceptor: acceptor, - }) - } - - /// Wait for and accept an incoming WebSocket connection, returning a WebSocketRequest - pub fn accept(&mut self) -> AcceptResult> { - let stream = match self.listener.accept() { - Ok(s) => s.0, - Err(e) => { - return Err(InvalidConnection { - stream: None, - parsed: None, - buffer: None, - error: e.into(), - }) - } - }; - - let stream = match self.ssl_acceptor.accept(stream) { - Ok(s) => s, - Err(err) => { - return Err(InvalidConnection { - stream: None, - parsed: None, - buffer: None, - error: io::Error::new(io::ErrorKind::Other, err).into(), - }) - } - }; - - match stream.into_ws() { - Ok(u) => Ok(u), - Err((s, r, b, e)) => { - Err(InvalidConnection { - stream: Some(s), - parsed: r, - buffer: b, - error: e.into(), - }) - } - } - } -} - -#[cfg(feature="ssl")] -impl Iterator for Server { - type Item = AcceptResult>; - - fn next(&mut self) -> Option<::Item> { - Some(self.accept()) - } -} - -impl Server { - /// Bind this Server to this socket - pub fn bind(addr: A) -> io::Result { - Ok(Server { - listener: try!(TcpListener::bind(&addr)), - ssl_acceptor: NoTlsAcceptor, - }) - } - - /// Wait for and accept an incoming WebSocket connection, returning a WebSocketRequest - pub fn accept(&mut self) -> AcceptResult { - let stream = match self.listener.accept() { - Ok(s) => s.0, - Err(e) => { - return Err(InvalidConnection { - stream: None, - parsed: None, - buffer: None, - error: e.into(), - }) - } - }; - - match stream.into_ws() { - Ok(u) => Ok(u), - Err((s, r, b, e)) => { - Err(InvalidConnection { - stream: Some(s), - parsed: r, - buffer: b, - error: e.into(), - }) - } - } - } - - /// Create a new independently owned handle to the underlying socket. - pub fn try_clone(&self) -> io::Result { - let inner = try!(self.listener.try_clone()); - Ok(Server { - listener: inner, - ssl_acceptor: self.ssl_acceptor.clone(), - }) - } -} - -impl Iterator for Server { - type Item = AcceptResult; - - fn next(&mut self) -> Option<::Item> { - Some(self.accept()) - } -} - -mod tests { - #[test] - // test the set_nonblocking() method for Server. - // Some of this is copied from - // https://doc.rust-lang.org/src/std/net/tcp.rs.html#1413 - fn set_nonblocking() { - - use super::*; - - // Test unsecure server - - let mut server = Server::bind("127.0.0.1:0").unwrap(); - - // Note that if set_nonblocking() doesn't work, but the following - // fails to panic for some reason, then the .accept() method below - // will block indefinitely. - server.set_nonblocking(true).unwrap(); - - let result = server.accept(); - match result { - // nobody tried to establish a connection, so we expect an error - Ok(_) => panic!("expected error"), - Err(e) => { - match e.error { - HyperIntoWsError::Io(ref e) if e.kind() == io::ErrorKind::WouldBlock => {} - _ => panic!("unexpected error {}"), - } - } - } - - } -} diff --git a/src/server/sync.rs b/src/server/sync.rs new file mode 100644 index 0000000000..06a9a0e7d9 --- /dev/null +++ b/src/server/sync.rs @@ -0,0 +1,337 @@ +//! Provides an implementation of a WebSocket server +use std::net::{SocketAddr, ToSocketAddrs, TcpListener, TcpStream}; +use std::io; +use std::convert::Into; +#[cfg(feature="ssl")] +use native_tls::{TlsStream, TlsAcceptor}; +use stream::Stream; +use server::{WsServer, OptionalTlsAcceptor, NoTlsAcceptor}; +use server::upgrade::{SyncWsUpgrade, IntoWs, Buffer}; +pub use server::upgrade::{Request, HyperIntoWsError}; + +#[cfg(feature="async")] +use tokio_core::reactor::Handle; +#[cfg(feature="async")] +use tokio_core::net::TcpListener as AsyncTcpListener; +#[cfg(feature="async")] +use server::async; + +/// When a sever tries to accept a connection many things can go wrong. +/// +/// This struct is all the information that is recovered from a failed +/// websocket handshake, in case one wants to use the connection for something +/// else (such as HTTP). +pub struct InvalidConnection + where S: Stream +{ + /// if the stream was successfully setup it will be included here + /// on a failed connection. + pub stream: Option, + /// the parsed request. **This is a normal HTTP request** meaning you can + /// simply run this server and handle both HTTP and Websocket connections. + /// If you already have a server you want to use, checkout the + /// `server::upgrade` module to integrate this crate with your server. + pub parsed: Option, + /// the buffered data that was already taken from the stream + pub buffer: Option, + /// the cause of the failed websocket connection setup + pub error: HyperIntoWsError, +} + +/// Either the stream was established and it sent a websocket handshake +/// which represents the `Ok` variant, or there was an error (this is the +/// `Err` variant). +pub type AcceptResult = Result, InvalidConnection>; + +/// Represents a WebSocket server which can work with either normal +/// (non-secure) connections, or secure WebSocket connections. +/// +/// This is a convenient way to implement WebSocket servers, however +/// it is possible to use any sendable Reader and Writer to obtain +/// a WebSocketClient, so if needed, an alternative server implementation can be used. +///# Non-secure Servers +/// +/// ```no_run +///extern crate websocket; +///# fn main() { +///use std::thread; +///use websocket::{Server, Message}; +/// +///let server = Server::bind("127.0.0.1:1234").unwrap(); +/// +///for connection in server.filter_map(Result::ok) { +/// // Spawn a new thread for each connection. +/// thread::spawn(move || { +/// let mut client = connection.accept().unwrap(); +/// +/// let message = Message::text("Hello, client!"); +/// let _ = client.send_message(&message); +/// +/// // ... +/// }); +///} +/// # } +/// ``` +/// +///# Secure Servers +/// ```no_run +///extern crate websocket; +///extern crate openssl; +///# fn main() { +///use std::thread; +///use std::io::Read; +///use std::fs::File; +///use websocket::{Server, Message}; +///use openssl::pkcs12::Pkcs12; +///use openssl::ssl::{SslMethod, SslAcceptorBuilder, SslStream}; +/// +///// In this example we retrieve our keypair and certificate chain from a PKCS #12 archive, +///// but but they can also be retrieved from, for example, individual PEM- or DER-formatted +///// files. See the documentation for the `PKey` and `X509` types for more details. +///let mut file = File::open("identity.pfx").unwrap(); +///let mut pkcs12 = vec![]; +///file.read_to_end(&mut pkcs12).unwrap(); +///let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap(); +///let identity = pkcs12.parse("password123").unwrap(); +/// +///let acceptor = SslAcceptorBuilder::mozilla_intermediate(SslMethod::tls(), +/// &identity.pkey, +/// &identity.cert, +/// &identity.chain) +/// .unwrap() +/// .build(); +/// +///let server = Server::bind_secure("127.0.0.1:1234", acceptor).unwrap(); +/// +///for connection in server.filter_map(Result::ok) { +/// // Spawn a new thread for each connection. +/// thread::spawn(move || { +/// let mut client = connection.accept().unwrap(); +/// +/// let message = Message::text("Hello, client!"); +/// let _ = client.send_message(&message); +/// +/// // ... +/// }); +///} +/// # } +/// ``` +/// +/// # A Hyper Server +/// This crates comes with hyper integration out of the box, you can create a hyper +/// server and serve websocket and HTTP **on the same port!** +/// check out the docs over at `websocket::server::upgrade::from_hyper` for an example. +/// +/// # A Custom Server +/// So you don't want to use any of our server implementations? That's O.K. +/// All it takes is implementing the `IntoWs` trait for your server's streams, +/// then calling `.into_ws()` on them. +/// check out the docs over at `websocket::server::upgrade` for more. +pub type Server = WsServer; + +impl Server + where S: OptionalTlsAcceptor +{ + /// Get the socket address of this server + pub fn local_addr(&self) -> io::Result { + self.listener.local_addr() + } + + /// Changes whether the Server is in nonblocking mode. + /// + /// If it is in nonblocking mode, accept() will return an error instead of blocking when there + /// are no incoming connections. + /// + ///# Examples + ///```no_run + /// # extern crate websocket; + /// # use websocket::Server; + /// # fn main() { + /// // Suppose we have to work in a single thread, but want to + /// // accomplish two unrelated things: + /// // (1) Once in a while we want to check if anybody tried to connect to + /// // our websocket server, and if so, handle the TcpStream. + /// // (2) In between we need to something else, possibly unrelated to networking. + /// + /// let mut server = Server::bind("127.0.0.1:0").unwrap(); + /// + /// // Set the server to non-blocking. + /// server.set_nonblocking(true); + /// + /// for i in 1..3 { + /// let result = match server.accept() { + /// Ok(wsupgrade) => { + /// // Do something with the established TcpStream. + /// } + /// _ => { + /// // Nobody tried to connect, move on. + /// } + /// }; + /// // Perform another task. Because we have a non-blocking server, + /// // this will execute independent of whether someone tried to + /// // establish a connection. + /// let two = 1+1; + /// } + /// # } + ///``` + pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { + self.listener.set_nonblocking(nonblocking) + } + + #[cfg(feature="async")] + pub fn into_async(self, handle: &Handle) -> io::Result> { + let addr = self.listener.local_addr()?; + Ok(WsServer { + listener: AsyncTcpListener::from_listener(self.listener, &addr, handle)?, + ssl_acceptor: self.ssl_acceptor, + }) + } +} + +#[cfg(feature="ssl")] +impl Server { + /// Bind this Server to this socket, utilising the given SslContext + pub fn bind_secure(addr: A, acceptor: TlsAcceptor) -> io::Result + where A: ToSocketAddrs + { + Ok(Server { + listener: try!(TcpListener::bind(&addr)), + ssl_acceptor: acceptor, + }) + } + + /// Wait for and accept an incoming WebSocket connection, returning a WebSocketRequest + pub fn accept(&mut self) -> AcceptResult> { + let stream = match self.listener.accept() { + Ok(s) => s.0, + Err(e) => { + return Err(InvalidConnection { + stream: None, + parsed: None, + buffer: None, + error: e.into(), + }) + } + }; + + let stream = match self.ssl_acceptor.accept(stream) { + Ok(s) => s, + Err(err) => { + return Err(InvalidConnection { + stream: None, + parsed: None, + buffer: None, + error: io::Error::new(io::ErrorKind::Other, err).into(), + }) + } + }; + + match stream.into_ws() { + Ok(u) => Ok(u), + Err((s, r, b, e)) => { + Err(InvalidConnection { + stream: Some(s), + parsed: r, + buffer: b, + error: e.into(), + }) + } + } + } +} + +#[cfg(feature="ssl")] +impl Iterator for Server { + type Item = AcceptResult>; + + fn next(&mut self) -> Option<::Item> { + Some(self.accept()) + } +} + +impl Server { + /// Bind this Server to this socket + pub fn bind(addr: A) -> io::Result { + Ok(Server { + listener: try!(TcpListener::bind(&addr)), + ssl_acceptor: NoTlsAcceptor, + }) + } + + /// Wait for and accept an incoming WebSocket connection, returning a WebSocketRequest + pub fn accept(&mut self) -> AcceptResult { + let stream = match self.listener.accept() { + Ok(s) => s.0, + Err(e) => { + return Err(InvalidConnection { + stream: None, + parsed: None, + buffer: None, + error: e.into(), + }) + } + }; + + match stream.into_ws() { + Ok(u) => Ok(u), + Err((s, r, b, e)) => { + Err(InvalidConnection { + stream: Some(s), + parsed: r, + buffer: b, + error: e.into(), + }) + } + } + } + + /// Create a new independently owned handle to the underlying socket. + pub fn try_clone(&self) -> io::Result { + let inner = try!(self.listener.try_clone()); + Ok(Server { + listener: inner, + ssl_acceptor: self.ssl_acceptor.clone(), + }) + } +} + +impl Iterator for Server { + type Item = AcceptResult; + + fn next(&mut self) -> Option<::Item> { + Some(self.accept()) + } +} + +mod tests { + #[test] + // test the set_nonblocking() method for Server. + // Some of this is copied from + // https://doc.rust-lang.org/src/std/net/tcp.rs.html#1413 + fn set_nonblocking() { + + use super::*; + + // Test unsecure server + + let mut server = Server::bind("127.0.0.1:0").unwrap(); + + // Note that if set_nonblocking() doesn't work, but the following + // fails to panic for some reason, then the .accept() method below + // will block indefinitely. + server.set_nonblocking(true).unwrap(); + + let result = server.accept(); + match result { + // nobody tried to establish a connection, so we expect an error + Ok(_) => panic!("expected error"), + Err(e) => { + match e.error { + HyperIntoWsError::Io(ref e) if e.kind() == io::ErrorKind::WouldBlock => {} + _ => panic!("unexpected error {}"), + } + } + } + + } +} From 5238b56231e6d1d83045fc48ee8f5dcf843dbe52 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Tue, 23 May 2017 13:57:52 -0400 Subject: [PATCH 35/52] Restructured to make crate prefer neither sync or async. --- Cargo.toml | 7 +- src/client/async.rs | 17 ++ src/client/builder.rs | 34 ++- src/client/mod.rs | 401 +------------------------------ src/client/sync.rs | 383 +++++++++++++++++++++++++++++ src/lib.rs | 79 +++++- src/receiver.rs | 4 +- src/result.rs | 22 +- src/sender.rs | 4 +- src/server/async.rs | 35 +-- src/server/mod.rs | 6 +- src/server/sync.rs | 20 +- src/server/upgrade/async.rs | 44 ++-- src/server/upgrade/from_hyper.rs | 88 ------- src/server/upgrade/mod.rs | 204 +--------------- src/server/upgrade/sync.rs | 281 ++++++++++++++++++++++ src/stream.rs | 276 ++++++++++----------- src/ws/mod.rs | 11 +- 18 files changed, 1005 insertions(+), 911 deletions(-) create mode 100644 src/client/async.rs create mode 100644 src/client/sync.rs delete mode 100644 src/server/upgrade/from_hyper.rs create mode 100644 src/server/upgrade/sync.rs diff --git a/Cargo.toml b/Cargo.toml index bac73238c4..d5d5c5f40d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,8 +36,9 @@ native-tls = { version = "^0.1.2", optional = true } git = "https://github.com/tokio-rs/tokio-io.git" [features] -default = ["ssl", "async", "async-ssl"] -ssl = ["native-tls"] -nightly = ["hyper/nightly"] +default = ["sync", "sync-ssl", "async", "async-ssl"] +sync = [] +sync-ssl = ["native-tls", "sync"] async = ["tokio-core", "tokio-io", "bytes", "futures"] async-ssl = ["native-tls", "tokio-tls", "async"] +nightly = ["hyper/nightly"] diff --git a/src/client/async.rs b/src/client/async.rs new file mode 100644 index 0000000000..e97a903832 --- /dev/null +++ b/src/client/async.rs @@ -0,0 +1,17 @@ +pub use tokio_core::reactor::Handle; +pub use tokio_io::codec::Framed; +pub use tokio_core::net::TcpStream; +pub use futures::Future; +use hyper::header::Headers; + +use result::WebSocketError; +use codec::ws::MessageCodec; +use message::OwnedMessage; + +#[cfg(feature="async-ssl")] +pub use tokio_tls::TlsStream; + +pub type Client = Framed>; + +pub type ClientNew = Box, Headers), Error = WebSocketError>>; + diff --git a/src/client/builder.rs b/src/client/builder.rs index c5a694237c..65bb7e96e5 100644 --- a/src/client/builder.rs +++ b/src/client/builder.rs @@ -16,17 +16,20 @@ use hyper::http::h1::parse_response; use hyper::header::{Headers, Header, HeaderFormat, Host, Connection, ConnectionOption, Upgrade, Protocol, ProtocolName}; use unicase::UniCase; -#[cfg(any(feature="ssl", feature="async-ssl"))] -use native_tls::{TlsStream, TlsConnector}; use header::extensions::Extension; use header::{WebSocketAccept, WebSocketKey, WebSocketVersion, WebSocketProtocol, WebSocketExtensions, Origin}; use result::{WSUrlErrorKind, WebSocketResult, WebSocketError}; -#[cfg(feature="ssl")] -use stream::NetworkStream; use stream::{self, Stream}; -use super::Client; +#[cfg(feature="sync")] +use super::sync::Client; + +#[cfg(feature="sync-ssl")] +use stream::sync::NetworkStream; + +#[cfg(any(feature="sync-ssl", feature="async-ssl"))] +use native_tls::{TlsStream, TlsConnector}; #[cfg(feature="async")] mod async_imports { @@ -375,7 +378,7 @@ impl<'u> ClientBuilder<'u> { /// let message = Message::text("m337 47 7pm"); /// client.send_message(&message).unwrap(); /// ``` - #[cfg(feature="ssl")] + #[cfg(feature="sync-ssl")] pub fn connect( &mut self, ssl_config: Option, @@ -406,6 +409,7 @@ impl<'u> ClientBuilder<'u> { /// // split into two (for some reason)! /// let (receiver, sender) = client.split().unwrap(); /// ``` + #[cfg(feature="sync")] pub fn connect_insecure(&mut self) -> WebSocketResult> { let tcp_stream = try!(self.establish_tcp(Some(false))); @@ -416,7 +420,7 @@ impl<'u> ClientBuilder<'u> { /// This will only use an `SslStream`, this is useful /// when you want to be sure to connect over SSL or when you want access /// to the `SslStream` functions (without having to go through a `Box`). - #[cfg(feature="ssl")] + #[cfg(feature="sync-ssl")] pub fn connect_secure( &mut self, ssl_config: Option, @@ -458,6 +462,7 @@ impl<'u> ClientBuilder<'u> { /// let text = String::from_utf8(text).unwrap(); /// assert!(text.contains("dGhlIHNhbXBsZSBub25jZQ=="), "{}", text); /// ``` + #[cfg(feature="sync")] pub fn connect_on(&mut self, mut stream: S) -> WebSocketResult> where S: Stream { @@ -476,6 +481,15 @@ impl<'u> ClientBuilder<'u> { Ok(Client::unchecked(reader, response.headers, true, false)) } + #[cfg(feature="async-ssl")] + pub fn async_connect( + self, + ssl_config: Option, + handle: &Handle, + ) -> async::ClientNew> { + unimplemented!(); + } + #[cfg(feature="async-ssl")] pub fn async_connect_secure( self, @@ -542,7 +556,7 @@ impl<'u> ClientBuilder<'u> { #[cfg(feature="async")] pub fn async_connect_on(self, stream: S) -> async::ClientNew - where S: stream::AsyncStream + Send + 'static + where S: stream::async::Stream + Send + 'static { let mut builder = ClientBuilder { url: Cow::Owned(self.url.into_owned()), @@ -695,7 +709,7 @@ impl<'u> ClientBuilder<'u> { Ok(TcpStream::connect(self.extract_host_port(secure)?)?) } - #[cfg(any(feature="ssl", feature="async-ssl"))] + #[cfg(any(feature="sync-ssl", feature="async-ssl"))] fn extract_host_ssl_conn( &self, connector: Option, @@ -711,7 +725,7 @@ impl<'u> ClientBuilder<'u> { Ok((host, connector)) } - #[cfg(feature="ssl")] + #[cfg(feature="sync-ssl")] fn wrap_ssl( &self, tcp_stream: TcpStream, diff --git a/src/client/mod.rs b/src/client/mod.rs index 46a88201c2..15e2bdb781 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,404 +1,9 @@ -//! Contains the WebSocket client. -extern crate url; - -use std::net::TcpStream; -use std::net::SocketAddr; -use std::io::Result as IoResult; -use std::io::{Read, Write}; -use hyper::header::Headers; -use hyper::buffer::BufReader; - -use ws; -use ws::sender::Sender as SenderTrait; -use ws::receiver::{DataFrameIterator, MessageIterator}; -use ws::receiver::Receiver as ReceiverTrait; -use message::OwnedMessage; -use result::{WebSocketResult, WebSocketError}; -use stream::{AsTcpStream, Stream, Splittable, Shutdown}; -use dataframe::DataFrame; -use header::{WebSocketProtocol, WebSocketExtensions}; -use header::extensions::Extension; - -use ws::dataframe::DataFrame as DataFrameable; -use sender::Sender; -use receiver::Receiver; -pub use sender::Writer; -pub use receiver::Reader; - pub mod builder; pub use self::builder::{ClientBuilder, Url, ParseError}; #[cfg(feature="async")] -pub mod async { - use super::*; - pub use tokio_core::reactor::Handle; - pub use tokio_io::codec::Framed; - pub use tokio_core::net::TcpStream; - pub use futures::Future; - use codec::ws::MessageCodec; - #[cfg(feature="async-ssl")] - pub use tokio_tls::TlsStream; - - pub type Client = Framed>; - - pub type ClientNew = Box, Headers), Error = WebSocketError>>; -} - -/// Represents a WebSocket client, which can send and receive messages/data frames. -/// -/// The client just wraps around a `Stream` (which is something that can be read from -/// and written to) and handles the websocket protocol. TCP or SSL over TCP is common, -/// but any stream can be used. -/// -/// A `Client` can also be split into a `Reader` and a `Writer` which can then be moved -/// to different threads, often using a send loop and receiver loop concurrently, -/// as shown in the client example in `examples/client.rs`. -/// This is only possible for streams that implement the `Splittable` trait, which -/// currently is only TCP streams. (it is unsafe to duplicate an SSL stream) -/// -///# Connecting to a Server -/// -///```no_run -///extern crate websocket; -///# fn main() { -/// -///use websocket::{ClientBuilder, Message}; -/// -///let mut client = ClientBuilder::new("ws://127.0.0.1:1234") -/// .unwrap() -/// .connect_insecure() -/// .unwrap(); -/// -///let message = Message::text("Hello, World!"); -///client.send_message(&message).unwrap(); // Send message -///# } -///``` -pub struct Client - where S: Stream -{ - stream: BufReader, - headers: Headers, - sender: Sender, - receiver: Receiver, -} - -impl Client { - /// Shuts down the sending half of the client connection, will cause all pending - /// and future IO to return immediately with an appropriate value. - pub fn shutdown_sender(&self) -> IoResult<()> { - self.stream.get_ref().as_tcp().shutdown(Shutdown::Write) - } - - /// Shuts down the receiving half of the client connection, will cause all pending - /// and future IO to return immediately with an appropriate value. - pub fn shutdown_receiver(&self) -> IoResult<()> { - self.stream.get_ref().as_tcp().shutdown(Shutdown::Read) - } -} - -impl Client - where S: AsTcpStream + Stream -{ - /// Shuts down the client connection, will cause all pending and future IO to - /// return immediately with an appropriate value. - pub fn shutdown(&self) -> IoResult<()> { - self.stream.get_ref().as_tcp().shutdown(Shutdown::Both) - } - - /// See [`TcpStream::peer_addr`] - /// (https://doc.rust-lang.org/std/net/struct.TcpStream.html#method.peer_addr). - pub fn peer_addr(&self) -> IoResult { - self.stream.get_ref().as_tcp().peer_addr() - } - - /// See [`TcpStream::local_addr`] - /// (https://doc.rust-lang.org/std/net/struct.TcpStream.html#method.local_addr). - pub fn local_addr(&self) -> IoResult { - self.stream.get_ref().as_tcp().local_addr() - } - - /// See [`TcpStream::set_nodelay`] - /// (https://doc.rust-lang.org/std/net/struct.TcpStream.html#method.set_nodelay). - pub fn set_nodelay(&mut self, nodelay: bool) -> IoResult<()> { - self.stream.get_ref().as_tcp().set_nodelay(nodelay) - } - - /// Changes whether the stream is in nonblocking mode. - pub fn set_nonblocking(&self, nonblocking: bool) -> IoResult<()> { - self.stream.get_ref().as_tcp().set_nonblocking(nonblocking) - } -} - -impl Client - where S: Stream -{ - /// Creates a Client from a given stream - /// **without sending any handshake** this is meant to only be used with - /// a stream that has a websocket connection already set up. - /// If in doubt, don't use this! - #[doc(hidden)] - pub fn unchecked( - stream: BufReader, - headers: Headers, - out_mask: bool, - in_mask: bool, - ) -> Self { - Client { - headers: headers, - stream: stream, - sender: Sender::new(out_mask), // true - receiver: Receiver::new(in_mask), // false - } - } - - /// Sends a single data frame to the remote endpoint. - pub fn send_dataframe(&mut self, dataframe: &D) -> WebSocketResult<()> - where D: DataFrameable - { - self.sender.send_dataframe(self.stream.get_mut(), dataframe) - } - - /// Sends a single message to the remote endpoint. - pub fn send_message(&mut self, message: &M) -> WebSocketResult<()> - where M: ws::Message - { - self.sender.send_message(self.stream.get_mut(), message) - } - - /// Reads a single data frame from the remote endpoint. - pub fn recv_dataframe(&mut self) -> WebSocketResult { - self.receiver.recv_dataframe(&mut self.stream) - } - - /// Returns an iterator over incoming data frames. - pub fn incoming_dataframes(&mut self) -> DataFrameIterator> { - self.receiver.incoming_dataframes(&mut self.stream) - } - - /// Reads a single message from this receiver. - /// - /// ```rust,no_run - /// use websocket::{ClientBuilder, Message}; - /// let mut client = ClientBuilder::new("ws://localhost:3000") - /// .unwrap() - /// .connect_insecure() - /// .unwrap(); - /// - /// client.send_message(&Message::text("Hello world!")).unwrap(); - /// - /// let message: Message = client.recv_message().unwrap(); - /// ``` - pub fn recv_message(&mut self) -> WebSocketResult - where I: Iterator - { - self.receiver.recv_message(&mut self.stream) - } - - /// Access the headers that were sent in the server's handshake response. - /// This is a catch all for headers other than protocols and extensions. - pub fn headers(&self) -> &Headers { - &self.headers - } - - /// **If you supplied a protocol, you must check that it was accepted by - /// the server** using this function. - /// This is not done automatically because the terms of accepting a protocol - /// can get complicated, especially if some protocols depend on others, etc. - /// - /// ```rust,no_run - /// # use websocket::ClientBuilder; - /// let mut client = ClientBuilder::new("wss://test.fysh.in").unwrap() - /// .add_protocol("xmpp") - /// .connect_insecure() - /// .unwrap(); - /// - /// // be sure to check the protocol is there! - /// assert!(client.protocols().iter().any(|p| p as &str == "xmpp")); - /// ``` - pub fn protocols(&self) -> &[String] { - self.headers - .get::() - .map(|p| p.0.as_slice()) - .unwrap_or(&[]) - } - - /// If you supplied a protocol, be sure to check if it was accepted by the - /// server here. Since no extensions are implemented out of the box yet, using - /// one will require its own implementation. - pub fn extensions(&self) -> &[Extension] { - self.headers - .get::() - .map(|e| e.0.as_slice()) - .unwrap_or(&[]) - } - - /// Get a reference to the stream. - /// Useful to be able to set options on the stream. - /// - /// ```rust,no_run - /// # use websocket::ClientBuilder; - /// let mut client = ClientBuilder::new("ws://double.down").unwrap() - /// .connect_insecure() - /// .unwrap(); - /// - /// client.stream_ref().set_ttl(60).unwrap(); - /// ``` - pub fn stream_ref(&self) -> &S { - self.stream.get_ref() - } - - /// Get a handle to the writable portion of this stream. - /// This can be used to write custom extensions. - /// - /// ```rust,no_run - /// # use websocket::ClientBuilder; - /// use websocket::Message; - /// use websocket::ws::sender::Sender as SenderTrait; - /// use websocket::sender::Sender; - /// - /// let mut client = ClientBuilder::new("ws://the.room").unwrap() - /// .connect_insecure() - /// .unwrap(); - /// - /// let message = Message::text("Oh hi, Mark."); - /// let mut sender = Sender::new(true); - /// let mut buf = Vec::new(); - /// - /// sender.send_message(&mut buf, &message); - /// - /// /* transform buf somehow */ - /// - /// client.writer_mut().write_all(&buf); - /// ``` - pub fn writer_mut(&mut self) -> &mut Write { - self.stream.get_mut() - } - - /// Get a handle to the readable portion of this stream. - /// This can be used to transform raw bytes before they - /// are read in. - /// - /// ```rust,no_run - /// # use websocket::ClientBuilder; - /// use std::io::Cursor; - /// use websocket::Message; - /// use websocket::ws::receiver::Receiver as ReceiverTrait; - /// use websocket::receiver::Receiver; - /// - /// let mut client = ClientBuilder::new("ws://the.room").unwrap() - /// .connect_insecure() - /// .unwrap(); - /// - /// let mut receiver = Receiver::new(false); - /// let mut buf = Vec::new(); - /// - /// client.reader_mut().read_to_end(&mut buf); - /// - /// /* transform buf somehow */ - /// - /// let mut buf_reader = Cursor::new(&mut buf); - /// let message: Message = receiver.recv_message(&mut buf_reader).unwrap(); - /// ``` - pub fn reader_mut(&mut self) -> &mut Read { - &mut self.stream - } - - /// Deconstruct the client into its underlying stream and - /// maybe some of the buffer that was already read from the stream. - /// The client uses a buffered reader to read in messages, so some - /// bytes might already be read from the stream when this is called, - /// these buffered bytes are returned in the form - /// - /// `(byte_buffer: Vec, buffer_capacity: usize, buffer_position: usize)` - pub fn into_stream(self) -> (S, Option<(Vec, usize, usize)>) { - let (stream, buf, pos, cap) = self.stream.into_parts(); - (stream, Some((buf, pos, cap))) - } +pub mod async; - /// Returns an iterator over incoming messages. - /// - ///```no_run - ///# extern crate websocket; - ///# fn main() { - ///use websocket::{ClientBuilder, Message}; - /// - ///let mut client = ClientBuilder::new("ws://127.0.0.1:1234").unwrap() - /// .connect(None).unwrap(); - /// - ///for message in client.incoming_messages() { - /// let message: Message = message.unwrap(); - /// println!("Recv: {:?}", message); - ///} - ///# } - ///``` - /// - /// Note that since this method mutably borrows the `Client`, it may be necessary to - /// first `split()` the `Client` and call `incoming_messages()` on the returned - /// `Receiver` to be able to send messages within an iteration. - /// - ///```no_run - ///# extern crate websocket; - ///# fn main() { - ///use websocket::{ClientBuilder, Message}; - /// - ///let mut client = ClientBuilder::new("ws://127.0.0.1:1234").unwrap() - /// .connect_insecure().unwrap(); - /// - ///let (mut receiver, mut sender) = client.split().unwrap(); - /// - ///for message in receiver.incoming_messages() { - /// let message: Message = message.unwrap(); - /// // Echo the message back - /// sender.send_message(&message).unwrap(); - ///} - ///# } - ///``` - pub fn incoming_messages<'a>(&'a mut self) -> MessageIterator<'a, Receiver, BufReader> { - self.receiver.incoming_messages(&mut self.stream) - } -} +#[cfg(feature="sync")] +pub mod sync; -impl Client - where S: Splittable + Stream -{ - /// Split this client into its constituent Sender and Receiver pair. - /// - /// This allows the Sender and Receiver to be sent to different threads. - /// - ///```no_run - ///# extern crate websocket; - ///# fn main() { - ///use std::thread; - ///use websocket::{ClientBuilder, Message}; - /// - ///let mut client = ClientBuilder::new("ws://127.0.0.1:1234").unwrap() - /// .connect_insecure().unwrap(); - /// - ///let (mut receiver, mut sender) = client.split().unwrap(); - /// - ///thread::spawn(move || { - /// for message in receiver.incoming_messages() { - /// let message: Message = message.unwrap(); - /// println!("Recv: {:?}", message); - /// } - ///}); - /// - ///let message = Message::text("Hello, World!"); - ///sender.send_message(&message).unwrap(); - ///# } - ///``` - pub fn split - (self,) - -> IoResult<(Reader<::Reader>, Writer<::Writer>)> { - let (stream, buf, pos, cap) = self.stream.into_parts(); - let (read, write) = try!(stream.split()); - Ok((Reader { - stream: BufReader::from_parts(read, buf, pos, cap), - receiver: self.receiver, - }, - Writer { - stream: write, - sender: self.sender, - })) - } -} diff --git a/src/client/sync.rs b/src/client/sync.rs new file mode 100644 index 0000000000..0a0749e482 --- /dev/null +++ b/src/client/sync.rs @@ -0,0 +1,383 @@ +//! Contains the WebSocket client. +use std::net::TcpStream; +use std::net::SocketAddr; +use std::io::Result as IoResult; +use std::io::{Read, Write}; +use hyper::header::Headers; +use hyper::buffer::BufReader; + +use ws; +use ws::sender::Sender as SenderTrait; +use ws::receiver::{DataFrameIterator, MessageIterator}; +use ws::receiver::Receiver as ReceiverTrait; +use message::OwnedMessage; +use result::WebSocketResult; +use stream::sync::{AsTcpStream, Stream, Splittable, Shutdown}; +use dataframe::DataFrame; +use header::{WebSocketProtocol, WebSocketExtensions}; +use header::extensions::Extension; + +use ws::dataframe::DataFrame as DataFrameable; +use sender::Sender; +use receiver::Receiver; +pub use sender::Writer; +pub use receiver::Reader; + +/// Represents a WebSocket client, which can send and receive messages/data frames. +/// +/// The client just wraps around a `Stream` (which is something that can be read from +/// and written to) and handles the websocket protocol. TCP or SSL over TCP is common, +/// but any stream can be used. +/// +/// A `Client` can also be split into a `Reader` and a `Writer` which can then be moved +/// to different threads, often using a send loop and receiver loop concurrently, +/// as shown in the client example in `examples/client.rs`. +/// This is only possible for streams that implement the `Splittable` trait, which +/// currently is only TCP streams. (it is unsafe to duplicate an SSL stream) +/// +///# Connecting to a Server +/// +///```no_run +///extern crate websocket; +///# fn main() { +/// +///use websocket::{ClientBuilder, Message}; +/// +///let mut client = ClientBuilder::new("ws://127.0.0.1:1234") +/// .unwrap() +/// .connect_insecure() +/// .unwrap(); +/// +///let message = Message::text("Hello, World!"); +///client.send_message(&message).unwrap(); // Send message +///# } +///``` +pub struct Client + where S: Stream +{ + stream: BufReader, + headers: Headers, + sender: Sender, + receiver: Receiver, +} + +impl Client { + /// Shuts down the sending half of the client connection, will cause all pending + /// and future IO to return immediately with an appropriate value. + pub fn shutdown_sender(&self) -> IoResult<()> { + self.stream.get_ref().as_tcp().shutdown(Shutdown::Write) + } + + /// Shuts down the receiving half of the client connection, will cause all pending + /// and future IO to return immediately with an appropriate value. + pub fn shutdown_receiver(&self) -> IoResult<()> { + self.stream.get_ref().as_tcp().shutdown(Shutdown::Read) + } +} + +impl Client + where S: AsTcpStream + Stream +{ + /// Shuts down the client connection, will cause all pending and future IO to + /// return immediately with an appropriate value. + pub fn shutdown(&self) -> IoResult<()> { + self.stream.get_ref().as_tcp().shutdown(Shutdown::Both) + } + + /// See [`TcpStream::peer_addr`] + /// (https://doc.rust-lang.org/std/net/struct.TcpStream.html#method.peer_addr). + pub fn peer_addr(&self) -> IoResult { + self.stream.get_ref().as_tcp().peer_addr() + } + + /// See [`TcpStream::local_addr`] + /// (https://doc.rust-lang.org/std/net/struct.TcpStream.html#method.local_addr). + pub fn local_addr(&self) -> IoResult { + self.stream.get_ref().as_tcp().local_addr() + } + + /// See [`TcpStream::set_nodelay`] + /// (https://doc.rust-lang.org/std/net/struct.TcpStream.html#method.set_nodelay). + pub fn set_nodelay(&mut self, nodelay: bool) -> IoResult<()> { + self.stream.get_ref().as_tcp().set_nodelay(nodelay) + } + + /// Changes whether the stream is in nonblocking mode. + pub fn set_nonblocking(&self, nonblocking: bool) -> IoResult<()> { + self.stream.get_ref().as_tcp().set_nonblocking(nonblocking) + } +} + +impl Client + where S: Stream +{ + /// Creates a Client from a given stream + /// **without sending any handshake** this is meant to only be used with + /// a stream that has a websocket connection already set up. + /// If in doubt, don't use this! + #[doc(hidden)] + pub fn unchecked( + stream: BufReader, + headers: Headers, + out_mask: bool, + in_mask: bool, + ) -> Self { + Client { + headers: headers, + stream: stream, + sender: Sender::new(out_mask), // true + receiver: Receiver::new(in_mask), // false + } + } + + /// Sends a single data frame to the remote endpoint. + pub fn send_dataframe(&mut self, dataframe: &D) -> WebSocketResult<()> + where D: DataFrameable + { + self.sender.send_dataframe(self.stream.get_mut(), dataframe) + } + + /// Sends a single message to the remote endpoint. + pub fn send_message(&mut self, message: &M) -> WebSocketResult<()> + where M: ws::Message + { + self.sender.send_message(self.stream.get_mut(), message) + } + + /// Reads a single data frame from the remote endpoint. + pub fn recv_dataframe(&mut self) -> WebSocketResult { + self.receiver.recv_dataframe(&mut self.stream) + } + + /// Returns an iterator over incoming data frames. + pub fn incoming_dataframes(&mut self) -> DataFrameIterator> { + self.receiver.incoming_dataframes(&mut self.stream) + } + + /// Reads a single message from this receiver. + /// + /// ```rust,no_run + /// use websocket::{ClientBuilder, Message}; + /// let mut client = ClientBuilder::new("ws://localhost:3000") + /// .unwrap() + /// .connect_insecure() + /// .unwrap(); + /// + /// client.send_message(&Message::text("Hello world!")).unwrap(); + /// + /// let message: Message = client.recv_message().unwrap(); + /// ``` + pub fn recv_message(&mut self) -> WebSocketResult + where I: Iterator + { + self.receiver.recv_message(&mut self.stream) + } + + /// Access the headers that were sent in the server's handshake response. + /// This is a catch all for headers other than protocols and extensions. + pub fn headers(&self) -> &Headers { + &self.headers + } + + /// **If you supplied a protocol, you must check that it was accepted by + /// the server** using this function. + /// This is not done automatically because the terms of accepting a protocol + /// can get complicated, especially if some protocols depend on others, etc. + /// + /// ```rust,no_run + /// # use websocket::ClientBuilder; + /// let mut client = ClientBuilder::new("wss://test.fysh.in").unwrap() + /// .add_protocol("xmpp") + /// .connect_insecure() + /// .unwrap(); + /// + /// // be sure to check the protocol is there! + /// assert!(client.protocols().iter().any(|p| p as &str == "xmpp")); + /// ``` + pub fn protocols(&self) -> &[String] { + self.headers + .get::() + .map(|p| p.0.as_slice()) + .unwrap_or(&[]) + } + + /// If you supplied a protocol, be sure to check if it was accepted by the + /// server here. Since no extensions are implemented out of the box yet, using + /// one will require its own implementation. + pub fn extensions(&self) -> &[Extension] { + self.headers + .get::() + .map(|e| e.0.as_slice()) + .unwrap_or(&[]) + } + + /// Get a reference to the stream. + /// Useful to be able to set options on the stream. + /// + /// ```rust,no_run + /// # use websocket::ClientBuilder; + /// let mut client = ClientBuilder::new("ws://double.down").unwrap() + /// .connect_insecure() + /// .unwrap(); + /// + /// client.stream_ref().set_ttl(60).unwrap(); + /// ``` + pub fn stream_ref(&self) -> &S { + self.stream.get_ref() + } + + /// Get a handle to the writable portion of this stream. + /// This can be used to write custom extensions. + /// + /// ```rust,no_run + /// # use websocket::ClientBuilder; + /// use websocket::Message; + /// use websocket::ws::sender::Sender as SenderTrait; + /// use websocket::sender::Sender; + /// + /// let mut client = ClientBuilder::new("ws://the.room").unwrap() + /// .connect_insecure() + /// .unwrap(); + /// + /// let message = Message::text("Oh hi, Mark."); + /// let mut sender = Sender::new(true); + /// let mut buf = Vec::new(); + /// + /// sender.send_message(&mut buf, &message); + /// + /// /* transform buf somehow */ + /// + /// client.writer_mut().write_all(&buf); + /// ``` + pub fn writer_mut(&mut self) -> &mut Write { + self.stream.get_mut() + } + + /// Get a handle to the readable portion of this stream. + /// This can be used to transform raw bytes before they + /// are read in. + /// + /// ```rust,no_run + /// # use websocket::ClientBuilder; + /// use std::io::Cursor; + /// use websocket::Message; + /// use websocket::ws::receiver::Receiver as ReceiverTrait; + /// use websocket::receiver::Receiver; + /// + /// let mut client = ClientBuilder::new("ws://the.room").unwrap() + /// .connect_insecure() + /// .unwrap(); + /// + /// let mut receiver = Receiver::new(false); + /// let mut buf = Vec::new(); + /// + /// client.reader_mut().read_to_end(&mut buf); + /// + /// /* transform buf somehow */ + /// + /// let mut buf_reader = Cursor::new(&mut buf); + /// let message: Message = receiver.recv_message(&mut buf_reader).unwrap(); + /// ``` + pub fn reader_mut(&mut self) -> &mut Read { + &mut self.stream + } + + /// Deconstruct the client into its underlying stream and + /// maybe some of the buffer that was already read from the stream. + /// The client uses a buffered reader to read in messages, so some + /// bytes might already be read from the stream when this is called, + /// these buffered bytes are returned in the form + /// + /// `(byte_buffer: Vec, buffer_capacity: usize, buffer_position: usize)` + pub fn into_stream(self) -> (S, Option<(Vec, usize, usize)>) { + let (stream, buf, pos, cap) = self.stream.into_parts(); + (stream, Some((buf, pos, cap))) + } + + /// Returns an iterator over incoming messages. + /// + ///```no_run + ///# extern crate websocket; + ///# fn main() { + ///use websocket::{ClientBuilder, Message}; + /// + ///let mut client = ClientBuilder::new("ws://127.0.0.1:1234").unwrap() + /// .connect(None).unwrap(); + /// + ///for message in client.incoming_messages() { + /// let message: Message = message.unwrap(); + /// println!("Recv: {:?}", message); + ///} + ///# } + ///``` + /// + /// Note that since this method mutably borrows the `Client`, it may be necessary to + /// first `split()` the `Client` and call `incoming_messages()` on the returned + /// `Receiver` to be able to send messages within an iteration. + /// + ///```no_run + ///# extern crate websocket; + ///# fn main() { + ///use websocket::{ClientBuilder, Message}; + /// + ///let mut client = ClientBuilder::new("ws://127.0.0.1:1234").unwrap() + /// .connect_insecure().unwrap(); + /// + ///let (mut receiver, mut sender) = client.split().unwrap(); + /// + ///for message in receiver.incoming_messages() { + /// let message: Message = message.unwrap(); + /// // Echo the message back + /// sender.send_message(&message).unwrap(); + ///} + ///# } + ///``` + pub fn incoming_messages<'a>(&'a mut self) -> MessageIterator<'a, Receiver, BufReader> { + self.receiver.incoming_messages(&mut self.stream) + } +} + +impl Client + where S: Splittable + Stream +{ + /// Split this client into its constituent Sender and Receiver pair. + /// + /// This allows the Sender and Receiver to be sent to different threads. + /// + ///```no_run + ///# extern crate websocket; + ///# fn main() { + ///use std::thread; + ///use websocket::{ClientBuilder, Message}; + /// + ///let mut client = ClientBuilder::new("ws://127.0.0.1:1234").unwrap() + /// .connect_insecure().unwrap(); + /// + ///let (mut receiver, mut sender) = client.split().unwrap(); + /// + ///thread::spawn(move || { + /// for message in receiver.incoming_messages() { + /// let message: Message = message.unwrap(); + /// println!("Recv: {:?}", message); + /// } + ///}); + /// + ///let message = Message::text("Hello, World!"); + ///sender.send_message(&message).unwrap(); + ///# } + ///``` + pub fn split + (self,) + -> IoResult<(Reader<::Reader>, Writer<::Writer>)> { + let (stream, buf, pos, cap) = self.stream.into_parts(); + let (read, write) = try!(stream.split()); + Ok((Reader { + stream: BufReader::from_parts(read, buf, pos, cap), + receiver: self.receiver, + }, + Writer { + stream: write, + sender: self.sender, + })) + } +} diff --git a/src/lib.rs b/src/lib.rs index 955bc73143..08bf0ff4f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,7 +43,7 @@ extern crate rand; extern crate byteorder; extern crate sha1; extern crate base64; -#[cfg(any(feature="ssl", feature="async-ssl"))] +#[cfg(any(feature="sync-ssl", feature="async-ssl"))] extern crate native_tls; #[cfg(feature="async")] extern crate tokio_core; @@ -62,13 +62,6 @@ extern crate bitflags; #[cfg(all(feature = "nightly", test))] extern crate test; -pub use self::client::{Client, ClientBuilder}; -pub use self::dataframe::DataFrame; -pub use self::message::{Message, OwnedMessage}; -pub use self::stream::Stream; -pub use self::ws::Sender; -pub use self::ws::Receiver; - macro_rules! upsert_header { ($headers:expr; $header:ty; { Some($pat:pat) => $some_match:expr, @@ -84,16 +77,76 @@ macro_rules! upsert_header { }} } - pub mod ws; -pub mod client; -pub mod server; pub mod dataframe; pub mod message; pub mod result; -pub mod stream; pub mod header; + +#[cfg(feature="async")] +pub mod codec; + +#[cfg(feature="sync")] pub mod receiver; +#[cfg(feature="sync")] pub mod sender; + +pub mod client; +pub mod server; +pub mod stream; + +#[cfg(feature="sync")] +pub mod sync { + pub use sender; + pub use sender::Writer; + + pub use receiver; + pub use receiver::Reader; + + pub use stream::sync::Stream; + pub use stream::sync as stream; + + pub mod server { + pub use server::sync::*; + pub use server::upgrade::sync::Upgrade; + pub use server::upgrade::sync::IntoWs; + pub use server::upgrade::sync as upgrade; + } + + pub mod client { + pub use client::sync::*; + pub use client::builder::ClientBuilder; + } +} + #[cfg(feature="async")] -pub mod codec; +pub mod async { + pub use codec; + pub use codec::ws::MessageCodec; + pub use codec::ws::Context as MessageContext; + pub use codec::http::HttpClientCodec; + pub use codec::http::HttpServerCodec; + + pub use stream::async::Stream; + pub use stream::async as stream; + + pub mod server { + pub use server::async::*; + pub use server::upgrade::async::Upgrade; + pub use server::upgrade::async::IntoWs; + pub use server::upgrade::async as upgrade; + } + + pub mod client { + pub use client::async::*; + pub use client::builder::ClientBuilder; + } +} + +pub use self::message::Message; +pub use self::message::CloseData; +pub use self::message::OwnedMessage; + +pub use self::result::WebSocketError; +pub use self::result::WebSocketResult; + diff --git a/src/receiver.rs b/src/receiver.rs index 98df5c909a..dc75589c6b 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -11,8 +11,8 @@ use ws; use ws::receiver::Receiver as ReceiverTrait; use ws::receiver::{MessageIterator, DataFrameIterator}; use message::OwnedMessage; -use stream::{AsTcpStream, Stream}; -pub use stream::Shutdown; +use stream::sync::{AsTcpStream, Stream}; +pub use stream::sync::Shutdown; /// This reader bundles an existing stream with a parsing algorithm. /// It is used by the client in its `.split()` function as the reading component. diff --git a/src/result.rs b/src/result.rs index b5f7e4b9f3..9b588dce48 100644 --- a/src/result.rs +++ b/src/result.rs @@ -8,9 +8,9 @@ use std::fmt; use hyper::Error as HttpError; use url::ParseError; -#[cfg(any(feature="ssl", feature="async-ssl"))] +#[cfg(any(feature="sync-ssl", feature="async-ssl"))] use native_tls::Error as TlsError; -#[cfg(any(feature="ssl", feature="async-ssl"))] +#[cfg(any(feature="sync-ssl", feature="async-ssl"))] use native_tls::HandshakeError as TlsHandshakeError; /// The type used for WebSocket results @@ -38,13 +38,13 @@ pub enum WebSocketError { /// A WebSocket URL error WebSocketUrlError(WSUrlErrorKind), /// An SSL error - #[cfg(any(feature="ssl", feature="async-ssl"))] + #[cfg(any(feature="sync-ssl", feature="async-ssl"))] TlsError(TlsError), /// an ssl handshake failure - #[cfg(any(feature="ssl", feature="async-ssl"))] + #[cfg(any(feature="sync-ssl", feature="async-ssl"))] TlsHandshakeFailure, /// an ssl handshake interruption - #[cfg(any(feature="ssl", feature="async-ssl"))] + #[cfg(any(feature="sync-ssl", feature="async-ssl"))] TlsHandshakeInterruption, /// A UTF-8 error Utf8Error(Utf8Error), @@ -69,11 +69,11 @@ impl Error for WebSocketError { WebSocketError::IoError(_) => "I/O failure", WebSocketError::HttpError(_) => "HTTP failure", WebSocketError::UrlError(_) => "URL failure", - #[cfg(any(feature="ssl", feature="async-ssl"))] + #[cfg(any(feature="sync-ssl", feature="async-ssl"))] WebSocketError::TlsError(_) => "TLS failure", - #[cfg(any(feature="ssl", feature="async-ssl"))] + #[cfg(any(feature="sync-ssl", feature="async-ssl"))] WebSocketError::TlsHandshakeFailure => "TLS Handshake failure", - #[cfg(any(feature="ssl", feature="async-ssl"))] + #[cfg(any(feature="sync-ssl", feature="async-ssl"))] WebSocketError::TlsHandshakeInterruption => "TLS Handshake interrupted", WebSocketError::Utf8Error(_) => "UTF-8 failure", WebSocketError::WebSocketUrlError(_) => "WebSocket URL failure", @@ -85,7 +85,7 @@ impl Error for WebSocketError { WebSocketError::IoError(ref error) => Some(error), WebSocketError::HttpError(ref error) => Some(error), WebSocketError::UrlError(ref error) => Some(error), - #[cfg(any(feature="ssl", feature="async-ssl"))] + #[cfg(any(feature="sync-ssl", feature="async-ssl"))] WebSocketError::TlsError(ref error) => Some(error), WebSocketError::Utf8Error(ref error) => Some(error), WebSocketError::WebSocketUrlError(ref error) => Some(error), @@ -115,14 +115,14 @@ impl From for WebSocketError { } } -#[cfg(any(feature="ssl", feature="async-ssl"))] +#[cfg(any(feature="sync-ssl", feature="async-ssl"))] impl From for WebSocketError { fn from(err: TlsError) -> WebSocketError { WebSocketError::TlsError(err) } } -#[cfg(any(feature="ssl", feature="async-ssl"))] +#[cfg(any(feature="sync-ssl", feature="async-ssl"))] impl From> for WebSocketError { fn from(err: TlsHandshakeError) -> WebSocketError { match err { diff --git a/src/sender.rs b/src/sender.rs index 72a5e9526d..3c6f514d55 100644 --- a/src/sender.rs +++ b/src/sender.rs @@ -4,10 +4,10 @@ use std::io::Write; use std::io::Result as IoResult; use result::WebSocketResult; use ws::dataframe::DataFrame; -use stream::AsTcpStream; +use stream::sync::AsTcpStream; use ws; use ws::sender::Sender as SenderTrait; -pub use stream::Shutdown; +pub use stream::sync::Shutdown; /// A writer that bundles a stream with a serializer to send the messages. /// This is used in the client's `.split()` function as the writing component. diff --git a/src/server/async.rs b/src/server/async.rs index 05b85c29fc..5351acc849 100644 --- a/src/server/async.rs +++ b/src/server/async.rs @@ -2,12 +2,14 @@ use std::io; use std::net::SocketAddr; use server::{WsServer, NoTlsAcceptor}; use tokio_core::net::TcpListener; -use native_tls::{TlsStream, TlsAcceptor}; pub use tokio_core::reactor::Handle; +#[cfg(any(feature="async-ssl"))] +use native_tls::{TlsStream, TlsAcceptor}; + pub type Server = WsServer; -impl Server { +impl WsServer { /// Bind this Server to this socket pub fn bind(addr: &SocketAddr, handle: &Handle) -> io::Result { Ok(Server { @@ -22,17 +24,22 @@ impl Server { } } -impl Server { - /// Bind this Server to this socket - pub fn bind_secure(addr: &SocketAddr, acceptor: TlsAcceptor, handle: &Handle) -> io::Result { - Ok(Server { - listener: TcpListener::bind(addr, handle)?, - ssl_acceptor: acceptor, - }) - } +#[cfg(any(feature="async-ssl"))] +impl WsServer { + /// Bind this Server to this socket + pub fn bind_secure( + addr: &SocketAddr, + acceptor: TlsAcceptor, + handle: &Handle, + ) -> io::Result { + Ok(Server { + listener: TcpListener::bind(addr, handle)?, + ssl_acceptor: acceptor, + }) + } - /// Wait for and accept an incoming WebSocket connection, returning a WebSocketRequest - pub fn incoming(&mut self) { - unimplemented!(); - } + /// Wait for and accept an incoming WebSocket connection, returning a WebSocketRequest + pub fn incoming(&mut self) { + unimplemented!(); + } } diff --git a/src/server/mod.rs b/src/server/mod.rs index 7c23899e25..b04daee0e1 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,13 +1,13 @@ //! Provides an implementation of a WebSocket server -#[cfg(feature="ssl")] +#[cfg(any(feature="sync-ssl", feature="async-ssl"))] use native_tls::TlsAcceptor; -pub use self::upgrade::{Request, HyperIntoWsError}; pub mod upgrade; #[cfg(feature="async")] pub mod async; +#[cfg(feature="sync")] pub mod sync; /// Marker struct for a struct not being secure @@ -19,7 +19,7 @@ pub struct NoTlsAcceptor; /// is running over SSL or not. pub trait OptionalTlsAcceptor {} impl OptionalTlsAcceptor for NoTlsAcceptor {} -#[cfg(feature="ssl")] +#[cfg(any(feature="sync-ssl", feature="async-ssl"))] impl OptionalTlsAcceptor for TlsAcceptor {} pub struct WsServer diff --git a/src/server/sync.rs b/src/server/sync.rs index 06a9a0e7d9..8199b5049b 100644 --- a/src/server/sync.rs +++ b/src/server/sync.rs @@ -2,11 +2,11 @@ use std::net::{SocketAddr, ToSocketAddrs, TcpListener, TcpStream}; use std::io; use std::convert::Into; -#[cfg(feature="ssl")] +#[cfg(feature="sync-ssl")] use native_tls::{TlsStream, TlsAcceptor}; use stream::Stream; use server::{WsServer, OptionalTlsAcceptor, NoTlsAcceptor}; -use server::upgrade::{SyncWsUpgrade, IntoWs, Buffer}; +use server::upgrade::sync::{Upgrade, IntoWs, Buffer}; pub use server::upgrade::{Request, HyperIntoWsError}; #[cfg(feature="async")] @@ -41,7 +41,7 @@ pub struct InvalidConnection /// Either the stream was established and it sent a websocket handshake /// which represents the `Ok` variant, or there was an error (this is the /// `Err` variant). -pub type AcceptResult = Result, InvalidConnection>; +pub type AcceptResult = Result, InvalidConnection>; /// Represents a WebSocket server which can work with either normal /// (non-secure) connections, or secure WebSocket connections. @@ -129,7 +129,7 @@ pub type AcceptResult = Result, InvalidConnection>; /// check out the docs over at `websocket::server::upgrade` for more. pub type Server = WsServer; -impl Server +impl WsServer where S: OptionalTlsAcceptor { /// Get the socket address of this server @@ -188,8 +188,8 @@ impl Server } } -#[cfg(feature="ssl")] -impl Server { +#[cfg(feature="sync-ssl")] +impl WsServer { /// Bind this Server to this socket, utilising the given SslContext pub fn bind_secure(addr: A, acceptor: TlsAcceptor) -> io::Result where A: ToSocketAddrs @@ -240,8 +240,8 @@ impl Server { } } -#[cfg(feature="ssl")] -impl Iterator for Server { +#[cfg(feature="sync-ssl")] +impl Iterator for WsServer { type Item = AcceptResult>; fn next(&mut self) -> Option<::Item> { @@ -249,7 +249,7 @@ impl Iterator for Server { } } -impl Server { +impl WsServer { /// Bind this Server to this socket pub fn bind(addr: A) -> io::Result { Ok(Server { @@ -295,7 +295,7 @@ impl Server { } } -impl Iterator for Server { +impl Iterator for WsServer { type Item = AcceptResult; fn next(&mut self) -> Option<::Item> { diff --git a/src/server/upgrade/async.rs b/src/server/upgrade/async.rs index 86f0a38dd0..424156b9d1 100644 --- a/src/server/upgrade/async.rs +++ b/src/server/upgrade/async.rs @@ -4,28 +4,30 @@ use tokio_io::codec::{Framed, FramedParts}; use hyper::header::Headers; use hyper::http::h1::Incoming; use hyper::status::StatusCode; -use stream::AsyncStream; -use futures::{Stream, Sink, Future}; +use stream::async::Stream; +use futures::{Sink, Future}; +use futures::Stream as StreamTrait; use futures::sink::Send; use codec::http::HttpServerCodec; use codec::ws::{MessageCodec, Context}; use bytes::BytesMut; use client::async::ClientNew; -pub type AsyncWsUpgrade = WsUpgrade; +pub type Upgrade = WsUpgrade; -impl AsyncWsUpgrade - where S: AsyncStream + 'static +/// TODO: docs, THIS IS THTE ASYNC ONE +impl WsUpgrade + where S: Stream + 'static { - pub fn async_accept(self) -> ClientNew { - self.internal_async_accept(None) + pub fn accept(self) -> ClientNew { + self.internal_accept(None) } - pub fn async_accept_with(self, custom_headers: &Headers) -> ClientNew { - self.internal_async_accept(Some(custom_headers)) + pub fn accept_with(self, custom_headers: &Headers) -> ClientNew { + self.internal_accept(Some(custom_headers)) } - fn internal_async_accept(mut self, custom_headers: Option<&Headers>) -> ClientNew { + fn internal_accept(mut self, custom_headers: Option<&Headers>) -> ClientNew { let status = self.prepare_headers(custom_headers); let WsUpgrade { headers, stream, request, buffer } = self; @@ -50,15 +52,15 @@ impl AsyncWsUpgrade Box::new(future) } - pub fn async_reject(self) -> Send> { - self.internal_async_reject(None) + pub fn reject(self) -> Send> { + self.internal_reject(None) } - pub fn async_reject_with(self, headers: &Headers) -> Send> { - self.internal_async_reject(Some(headers)) + pub fn reject_with(self, headers: &Headers) -> Send> { + self.internal_reject(Some(headers)) } - fn internal_async_reject( + fn internal_reject( mut self, headers: Option<&Headers>, ) -> Send> { @@ -80,9 +82,9 @@ impl AsyncWsUpgrade } -pub trait AsyncIntoWs { +pub trait IntoWs { /// The type of stream this upgrade process is working with (TcpStream, etc.) - type Stream: AsyncStream; + type Stream: Stream; /// An error value in case the stream is not asking for a websocket connection /// or something went wrong. It is common to also include the stream here. type Error; @@ -92,16 +94,16 @@ pub trait AsyncIntoWs { /// /// Note: this is the asynchronous version, meaning it will not block when /// trying to read a request. - fn into_ws(self) -> Box, Error = Self::Error>>; + fn into_ws(self) -> Box, Error = Self::Error>>; } -impl AsyncIntoWs for S - where S: AsyncStream + 'static +impl IntoWs for S + where S: Stream + 'static { type Stream = S; type Error = (S, Option, BytesMut, HyperIntoWsError); - fn into_ws(self) -> Box, Error = Self::Error>> { + fn into_ws(self) -> Box, Error = Self::Error>> { let future = self.framed(HttpServerCodec) .into_future() .map_err(|(e, s)| { diff --git a/src/server/upgrade/from_hyper.rs b/src/server/upgrade/from_hyper.rs deleted file mode 100644 index 342f296d69..0000000000 --- a/src/server/upgrade/from_hyper.rs +++ /dev/null @@ -1,88 +0,0 @@ -//! Upgrade a hyper connection to a websocket one. -//! -//! Using this method, one can start a hyper server and check if each request -//! is a websocket upgrade request, if so you can use websockets and hyper on the -//! same port! -//! -//! ```rust,no_run -//! # extern crate hyper; -//! # extern crate websocket; -//! # fn main() { -//! use hyper::server::{Server, Request, Response}; -//! use websocket::Message; -//! use websocket::server::upgrade::IntoWs; -//! use websocket::server::upgrade::from_hyper::HyperRequest; -//! -//! Server::http("0.0.0.0:80").unwrap().handle(move |req: Request, res: Response| { -//! match HyperRequest(req).into_ws() { -//! Ok(upgrade) => { -//! // `accept` sends a successful handshake, no need to worry about res -//! let mut client = match upgrade.accept() { -//! Ok(c) => c, -//! Err(_) => panic!(), -//! }; -//! -//! client.send_message(&Message::text("its free real estate")); -//! }, -//! -//! Err((request, err)) => { -//! // continue using the request as normal, "echo uri" -//! res.send(b"Try connecting over ws instead.").unwrap(); -//! }, -//! }; -//! }) -//! .unwrap(); -//! # } -//! ``` - -use hyper::net::NetworkStream; -use super::{IntoWs, SyncWsUpgrade, Buffer}; - -pub use hyper::http::h1::Incoming; -pub use hyper::method::Method; -pub use hyper::version::HttpVersion; -pub use hyper::uri::RequestUri; -pub use hyper::buffer::BufReader; -use hyper::server::Request; -pub use hyper::header::{Headers, Upgrade, ProtocolName, Connection, ConnectionOption}; - -use super::validate; -use super::HyperIntoWsError; - -/// A hyper request is implicitly defined as a stream from other `impl`s of Stream. -/// Until trait impl specialization comes along, we use this struct to differentiate -/// a hyper request (which already has parsed headers) from a normal stream. -pub struct HyperRequest<'a, 'b: 'a>(pub Request<'a, 'b>); - -impl<'a, 'b> IntoWs for HyperRequest<'a, 'b> { - type Stream = &'a mut &'b mut NetworkStream; - type Error = (Request<'a, 'b>, HyperIntoWsError); - - fn into_ws(self) -> Result, Self::Error> { - if let Err(e) = validate(&self.0.method, &self.0.version, &self.0.headers) { - return Err((self.0, e)); - } - - let (_, method, headers, uri, version, reader) = - self.0.deconstruct(); - - let reader = reader.into_inner(); - let (buf, pos, cap) = reader.take_buf(); - let stream = reader.get_mut(); - - Ok(SyncWsUpgrade { - headers: Headers::new(), - stream: stream, - buffer: Some(Buffer { - buf: buf, - pos: pos, - cap: cap, - }), - request: Incoming { - version: version, - headers: headers, - subject: (method, uri), - }, - }) - } -} diff --git a/src/server/upgrade/mod.rs b/src/server/upgrade/mod.rs index cc024646ae..7018c471bd 100644 --- a/src/server/upgrade/mod.rs +++ b/src/server/upgrade/mod.rs @@ -3,14 +3,11 @@ use std::error::Error; use std::net::TcpStream; use std::io; -use std::io::Result as IoResult; -use std::io::Error as IoError; use std::fmt::{self, Formatter, Display}; -use stream::{Stream, AsTcpStream}; +use stream::Stream; use header::extensions::Extension; use header::{WebSocketAccept, WebSocketKey, WebSocketVersion, WebSocketProtocol, WebSocketExtensions, Origin}; -use client::Client; use unicase::UniCase; use hyper::status::StatusCode; @@ -18,42 +15,17 @@ use hyper::http::h1::Incoming; use hyper::method::Method; use hyper::version::HttpVersion; use hyper::uri::RequestUri; -use hyper::buffer::BufReader; -use hyper::http::h1::parse_request; use hyper::header::{Headers, Upgrade, Protocol, ProtocolName, Connection, ConnectionOption}; -pub mod from_hyper; - #[cfg(feature="async")] pub mod async; -/// This crate uses buffered readers to read in the handshake quickly, in order to -/// interface with other use cases that don't use buffered readers the buffered readers -/// is deconstructed when it is returned to the user and given as the underlying -/// reader and the buffer. -/// -/// This struct represents bytes that have already been read in from the stream. -/// A slice of valid data in this buffer can be obtained by: `&buf[pos..cap]`. -#[derive(Debug)] -pub struct Buffer { - /// the contents of the buffered stream data - pub buf: Vec, - /// the current position of cursor in the buffer - /// Any data before `pos` has already been read and parsed. - pub pos: usize, - /// the last location of valid data - /// Any data after `cap` is not valid. - pub cap: usize, -} +#[cfg(feature="sync")] +pub mod sync; /// A typical request from hyper pub type Request = Incoming<(Method, RequestUri)>; -/// If you have your requests separate from your stream you can use this struct -/// to upgrade the connection based on the request given -/// (the request should be a handshake). -pub struct RequestStreamPair(pub S, pub Request); - /// Intermediate representation of a half created websocket session. /// Should be used to examine the client's handshake /// accept the protocols requested, route the path, etc. @@ -73,8 +45,6 @@ pub struct WsUpgrade pub buffer: B, } -pub type SyncWsUpgrade = WsUpgrade>; - impl WsUpgrade where S: Stream { @@ -111,27 +81,6 @@ impl WsUpgrade self } - /// Reject the client's request to make a websocket connection. - pub fn reject(self) -> Result { - self.internal_reject(None) - } - - /// Reject the client's request to make a websocket connection - /// and send extra headers. - pub fn reject_with(self, headers: &Headers) -> Result { - self.internal_reject(Some(headers)) - } - - fn internal_reject(mut self, headers: Option<&Headers>) -> Result { - if let Some(custom) = headers { - self.headers.extend(custom.iter()); - } - match self.send(StatusCode::BadRequest) { - Ok(()) => Ok(self.stream), - Err(e) => Err((self.stream, e)), - } - } - /// Drop the connection without saying anything. pub fn drop(self) { ::std::mem::drop(self); @@ -170,7 +119,7 @@ impl WsUpgrade self.request.headers.get::().map(|o| &o.0 as &str) } - fn send(&mut self, status: StatusCode) -> IoResult<()> { + fn send(&mut self, status: StatusCode) -> io::Result<()> { try!(write!(&mut self.stream, "{} {}\r\n", self.request.version, status)); try!(write!(&mut self.stream, "{}\r\n", self.headers)); Ok(()) @@ -195,151 +144,6 @@ impl WsUpgrade } } -impl SyncWsUpgrade - where S: Stream -{ - /// Accept the handshake request and send a response, - /// if nothing goes wrong a client will be created. - pub fn accept(self) -> Result, (S, IoError)> { - self.internal_accept(None) - } - - /// Accept the handshake request and send a response while - /// adding on a few headers. These headers are added before the required - /// headers are, so some might be overwritten. - pub fn accept_with(self, custom_headers: &Headers) -> Result, (S, IoError)> { - self.internal_accept(Some(custom_headers)) - } - - fn internal_accept(mut self, headers: Option<&Headers>) -> Result, (S, IoError)> { - let status = self.prepare_headers(headers); - - if let Err(e) = self.send(status) { - return Err((self.stream, e)); - } - - let stream = match self.buffer { - Some(Buffer { buf, pos, cap }) => BufReader::from_parts(self.stream, buf, pos, cap), - None => BufReader::new(self.stream), - }; - - Ok(Client::unchecked(stream, self.headers, false, true)) - } -} - -impl WsUpgrade - where S: Stream + AsTcpStream -{ - /// Get a handle to the underlying TCP stream, useful to be able to set - /// TCP options, etc. - pub fn tcp_stream(&self) -> &TcpStream { - self.stream.as_tcp() - } -} - -/// Trait to take a stream or similar and attempt to recover the start of a -/// websocket handshake from it. -/// Should be used when a stream might contain a request for a websocket session. -/// -/// If an upgrade request can be parsed, one can accept or deny the handshake with -/// the `WsUpgrade` struct. -/// Otherwise the original stream is returned along with an error. -/// -/// Note: the stream is owned because the websocket client expects to own its stream. -/// -/// This is already implemented for all Streams, which means all types with Read + Write. -/// -/// # Example -/// -/// ```rust,no_run -/// use std::net::TcpListener; -/// use std::net::TcpStream; -/// use websocket::server::upgrade::IntoWs; -/// use websocket::Client; -/// -/// let listener = TcpListener::bind("127.0.0.1:80").unwrap(); -/// -/// for stream in listener.incoming().filter_map(Result::ok) { -/// let mut client: Client = match stream.into_ws() { -/// Ok(upgrade) => { -/// match upgrade.accept() { -/// Ok(client) => client, -/// Err(_) => panic!(), -/// } -/// }, -/// Err(_) => panic!(), -/// }; -/// } -/// ``` -pub trait IntoWs { - /// The type of stream this upgrade process is working with (TcpStream, etc.) - type Stream: Stream; - /// An error value in case the stream is not asking for a websocket connection - /// or something went wrong. It is common to also include the stream here. - type Error; - /// Attempt to parse the start of a Websocket handshake, later with the returned - /// `WsUpgrade` struct, call `accept to start a websocket client, and `reject` to - /// send a handshake rejection response. - fn into_ws(self) -> Result, Self::Error>; -} - -impl IntoWs for S - where S: Stream -{ - type Stream = S; - type Error = (S, Option, Option, HyperIntoWsError); - - fn into_ws(self) -> Result, Self::Error> { - let mut reader = BufReader::new(self); - let request = parse_request(&mut reader); - - let (stream, buf, pos, cap) = reader.into_parts(); - let buffer = Some(Buffer { - buf: buf, - cap: cap, - pos: pos, - }); - - let request = match request { - Ok(r) => r, - Err(e) => return Err((stream, None, buffer, e.into())), - }; - - match validate(&request.subject.0, &request.version, &request.headers) { - Ok(_) => { - Ok(WsUpgrade { - headers: Headers::new(), - stream: stream, - request: request, - buffer: buffer, - }) - } - Err(e) => Err((stream, Some(request), buffer, e)), - } - } -} - -impl IntoWs for RequestStreamPair - where S: Stream -{ - type Stream = S; - type Error = (S, Request, HyperIntoWsError); - - fn into_ws(self) -> Result, Self::Error> { - match validate(&self.1.subject.0, &self.1.version, &self.1.headers) { - Ok(_) => { - Ok(WsUpgrade { - headers: Headers::new(), - stream: self.0, - request: self.1, - buffer: None, - }) - } - Err(e) => Err((self.0, self.1, e)), - } - } -} - /// Errors that can occur when one tries to upgrade a connection to a /// websocket connection. #[derive(Debug)] diff --git a/src/server/upgrade/sync.rs b/src/server/upgrade/sync.rs new file mode 100644 index 0000000000..095c61eb79 --- /dev/null +++ b/src/server/upgrade/sync.rs @@ -0,0 +1,281 @@ +//! Allows you to take an existing request or stream of data and convert it into a +//! WebSocket client. +use std::io; +use std::net::TcpStream; +use stream::sync::{Stream, AsTcpStream}; +use server::upgrade::{Request, WsUpgrade, HyperIntoWsError, validate}; +use client::sync::Client; + +use hyper::status::StatusCode; +use hyper::http::h1::Incoming; +use hyper::buffer::BufReader; +use hyper::http::h1::parse_request; +use hyper::header::Headers; +use hyper::net::NetworkStream; + +/// This crate uses buffered readers to read in the handshake quickly, in order to +/// interface with other use cases that don't use buffered readers the buffered readers +/// is deconstructed when it is returned to the user and given as the underlying +/// reader and the buffer. +/// +/// This struct represents bytes that have already been read in from the stream. +/// A slice of valid data in this buffer can be obtained by: `&buf[pos..cap]`. +#[derive(Debug)] +pub struct Buffer { + /// the contents of the buffered stream data + pub buf: Vec, + /// the current position of cursor in the buffer + /// Any data before `pos` has already been read and parsed. + pub pos: usize, + /// the last location of valid data + /// Any data after `cap` is not valid. + pub cap: usize, +} + +/// If you have your requests separate from your stream you can use this struct +/// to upgrade the connection based on the request given +/// (the request should be a handshake). +pub struct RequestStreamPair(pub S, pub Request); + +pub type Upgrade = WsUpgrade>; + +impl WsUpgrade> + where S: Stream +{ + /// Accept the handshake request and send a response, + /// if nothing goes wrong a client will be created. + pub fn accept(self) -> Result, (S, io::Error)> { + self.internal_accept(None) + } + + /// Accept the handshake request and send a response while + /// adding on a few headers. These headers are added before the required + /// headers are, so some might be overwritten. + pub fn accept_with(self, custom_headers: &Headers) -> Result, (S, io::Error)> { + self.internal_accept(Some(custom_headers)) + } + + fn internal_accept(mut self, headers: Option<&Headers>) -> Result, (S, io::Error)> { + let status = self.prepare_headers(headers); + + if let Err(e) = self.send(status) { + return Err((self.stream, e)); + } + + let stream = match self.buffer { + Some(Buffer { buf, pos, cap }) => BufReader::from_parts(self.stream, buf, pos, cap), + None => BufReader::new(self.stream), + }; + + Ok(Client::unchecked(stream, self.headers, false, true)) + } + + /// Reject the client's request to make a websocket connection. + pub fn reject(self) -> Result { + self.internal_reject(None) + } + + /// Reject the client's request to make a websocket connection + /// and send extra headers. + pub fn reject_with(self, headers: &Headers) -> Result { + self.internal_reject(Some(headers)) + } + + fn internal_reject(mut self, headers: Option<&Headers>) -> Result { + if let Some(custom) = headers { + self.headers.extend(custom.iter()); + } + match self.send(StatusCode::BadRequest) { + Ok(()) => Ok(self.stream), + Err(e) => Err((self.stream, e)), + } + } +} + +impl WsUpgrade + where S: Stream + AsTcpStream +{ + /// Get a handle to the underlying TCP stream, useful to be able to set + /// TCP options, etc. + pub fn tcp_stream(&self) -> &TcpStream { + self.stream.as_tcp() + } +} + +/// Trait to take a stream or similar and attempt to recover the start of a +/// websocket handshake from it. +/// Should be used when a stream might contain a request for a websocket session. +/// +/// If an upgrade request can be parsed, one can accept or deny the handshake with +/// the `WsUpgrade` struct. +/// Otherwise the original stream is returned along with an error. +/// +/// Note: the stream is owned because the websocket client expects to own its stream. +/// +/// This is already implemented for all Streams, which means all types with Read + Write. +/// +/// # Example +/// +/// ```rust,no_run +/// use std::net::TcpListener; +/// use std::net::TcpStream; +/// use websocket::server::upgrade::IntoWs; +/// use websocket::Client; +/// +/// let listener = TcpListener::bind("127.0.0.1:80").unwrap(); +/// +/// for stream in listener.incoming().filter_map(Result::ok) { +/// let mut client: Client = match stream.into_ws() { +/// Ok(upgrade) => { +/// match upgrade.accept() { +/// Ok(client) => client, +/// Err(_) => panic!(), +/// } +/// }, +/// Err(_) => panic!(), +/// }; +/// } +/// ``` +pub trait IntoWs { + /// The type of stream this upgrade process is working with (TcpStream, etc.) + type Stream: Stream; + /// An error value in case the stream is not asking for a websocket connection + /// or something went wrong. It is common to also include the stream here. + type Error; + /// Attempt to parse the start of a Websocket handshake, later with the returned + /// `WsUpgrade` struct, call `accept to start a websocket client, and `reject` to + /// send a handshake rejection response. + fn into_ws(self) -> Result, Self::Error>; +} + +impl IntoWs for S + where S: Stream +{ + type Stream = S; + type Error = (S, Option, Option, HyperIntoWsError); + + fn into_ws(self) -> Result, Self::Error> { + let mut reader = BufReader::new(self); + let request = parse_request(&mut reader); + + let (stream, buf, pos, cap) = reader.into_parts(); + let buffer = Some(Buffer { + buf: buf, + cap: cap, + pos: pos, + }); + + let request = match request { + Ok(r) => r, + Err(e) => return Err((stream, None, buffer, e.into())), + }; + + match validate(&request.subject.0, &request.version, &request.headers) { + Ok(_) => { + Ok(WsUpgrade { + headers: Headers::new(), + stream: stream, + request: request, + buffer: buffer, + }) + } + Err(e) => Err((stream, Some(request), buffer, e)), + } + } +} + +impl IntoWs for RequestStreamPair + where S: Stream +{ + type Stream = S; + type Error = (S, Request, HyperIntoWsError); + + fn into_ws(self) -> Result, Self::Error> { + match validate(&self.1.subject.0, &self.1.version, &self.1.headers) { + Ok(_) => { + Ok(WsUpgrade { + headers: Headers::new(), + stream: self.0, + request: self.1, + buffer: None, + }) + } + Err(e) => Err((self.0, self.1, e)), + } + } +} + +/// Upgrade a hyper connection to a websocket one. +/// +/// A hyper request is implicitly defined as a stream from other `impl`s of Stream. +/// Until trait impl specialization comes along, we use this struct to differentiate +/// a hyper request (which already has parsed headers) from a normal stream. +/// +/// Using this method, one can start a hyper server and check if each request +/// is a websocket upgrade request, if so you can use websockets and hyper on the +/// same port! +/// +/// ```rust,no_run +/// # extern crate hyper; +/// # extern crate websocket; +/// # fn main() { +/// use hyper::server::{Server, Request, Response}; +/// use websocket::Message; +/// use websocket::server::upgrade::IntoWs; +/// use websocket::server::upgrade::from_hyper::HyperRequest; +/// +/// Server::http("0.0.0.0:80").unwrap().handle(move |req: Request, res: Response| { +/// match HyperRequest(req).into_ws() { +/// Ok(upgrade) => { +/// // `accept` sends a successful handshake, no need to worry about res +/// let mut client = match upgrade.accept() { +/// Ok(c) => c, +/// Err(_) => panic!(), +/// }; +/// +/// client.send_message(&Message::text("its free real estate")); +/// }, +/// +/// Err((request, err)) => { +/// // continue using the request as normal, "echo uri" +/// res.send(b"Try connecting over ws instead.").unwrap(); +/// }, +/// }; +/// }) +/// .unwrap(); +/// # } +/// ``` +pub struct HyperRequest<'a, 'b: 'a>(pub ::hyper::server::Request<'a, 'b>); + +impl<'a, 'b> IntoWs for HyperRequest<'a, 'b> { + type Stream = &'a mut &'b mut NetworkStream; + type Error = (::hyper::server::Request<'a, 'b>, HyperIntoWsError); + + fn into_ws(self) -> Result, Self::Error> { + if let Err(e) = validate(&self.0.method, &self.0.version, &self.0.headers) { + return Err((self.0, e)); + } + + let (_, method, headers, uri, version, reader) = + self.0.deconstruct(); + + let reader = reader.into_inner(); + let (buf, pos, cap) = reader.take_buf(); + let stream = reader.get_mut(); + + Ok(Upgrade { + headers: Headers::new(), + stream: stream, + buffer: Some(Buffer { + buf: buf, + pos: pos, + cap: cap, + }), + request: Incoming { + version: version, + headers: headers, + subject: (method, uri), + }, + }) + } +} diff --git a/src/stream.rs b/src/stream.rs index 55d922b5f1..6437310d6a 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -1,16 +1,6 @@ //! Provides the default stream type for WebSocket connections. -use std::ops::Deref; -use std::fmt::Arguments; use std::io::{self, Read, Write}; -pub use std::net::TcpStream; -pub use std::net::Shutdown; -#[cfg(feature="ssl")] -pub use native_tls::TlsStream; -#[cfg(feature="async")] -pub use tokio_io::{AsyncWrite, AsyncRead}; -#[cfg(feature="async")] -pub use tokio_io::io::{ReadHalf, WriteHalf}; /// Represents a stream that can be read from, and written to. /// This is an abstraction around readable and writable things to be able @@ -18,131 +8,149 @@ pub use tokio_io::io::{ReadHalf, WriteHalf}; pub trait Stream: Read + Write {} impl Stream for S where S: Read + Write {} -/// TODO: docs -#[cfg(feature="async")] -pub trait AsyncStream: AsyncRead + AsyncWrite {} #[cfg(feature="async")] -impl AsyncStream for S where S: AsyncRead + AsyncWrite {} - -/// a `Stream` that can also be used as a borrow to a `TcpStream` -/// this is useful when you want to set `TcpStream` options on a -/// `Stream` like `nonblocking`. -pub trait NetworkStream: Read + Write + AsTcpStream {} - -impl NetworkStream for S where S: Read + Write + AsTcpStream {} - -/// some streams can be split up into separate reading and writing components -/// `TcpStream` is an example. This trait marks this ability so one can split -/// up the client into two parts. -/// -/// Notice however that this is not possible to do with SSL. -pub trait Splittable { - /// The reading component of this type - type Reader: Read; - /// The writing component of this type - type Writer: Write; - - /// Split apart this type into a reading and writing component. - fn split(self) -> io::Result<(Self::Reader, Self::Writer)>; -} - -impl Splittable for ReadWritePair - where R: Read, - W: Write -{ - type Reader = R; - type Writer = W; - - fn split(self) -> io::Result<(R, W)> { - Ok((self.0, self.1)) - } -} - -impl Splittable for TcpStream { - type Reader = TcpStream; - type Writer = TcpStream; - - fn split(self) -> io::Result<(TcpStream, TcpStream)> { - self.try_clone().map(|s| (s, self)) - } -} - -/// The ability access a borrow to an underlying TcpStream, -/// so one can set options on the stream such as `nonblocking`. -pub trait AsTcpStream { - /// Get a borrow of the TcpStream - fn as_tcp(&self) -> &TcpStream; -} - -impl AsTcpStream for TcpStream { - fn as_tcp(&self) -> &TcpStream { - self - } -} - -#[cfg(feature="ssl")] -impl AsTcpStream for TlsStream { - fn as_tcp(&self) -> &TcpStream { - self.get_ref() - } -} - -impl AsTcpStream for Box - where T: AsTcpStream -{ - fn as_tcp(&self) -> &TcpStream { - self.deref().as_tcp() - } -} - -/// If you would like to combine an input stream and an output stream into a single -/// stream to talk websockets over then this is the struct for you! -/// -/// This is useful if you want to use different mediums for different directions. -pub struct ReadWritePair(pub R, pub W) - where R: Read, - W: Write; - -impl Read for ReadWritePair - where R: Read, - W: Write -{ - #[inline(always)] - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.0.read(buf) - } - #[inline(always)] - fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { - self.0.read_to_end(buf) - } - #[inline(always)] - fn read_to_string(&mut self, buf: &mut String) -> io::Result { - self.0.read_to_string(buf) - } - #[inline(always)] - fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { - self.0.read_exact(buf) - } +pub mod async { + pub use tokio_core::net::TcpStream; + pub use tokio_io::{AsyncWrite, AsyncRead}; + pub use tokio_io::io::{ReadHalf, WriteHalf}; + + pub trait Stream: AsyncRead + AsyncWrite {} + impl Stream for S where S: AsyncRead + AsyncWrite {} + // TODO: implement for a pair of async read/write? } -impl Write for ReadWritePair - where R: Read, - W: Write -{ - #[inline(always)] - fn write(&mut self, buf: &[u8]) -> io::Result { - self.1.write(buf) - } - #[inline(always)] - fn flush(&mut self) -> io::Result<()> { - self.1.flush() - } - #[inline(always)] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - self.1.write_all(buf) - } - #[inline(always)] - fn write_fmt(&mut self, fmt: Arguments) -> io::Result<()> { - self.1.write_fmt(fmt) - } +#[cfg(feature="sync")] +pub mod sync { + use std::io::{self, Read, Write}; + use std::ops::Deref; + use std::fmt::Arguments; + pub use std::net::TcpStream; + pub use std::net::Shutdown; + #[cfg(feature="sync-ssl")] + pub use native_tls::TlsStream; + + pub use super::Stream; + + /// a `Stream` that can also be used as a borrow to a `TcpStream` + /// this is useful when you want to set `TcpStream` options on a + /// `Stream` like `nonblocking`. + pub trait NetworkStream: Read + Write + AsTcpStream {} + + impl NetworkStream for S where S: Read + Write + AsTcpStream {} + + /// some streams can be split up into separate reading and writing components + /// `TcpStream` is an example. This trait marks this ability so one can split + /// up the client into two parts. + /// + /// Notice however that this is not possible to do with SSL. + pub trait Splittable { + /// The reading component of this type + type Reader: Read; + /// The writing component of this type + type Writer: Write; + + /// Split apart this type into a reading and writing component. + fn split(self) -> io::Result<(Self::Reader, Self::Writer)>; + } + + impl Splittable for ReadWritePair + where R: Read, + W: Write + { + type Reader = R; + type Writer = W; + + fn split(self) -> io::Result<(R, W)> { + Ok((self.0, self.1)) + } + } + + impl Splittable for TcpStream { + type Reader = TcpStream; + type Writer = TcpStream; + + fn split(self) -> io::Result<(TcpStream, TcpStream)> { + self.try_clone().map(|s| (s, self)) + } + } + + /// The ability access a borrow to an underlying TcpStream, + /// so one can set options on the stream such as `nonblocking`. + pub trait AsTcpStream { + /// Get a borrow of the TcpStream + fn as_tcp(&self) -> &TcpStream; + } + + impl AsTcpStream for TcpStream { + fn as_tcp(&self) -> &TcpStream { + self + } + } + + #[cfg(feature="sync-ssl")] + impl AsTcpStream for TlsStream { + fn as_tcp(&self) -> &TcpStream { + self.get_ref() + } + } + + impl AsTcpStream for Box + where T: AsTcpStream + { + fn as_tcp(&self) -> &TcpStream { + self.deref().as_tcp() + } + } + + /// If you would like to combine an input stream and an output stream into a single + /// stream to talk websockets over then this is the struct for you! + /// + /// This is useful if you want to use different mediums for different directions. + pub struct ReadWritePair(pub R, pub W) + where R: Read, + W: Write; + + impl Read for ReadWritePair + where R: Read, + W: Write + { + #[inline(always)] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.0.read(buf) + } + #[inline(always)] + fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { + self.0.read_to_end(buf) + } + #[inline(always)] + fn read_to_string(&mut self, buf: &mut String) -> io::Result { + self.0.read_to_string(buf) + } + #[inline(always)] + fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { + self.0.read_exact(buf) + } + } + + impl Write for ReadWritePair + where R: Read, + W: Write + { + #[inline(always)] + fn write(&mut self, buf: &[u8]) -> io::Result { + self.1.write(buf) + } + #[inline(always)] + fn flush(&mut self) -> io::Result<()> { + self.1.flush() + } + #[inline(always)] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + self.1.write_all(buf) + } + #[inline(always)] + fn write_fmt(&mut self, fmt: Arguments) -> io::Result<()> { + self.1.write_fmt(fmt) + } + } } diff --git a/src/ws/mod.rs b/src/ws/mod.rs index bb70489e27..dd3ee71361 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -38,12 +38,19 @@ //! To make life easier for a `Receiver`, several utility functions are provided which read //! various pieces of data from a Reader. These are found within the `util` module. pub use self::message::Message; + +#[cfg(feature="sync")] pub use self::sender::Sender; +#[cfg(feature="sync")] pub use self::receiver::Receiver; +#[cfg(feature="sync")] pub use self::receiver::{DataFrameIterator, MessageIterator}; pub mod message; -pub mod sender; -pub mod receiver; pub mod util; pub mod dataframe; + +#[cfg(feature="sync")] +pub mod sender; +#[cfg(feature="sync")] +pub mod receiver; From 80d6e4a8d7ac1273cfe511e113739d2911723de7 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Tue, 23 May 2017 21:56:44 -0400 Subject: [PATCH 36/52] Test all feature combos on travis, added build script. --- .travis.yml | 2 +- scripts/build-all.sh | 47 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100755 scripts/build-all.sh diff --git a/.travis.yml b/.travis.yml index 74c38f865d..fb83404629 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ before_script: script: - cargo fmt -- --write-mode=diff - - cargo build --features nightly + - ./scripts/build-all.sh - cargo test --features nightly - cargo bench --features nightly diff --git a/scripts/build-all.sh b/scripts/build-all.sh new file mode 100755 index 0000000000..798601932c --- /dev/null +++ b/scripts/build-all.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -euo pipefail +SOURCE_DIR=$(readlink -f "${BASH_SOURCE[0]}") +SOURCE_DIR=$(dirname "$SOURCE_DIR") +cd "${SOURCE_DIR}/.." + +if [[ ${1:-} = "-i" ]]; then + INTERACTIVE=true +fi + +ALL_FEATURES=" +async +sync +sync-ssl +async-ssl +async sync +async sync-ssl +sync async-ssl +sync-ssl async-ssl" + +while read FEATS; do + if [[ ${INTERACTIVE:-} ]]; then + cargo build --no-default-features --features "$FEATS" \ + --color always 2>&1 | less -r + else + set -x + cargo build --no-default-features --features "$FEATS" + set +x + fi +done < <(echo "$ALL_FEATURES") + +## all combs of features (lol) +# async +# sync +# sync-ssl +# async-ssl +# async sync +# async sync-ssl +# async async-ssl +# sync sync-ssl +# sync async-ssl +# sync-ssl async-ssl +# async sync sync-ssl +# async sync async-ssl +# async sync-ssl async-ssl +# sync sync-ssl async-ssl +# async sync sync-ssl async-ssl From b99efe6f62a925a418a1ee89c563116bb17f4a97 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Tue, 23 May 2017 21:57:44 -0400 Subject: [PATCH 37/52] Added async server impl. --- src/client/async.rs | 1 - src/client/builder.rs | 2 + src/client/mod.rs | 1 - src/lib.rs | 81 ++++++----- src/server/async.rs | 84 +++++++++-- src/server/mod.rs | 26 +++- src/server/sync.rs | 26 +--- src/server/upgrade/async.rs | 5 +- src/server/upgrade/sync.rs | 10 +- src/stream.rs | 274 ++++++++++++++++++------------------ 10 files changed, 286 insertions(+), 224 deletions(-) diff --git a/src/client/async.rs b/src/client/async.rs index e97a903832..a1a9d1eee6 100644 --- a/src/client/async.rs +++ b/src/client/async.rs @@ -14,4 +14,3 @@ pub use tokio_tls::TlsStream; pub type Client = Framed>; pub type ClientNew = Box, Headers), Error = WebSocketError>>; - diff --git a/src/client/builder.rs b/src/client/builder.rs index 65bb7e96e5..df363b2176 100644 --- a/src/client/builder.rs +++ b/src/client/builder.rs @@ -47,6 +47,8 @@ mod async_imports { #[cfg(feature="async")] use self::async_imports::*; +// TODO: add extra funcs for future stuff, like auto ping and auto close + /// Build clients with a builder-style API /// This makes it easy to create and configure a websocket diff --git a/src/client/mod.rs b/src/client/mod.rs index 15e2bdb781..8942db3228 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -6,4 +6,3 @@ pub mod async; #[cfg(feature="sync")] pub mod sync; - diff --git a/src/lib.rs b/src/lib.rs index 08bf0ff4f1..8e89a0e6d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,50 +97,50 @@ pub mod stream; #[cfg(feature="sync")] pub mod sync { - pub use sender; - pub use sender::Writer; - - pub use receiver; - pub use receiver::Reader; - - pub use stream::sync::Stream; - pub use stream::sync as stream; - - pub mod server { - pub use server::sync::*; - pub use server::upgrade::sync::Upgrade; - pub use server::upgrade::sync::IntoWs; - pub use server::upgrade::sync as upgrade; - } - - pub mod client { - pub use client::sync::*; - pub use client::builder::ClientBuilder; - } + pub use sender; + pub use sender::Writer; + + pub use receiver; + pub use receiver::Reader; + + pub use stream::sync::Stream; + pub use stream::sync as stream; + + pub mod server { + pub use server::sync::*; + pub use server::upgrade::sync::Upgrade; + pub use server::upgrade::sync::IntoWs; + pub use server::upgrade::sync as upgrade; + } + + pub mod client { + pub use client::sync::*; + pub use client::builder::ClientBuilder; + } } #[cfg(feature="async")] pub mod async { - pub use codec; - pub use codec::ws::MessageCodec; - pub use codec::ws::Context as MessageContext; - pub use codec::http::HttpClientCodec; - pub use codec::http::HttpServerCodec; - - pub use stream::async::Stream; - pub use stream::async as stream; - - pub mod server { - pub use server::async::*; - pub use server::upgrade::async::Upgrade; - pub use server::upgrade::async::IntoWs; - pub use server::upgrade::async as upgrade; - } - - pub mod client { - pub use client::async::*; - pub use client::builder::ClientBuilder; - } + pub use codec; + pub use codec::ws::MessageCodec; + pub use codec::ws::Context as MessageContext; + pub use codec::http::HttpClientCodec; + pub use codec::http::HttpServerCodec; + + pub use stream::async::Stream; + pub use stream::async as stream; + + pub mod server { + pub use server::async::*; + pub use server::upgrade::async::Upgrade; + pub use server::upgrade::async::IntoWs; + pub use server::upgrade::async as upgrade; + } + + pub mod client { + pub use client::async::*; + pub use client::builder::ClientBuilder; + } } pub use self::message::Message; @@ -149,4 +149,3 @@ pub use self::message::OwnedMessage; pub use self::result::WebSocketError; pub use self::result::WebSocketResult; - diff --git a/src/server/async.rs b/src/server/async.rs index 5351acc849..017c09f8c4 100644 --- a/src/server/async.rs +++ b/src/server/async.rs @@ -1,16 +1,28 @@ use std::io; use std::net::SocketAddr; use server::{WsServer, NoTlsAcceptor}; -use tokio_core::net::TcpListener; +use tokio_core::net::{TcpListener, TcpStream}; +use futures::{Stream, Future}; +use server::upgrade::async::{IntoWs, Upgrade}; +use server::InvalidConnection; +use bytes::BytesMut; pub use tokio_core::reactor::Handle; #[cfg(any(feature="async-ssl"))] -use native_tls::{TlsStream, TlsAcceptor}; +use native_tls::TlsAcceptor; +#[cfg(any(feature="async-ssl"))] +use tokio_tls::{TlsAcceptorExt, TlsStream}; pub type Server = WsServer; +pub type Incoming = Box, Error = InvalidConnection>>; + +pub enum AcceptError { + Io(io::Error), + Upgrade(E), +} + impl WsServer { - /// Bind this Server to this socket pub fn bind(addr: &SocketAddr, handle: &Handle) -> io::Result { Ok(Server { listener: TcpListener::bind(addr, handle)?, @@ -18,15 +30,34 @@ impl WsServer { }) } - /// Wait for and accept an incoming WebSocket connection, returning a WebSocketRequest - pub fn incoming(&mut self) { - unimplemented!(); + pub fn incoming(self) -> Incoming { + let future = self.listener + .incoming() + .map_err(|e| { + InvalidConnection { + stream: None, + parsed: None, + buffer: None, + error: e.into(), + } + }) + .and_then(|(stream, _)| { + stream.into_ws() + .map_err(|(stream, req, buf, err)| { + InvalidConnection { + stream: Some(stream), + parsed: req, + buffer: Some(buf), + error: err, + } + }) + }); + Box::new(future) } } #[cfg(any(feature="async-ssl"))] impl WsServer { - /// Bind this Server to this socket pub fn bind_secure( addr: &SocketAddr, acceptor: TlsAcceptor, @@ -38,8 +69,41 @@ impl WsServer { }) } - /// Wait for and accept an incoming WebSocket connection, returning a WebSocketRequest - pub fn incoming(&mut self) { - unimplemented!(); + pub fn incoming(self) -> Incoming> { + let acceptor = self.ssl_acceptor; + let future = self.listener + .incoming() + .map_err(|e| { + InvalidConnection { + stream: None, + parsed: None, + buffer: None, + error: e.into(), + } + }) + .and_then(move |(stream, _)| { + acceptor.accept_async(stream) + .map_err(|e| { + InvalidConnection { + stream: None, + parsed: None, + buffer: None, + // TODO: better error types + error: io::Error::new(io::ErrorKind::Other, e).into(), + } + }) + }) + .and_then(|stream| { + stream.into_ws() + .map_err(|(stream, req, buf, err)| { + InvalidConnection { + stream: Some(stream), + parsed: req, + buffer: Some(buf), + error: err, + } + }) + }); + Box::new(future) } } diff --git a/src/server/mod.rs b/src/server/mod.rs index b04daee0e1..9983cf705d 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -2,6 +2,9 @@ #[cfg(any(feature="sync-ssl", feature="async-ssl"))] use native_tls::TlsAcceptor; +use stream::Stream; +use self::upgrade::{Request, HyperIntoWsError}; + pub mod upgrade; #[cfg(feature="async")] @@ -22,10 +25,31 @@ impl OptionalTlsAcceptor for NoTlsAcceptor {} #[cfg(any(feature="sync-ssl", feature="async-ssl"))] impl OptionalTlsAcceptor for TlsAcceptor {} +/// When a sever tries to accept a connection many things can go wrong. +/// +/// This struct is all the information that is recovered from a failed +/// websocket handshake, in case one wants to use the connection for something +/// else (such as HTTP). +pub struct InvalidConnection + where S: Stream +{ + /// if the stream was successfully setup it will be included here + /// on a failed connection. + pub stream: Option, + /// the parsed request. **This is a normal HTTP request** meaning you can + /// simply run this server and handle both HTTP and Websocket connections. + /// If you already have a server you want to use, checkout the + /// `server::upgrade` module to integrate this crate with your server. + pub parsed: Option, + /// the buffered data that was already taken from the stream + pub buffer: Option, + /// the cause of the failed websocket connection setup + pub error: HyperIntoWsError, +} + pub struct WsServer where S: OptionalTlsAcceptor { listener: L, ssl_acceptor: S, } - diff --git a/src/server/sync.rs b/src/server/sync.rs index 8199b5049b..4ca4c6bdef 100644 --- a/src/server/sync.rs +++ b/src/server/sync.rs @@ -5,7 +5,7 @@ use std::convert::Into; #[cfg(feature="sync-ssl")] use native_tls::{TlsStream, TlsAcceptor}; use stream::Stream; -use server::{WsServer, OptionalTlsAcceptor, NoTlsAcceptor}; +use server::{WsServer, OptionalTlsAcceptor, NoTlsAcceptor, InvalidConnection}; use server::upgrade::sync::{Upgrade, IntoWs, Buffer}; pub use server::upgrade::{Request, HyperIntoWsError}; @@ -16,32 +16,10 @@ use tokio_core::net::TcpListener as AsyncTcpListener; #[cfg(feature="async")] use server::async; -/// When a sever tries to accept a connection many things can go wrong. -/// -/// This struct is all the information that is recovered from a failed -/// websocket handshake, in case one wants to use the connection for something -/// else (such as HTTP). -pub struct InvalidConnection - where S: Stream -{ - /// if the stream was successfully setup it will be included here - /// on a failed connection. - pub stream: Option, - /// the parsed request. **This is a normal HTTP request** meaning you can - /// simply run this server and handle both HTTP and Websocket connections. - /// If you already have a server you want to use, checkout the - /// `server::upgrade` module to integrate this crate with your server. - pub parsed: Option, - /// the buffered data that was already taken from the stream - pub buffer: Option, - /// the cause of the failed websocket connection setup - pub error: HyperIntoWsError, -} - /// Either the stream was established and it sent a websocket handshake /// which represents the `Ok` variant, or there was an error (this is the /// `Err` variant). -pub type AcceptResult = Result, InvalidConnection>; +pub type AcceptResult = Result, InvalidConnection>; /// Represents a WebSocket server which can work with either normal /// (non-secure) connections, or secure WebSocket connections. diff --git a/src/server/upgrade/async.rs b/src/server/upgrade/async.rs index 424156b9d1..97616b8894 100644 --- a/src/server/upgrade/async.rs +++ b/src/server/upgrade/async.rs @@ -60,10 +60,7 @@ impl WsUpgrade self.internal_reject(Some(headers)) } - fn internal_reject( - mut self, - headers: Option<&Headers>, - ) -> Send> { + fn internal_reject(mut self, headers: Option<&Headers>) -> Send> { if let Some(custom) = headers { self.headers.extend(custom.iter()); } diff --git a/src/server/upgrade/sync.rs b/src/server/upgrade/sync.rs index 095c61eb79..7792d28fc9 100644 --- a/src/server/upgrade/sync.rs +++ b/src/server/upgrade/sync.rs @@ -95,11 +95,11 @@ impl WsUpgrade> impl WsUpgrade where S: Stream + AsTcpStream { - /// Get a handle to the underlying TCP stream, useful to be able to set - /// TCP options, etc. - pub fn tcp_stream(&self) -> &TcpStream { - self.stream.as_tcp() - } + /// Get a handle to the underlying TCP stream, useful to be able to set + /// TCP options, etc. + pub fn tcp_stream(&self) -> &TcpStream { + self.stream.as_tcp() + } } /// Trait to take a stream or similar and attempt to recover the start of a diff --git a/src/stream.rs b/src/stream.rs index 6437310d6a..13827485f1 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -10,147 +10,147 @@ impl Stream for S where S: Read + Write {} #[cfg(feature="async")] pub mod async { - pub use tokio_core::net::TcpStream; - pub use tokio_io::{AsyncWrite, AsyncRead}; - pub use tokio_io::io::{ReadHalf, WriteHalf}; + pub use tokio_core::net::TcpStream; + pub use tokio_io::{AsyncWrite, AsyncRead}; + pub use tokio_io::io::{ReadHalf, WriteHalf}; - pub trait Stream: AsyncRead + AsyncWrite {} - impl Stream for S where S: AsyncRead + AsyncWrite {} - // TODO: implement for a pair of async read/write? + pub trait Stream: AsyncRead + AsyncWrite {} + impl Stream for S where S: AsyncRead + AsyncWrite {} + // TODO: implement for a pair of async read/write? } #[cfg(feature="sync")] pub mod sync { - use std::io::{self, Read, Write}; - use std::ops::Deref; - use std::fmt::Arguments; - pub use std::net::TcpStream; - pub use std::net::Shutdown; - #[cfg(feature="sync-ssl")] - pub use native_tls::TlsStream; - - pub use super::Stream; - - /// a `Stream` that can also be used as a borrow to a `TcpStream` - /// this is useful when you want to set `TcpStream` options on a - /// `Stream` like `nonblocking`. - pub trait NetworkStream: Read + Write + AsTcpStream {} - - impl NetworkStream for S where S: Read + Write + AsTcpStream {} - - /// some streams can be split up into separate reading and writing components - /// `TcpStream` is an example. This trait marks this ability so one can split - /// up the client into two parts. - /// - /// Notice however that this is not possible to do with SSL. - pub trait Splittable { - /// The reading component of this type - type Reader: Read; - /// The writing component of this type - type Writer: Write; - - /// Split apart this type into a reading and writing component. - fn split(self) -> io::Result<(Self::Reader, Self::Writer)>; - } - - impl Splittable for ReadWritePair - where R: Read, - W: Write - { - type Reader = R; - type Writer = W; - - fn split(self) -> io::Result<(R, W)> { - Ok((self.0, self.1)) - } - } - - impl Splittable for TcpStream { - type Reader = TcpStream; - type Writer = TcpStream; - - fn split(self) -> io::Result<(TcpStream, TcpStream)> { - self.try_clone().map(|s| (s, self)) - } - } - - /// The ability access a borrow to an underlying TcpStream, - /// so one can set options on the stream such as `nonblocking`. - pub trait AsTcpStream { - /// Get a borrow of the TcpStream - fn as_tcp(&self) -> &TcpStream; - } - - impl AsTcpStream for TcpStream { - fn as_tcp(&self) -> &TcpStream { - self - } - } - - #[cfg(feature="sync-ssl")] - impl AsTcpStream for TlsStream { - fn as_tcp(&self) -> &TcpStream { - self.get_ref() - } - } - - impl AsTcpStream for Box + use std::io::{self, Read, Write}; + use std::ops::Deref; + use std::fmt::Arguments; + pub use std::net::TcpStream; + pub use std::net::Shutdown; + #[cfg(feature="sync-ssl")] + pub use native_tls::TlsStream; + + pub use super::Stream; + + /// a `Stream` that can also be used as a borrow to a `TcpStream` + /// this is useful when you want to set `TcpStream` options on a + /// `Stream` like `nonblocking`. + pub trait NetworkStream: Read + Write + AsTcpStream {} + + impl NetworkStream for S where S: Read + Write + AsTcpStream {} + + /// some streams can be split up into separate reading and writing components + /// `TcpStream` is an example. This trait marks this ability so one can split + /// up the client into two parts. + /// + /// Notice however that this is not possible to do with SSL. + pub trait Splittable { + /// The reading component of this type + type Reader: Read; + /// The writing component of this type + type Writer: Write; + + /// Split apart this type into a reading and writing component. + fn split(self) -> io::Result<(Self::Reader, Self::Writer)>; + } + + impl Splittable for ReadWritePair + where R: Read, + W: Write + { + type Reader = R; + type Writer = W; + + fn split(self) -> io::Result<(R, W)> { + Ok((self.0, self.1)) + } + } + + impl Splittable for TcpStream { + type Reader = TcpStream; + type Writer = TcpStream; + + fn split(self) -> io::Result<(TcpStream, TcpStream)> { + self.try_clone().map(|s| (s, self)) + } + } + + /// The ability access a borrow to an underlying TcpStream, + /// so one can set options on the stream such as `nonblocking`. + pub trait AsTcpStream { + /// Get a borrow of the TcpStream + fn as_tcp(&self) -> &TcpStream; + } + + impl AsTcpStream for TcpStream { + fn as_tcp(&self) -> &TcpStream { + self + } + } + + #[cfg(feature="sync-ssl")] + impl AsTcpStream for TlsStream { + fn as_tcp(&self) -> &TcpStream { + self.get_ref() + } + } + + impl AsTcpStream for Box where T: AsTcpStream - { - fn as_tcp(&self) -> &TcpStream { - self.deref().as_tcp() - } - } - - /// If you would like to combine an input stream and an output stream into a single - /// stream to talk websockets over then this is the struct for you! - /// - /// This is useful if you want to use different mediums for different directions. - pub struct ReadWritePair(pub R, pub W) - where R: Read, - W: Write; - - impl Read for ReadWritePair - where R: Read, - W: Write - { - #[inline(always)] - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.0.read(buf) - } - #[inline(always)] - fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { - self.0.read_to_end(buf) - } - #[inline(always)] - fn read_to_string(&mut self, buf: &mut String) -> io::Result { - self.0.read_to_string(buf) - } - #[inline(always)] - fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { - self.0.read_exact(buf) - } - } - - impl Write for ReadWritePair - where R: Read, - W: Write - { - #[inline(always)] - fn write(&mut self, buf: &[u8]) -> io::Result { - self.1.write(buf) - } - #[inline(always)] - fn flush(&mut self) -> io::Result<()> { - self.1.flush() - } - #[inline(always)] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - self.1.write_all(buf) - } - #[inline(always)] - fn write_fmt(&mut self, fmt: Arguments) -> io::Result<()> { - self.1.write_fmt(fmt) - } - } + { + fn as_tcp(&self) -> &TcpStream { + self.deref().as_tcp() + } + } + + /// If you would like to combine an input stream and an output stream into a single + /// stream to talk websockets over then this is the struct for you! + /// + /// This is useful if you want to use different mediums for different directions. + pub struct ReadWritePair(pub R, pub W) + where R: Read, + W: Write; + + impl Read for ReadWritePair + where R: Read, + W: Write + { + #[inline(always)] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.0.read(buf) + } + #[inline(always)] + fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { + self.0.read_to_end(buf) + } + #[inline(always)] + fn read_to_string(&mut self, buf: &mut String) -> io::Result { + self.0.read_to_string(buf) + } + #[inline(always)] + fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { + self.0.read_exact(buf) + } + } + + impl Write for ReadWritePair + where R: Read, + W: Write + { + #[inline(always)] + fn write(&mut self, buf: &[u8]) -> io::Result { + self.1.write(buf) + } + #[inline(always)] + fn flush(&mut self) -> io::Result<()> { + self.1.flush() + } + #[inline(always)] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + self.1.write_all(buf) + } + #[inline(always)] + fn write_fmt(&mut self, fmt: Arguments) -> io::Result<()> { + self.1.write_fmt(fmt) + } + } } From cbae3912b57bf2cbff57fe0a1b7bf0d67e29310a Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Wed, 24 May 2017 12:16:25 -0400 Subject: [PATCH 38/52] Added async agnostic connect. --- src/client/builder.rs | 46 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/client/builder.rs b/src/client/builder.rs index df363b2176..73be8d697d 100644 --- a/src/client/builder.rs +++ b/src/client/builder.rs @@ -489,7 +489,51 @@ impl<'u> ClientBuilder<'u> { ssl_config: Option, handle: &Handle, ) -> async::ClientNew> { - unimplemented!(); + // connect to the tcp stream + let tcp_stream = match self.async_tcpstream(handle) { + Ok(t) => t, + Err(e) => return future::err(e).boxed(), + }; + + let builder = ClientBuilder { + url: Cow::Owned(self.url.into_owned()), + version: self.version, + headers: self.headers, + version_set: self.version_set, + key_set: self.key_set, + }; + + // check if we should connect over ssl or not + if builder.url.scheme() == "wss" { + // configure the tls connection + let (host, connector) = { + match builder.extract_host_ssl_conn(ssl_config) { + Ok((h, conn)) => (h.to_string(), conn), + Err(e) => return future::err(e).boxed(), + } + }; + // secure connection, wrap with ssl + let future = + tcp_stream.map_err(|e| e.into()) + .and_then(move |s| { + connector.connect_async(&host, s) + .map_err(|e| e.into()) + }) + .and_then(move |stream| { + let stream: Box = Box::new(stream); + builder.async_connect_on(stream) + }); + Box::new(future) + } else { + // insecure connection, connect normally + let future = + tcp_stream.map_err(|e| e.into()) + .and_then(move |stream| { + let stream: Box = Box::new(stream); + builder.async_connect_on(stream) + }); + Box::new(future) + } } #[cfg(feature="async-ssl")] From 39f6e5563cf02a7d7e3675d208e5fcbe43d5b2fe Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Wed, 24 May 2017 15:32:55 -0400 Subject: [PATCH 39/52] Fix HTTP parsing error in codec. --- src/codec/http.rs | 69 +++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/src/codec/http.rs b/src/codec/http.rs index 5386b86a68..6ea786f6fb 100644 --- a/src/codec/http.rs +++ b/src/codec/http.rs @@ -17,6 +17,13 @@ use bytes::BufMut; #[derive(Copy, Clone, Debug)] pub struct HttpClientCodec; +fn split_off_http(src: &mut BytesMut) -> Option { + match src.windows(4).position(|i| i == b"\r\n\r\n") { + Some(p) => Some(src.split_to(p + 4)), + None => None, + } +} + impl Encoder for HttpClientCodec { type Item = Incoming<(Method, RequestUri)>; type Error = io::Error; @@ -43,22 +50,21 @@ impl Decoder for HttpClientCodec { fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { // check if we get a request from hyper // TODO: this is ineffecient, but hyper does not give us a better way to parse - let (response, bytes_read) = { - let mut reader = BufReader::new(&*src as &[u8]); - let res = match parse_response(&mut reader) { - Err(hyper::Error::Io(ref e)) if e.kind() == io::ErrorKind::UnexpectedEof => { - return Ok(None) - } - Err(hyper::Error::TooLarge) => return Ok(None), - Err(e) => return Err(e.into()), - Ok(r) => r, - }; - let (_, _, pos, _) = reader.into_parts(); - (res, pos) - }; - - src.split_to(bytes_read); - Ok(Some(response)) + match split_off_http(src) { + Some(buf) => { + let mut reader = BufReader::new(&*src as &[u8]); + let res = match parse_response(&mut reader) { + Err(hyper::Error::Io(ref e)) if e.kind() == io::ErrorKind::UnexpectedEof => { + return Ok(None) + } + Err(hyper::Error::TooLarge) => return Ok(None), + Err(e) => return Err(e.into()), + Ok(r) => r, + }; + Ok(Some(res)) + } + None => Ok(None), + } } } @@ -87,22 +93,21 @@ impl Decoder for HttpServerCodec { fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { // check if we get a request from hyper // TODO: this is ineffecient, but hyper does not give us a better way to parse - let (response, bytes_read) = { - let mut reader = BufReader::new(&*src as &[u8]); - let res = match parse_request(&mut reader) { - Err(hyper::Error::Io(ref e)) if e.kind() == io::ErrorKind::UnexpectedEof => { - return Ok(None) - } - Err(hyper::Error::TooLarge) => return Ok(None), - Err(e) => return Err(e.into()), - Ok(r) => r, - }; - let (_, _, pos, _) = reader.into_parts(); - (res, pos) - }; - - src.split_to(bytes_read); - Ok(Some(response)) + match split_off_http(src) { + Some(buf) => { + let mut reader = BufReader::with_capacity(&*buf as &[u8], buf.len()); + let res = match parse_request(&mut reader) { + Err(hyper::Error::Io(ref e)) if e.kind() == io::ErrorKind::UnexpectedEof => { + return Ok(None); + } + Err(hyper::Error::TooLarge) => return Ok(None), + Err(e) => return Err(e.into()), + Ok(r) => r, + }; + Ok(Some(res)) + } + None => Ok(None), + } } } From 4e9c6f244fe99113b6adc0fe3d9a56c7aa211b48 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Wed, 24 May 2017 15:34:08 -0400 Subject: [PATCH 40/52] Matched sync server API with async, small fixes. --- src/result.rs | 26 ++++++++++++++++++++++++++ src/server/async.rs | 16 ++++++++++------ src/server/upgrade/async.rs | 2 +- src/server/upgrade/mod.rs | 2 +- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/result.rs b/src/result.rs index 9b588dce48..e64e0b59e6 100644 --- a/src/result.rs +++ b/src/result.rs @@ -7,6 +7,7 @@ use std::convert::From; use std::fmt; use hyper::Error as HttpError; use url::ParseError; +use server::upgrade::HyperIntoWsError; #[cfg(any(feature="sync-ssl", feature="async-ssl"))] use native_tls::Error as TlsError; @@ -16,6 +17,12 @@ use native_tls::HandshakeError as TlsHandshakeError; /// The type used for WebSocket results pub type WebSocketResult = Result; +pub mod async { + use futures::Future; + use super::WebSocketError; + pub type WebSocketFuture = Box>; +} + /// Represents a WebSocket error #[derive(Debug)] pub enum WebSocketError { @@ -154,6 +161,25 @@ impl From for WebSocketError { } } +impl From for WebSocketError { + fn from(err: HyperIntoWsError) -> WebSocketError { + use self::HyperIntoWsError::*; + use WebSocketError::*; + match err { + Io(io) => IoError(io), + Parsing(err) => HttpError(err), + MethodNotGet => ProtocolError("Request method must be GET"), + UnsupportedHttpVersion => ProtocolError("Unsupported request HTTP version"), + UnsupportedWebsocketVersion => ProtocolError("Unsupported WebSocket version"), + NoSecWsKeyHeader => ProtocolError("Missing Sec-WebSocket-Key header"), + NoWsUpgradeHeader => ProtocolError("Invalid Upgrade WebSocket header"), + NoUpgradeHeader => ProtocolError("Missing Upgrade WebSocket header"), + NoWsConnectionHeader => ProtocolError("Invalid Connection WebSocket header"), + NoConnectionHeader => ProtocolError("Missing Connection WebSocket header"), + } + } +} + /// Represents a WebSocket URL error #[derive(Debug)] pub enum WSUrlErrorKind { diff --git a/src/server/async.rs b/src/server/async.rs index 017c09f8c4..ffaacb0a10 100644 --- a/src/server/async.rs +++ b/src/server/async.rs @@ -1,5 +1,5 @@ use std::io; -use std::net::SocketAddr; +use std::net::ToSocketAddrs; use server::{WsServer, NoTlsAcceptor}; use tokio_core::net::{TcpListener, TcpStream}; use futures::{Stream, Future}; @@ -23,9 +23,11 @@ pub enum AcceptError { } impl WsServer { - pub fn bind(addr: &SocketAddr, handle: &Handle) -> io::Result { + pub fn bind(addr: A, handle: &Handle) -> io::Result { + let tcp = ::std::net::TcpListener::bind(addr)?; + let address = tcp.local_addr()?; Ok(Server { - listener: TcpListener::bind(addr, handle)?, + listener: TcpListener::from_listener(tcp, &address, handle)?, ssl_acceptor: NoTlsAcceptor, }) } @@ -58,13 +60,15 @@ impl WsServer { #[cfg(any(feature="async-ssl"))] impl WsServer { - pub fn bind_secure( - addr: &SocketAddr, + pub fn bind_secure( + addr: A, acceptor: TlsAcceptor, handle: &Handle, ) -> io::Result { + let tcp = ::std::net::TcpListener::bind(addr)?; + let address = tcp.local_addr()?; Ok(Server { - listener: TcpListener::bind(addr, handle)?, + listener: TcpListener::from_listener(tcp, &address, handle)?, ssl_acceptor: acceptor, }) } diff --git a/src/server/upgrade/async.rs b/src/server/upgrade/async.rs index 97616b8894..c32df3eb6f 100644 --- a/src/server/upgrade/async.rs +++ b/src/server/upgrade/async.rs @@ -44,7 +44,7 @@ impl WsUpgrade headers: headers.clone(), }) .map(move |s| { - let codec = MessageCodec::default(Context::Client); + let codec = MessageCodec::default(Context::Server); let client = Framed::from_parts(s.into_parts(), codec); (client, headers) }) diff --git a/src/server/upgrade/mod.rs b/src/server/upgrade/mod.rs index 7018c471bd..b6ecf9e88d 100644 --- a/src/server/upgrade/mod.rs +++ b/src/server/upgrade/mod.rs @@ -86,7 +86,7 @@ impl WsUpgrade ::std::mem::drop(self); } - /// Aelist of protocols requested from the client. + /// A list of protocols requested from the client. pub fn protocols(&self) -> &[String] { self.request .headers From 7ffdf7ed63dc6c998b57eb27038388a486d90685 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Wed, 24 May 2017 15:34:28 -0400 Subject: [PATCH 41/52] Added async server example and updated examples. --- examples/async-server.rs | 62 +++++++++++++++++++++++++++++++++++++ examples/autobahn-server.rs | 4 ++- examples/hyper.rs | 3 +- examples/server.rs | 3 +- src/lib.rs | 7 +++++ 5 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 examples/async-server.rs diff --git a/examples/async-server.rs b/examples/async-server.rs new file mode 100644 index 0000000000..c005eb31d9 --- /dev/null +++ b/examples/async-server.rs @@ -0,0 +1,62 @@ +extern crate websocket; +extern crate futures; +extern crate tokio_core; + +use websocket::message::{Message, OwnedMessage}; +use websocket::server::InvalidConnection; +use websocket::async::Server; +use websocket::async::client::Client; +use websocket::async::WebSocketFuture; + +use tokio_core::reactor::Core; +use tokio_core::net::TcpStream; +use futures::{Future, Sink, Stream}; + +fn main() { + let mut core = Core::new().unwrap(); + let handle = core.handle(); + // bind to the server + let server = Server::bind("127.0.0.1:2794", &handle).unwrap(); + + // time to build the server's future + // this will be a struct containing everything the server is going to do + + // a stream of incoming connections + let f = server.incoming() + // we don't wanna save the stream if it drops + .map_err(|InvalidConnection { error, .. }| error.into()) + // negotiate with the client + .and_then(|upgrade| { + // check if it has the protocol we want + let uses_proto = upgrade.protocols().iter().any(|s| s == "rust-websocket"); + + let f: WebSocketFuture>> = if uses_proto { + // accept the request to be a ws connection if it does + Box::new(upgrade.use_protocol("rust-websocket").accept().map(|(s, _)| Some(s))) + } else { + // reject it if it doesn't + Box::new(upgrade.reject().map(|_| None).map_err(|e| e.into())) + }; + f + }) + // get rid of the bad connections + .filter_map(|i| i) + // send a greeting! + .and_then(|s| s.send(Message::text("Hello World!").into())) + // simple echo server impl + .and_then(|s| { + let (sink, stream) = s.split(); + stream.filter_map(|m| { + println!("Message from Client: {:?}", m); + match m { + OwnedMessage::Ping(p) => Some(OwnedMessage::Pong(p)), + OwnedMessage::Pong(_) => None, + _ => Some(m), + } + }).forward(sink) + }) + // TODO: ?? + .collect(); + + core.run(f).unwrap(); +} diff --git a/examples/autobahn-server.rs b/examples/autobahn-server.rs index f7bdf544f7..5b7f14229e 100644 --- a/examples/autobahn-server.rs +++ b/examples/autobahn-server.rs @@ -1,7 +1,9 @@ extern crate websocket; use std::thread; -use websocket::{Server, Message, OwnedMessage}; +use websocket::{Message, OwnedMessage}; +use websocket::sync::Server; + fn main() { let server = Server::bind("127.0.0.1:9002").unwrap(); diff --git a/examples/hyper.rs b/examples/hyper.rs index 6ccb48521c..a8fa0a8307 100644 --- a/examples/hyper.rs +++ b/examples/hyper.rs @@ -3,7 +3,8 @@ extern crate hyper; use std::thread; use std::io::Write; -use websocket::{Server, Message, OwnedMessage}; +use websocket::{Message, OwnedMessage}; +use websocket::sync::Server; use hyper::Server as HttpServer; use hyper::net::Fresh; use hyper::server::request::Request; diff --git a/examples/server.rs b/examples/server.rs index 01ccdbc91d..324efdfa41 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -1,7 +1,8 @@ extern crate websocket; use std::thread; -use websocket::{Server, OwnedMessage}; +use websocket::OwnedMessage; +use websocket::sync::Server; fn main() { let server = Server::bind("127.0.0.1:2794").unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 8e89a0e6d8..4bc27da719 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,11 +112,13 @@ pub mod sync { pub use server::upgrade::sync::IntoWs; pub use server::upgrade::sync as upgrade; } + pub use server::sync::Server; pub mod client { pub use client::sync::*; pub use client::builder::ClientBuilder; } + pub use client::sync::Client; } #[cfg(feature="async")] @@ -136,16 +138,21 @@ pub mod async { pub use server::upgrade::async::IntoWs; pub use server::upgrade::async as upgrade; } + pub use server::async::Server; pub mod client { pub use client::async::*; pub use client::builder::ClientBuilder; } + pub use client::async::Client; + + pub use result::async::WebSocketFuture; } pub use self::message::Message; pub use self::message::CloseData; pub use self::message::OwnedMessage; +pub use self::client::builder::ClientBuilder; pub use self::result::WebSocketError; pub use self::result::WebSocketResult; From aacd6abf718cd486d066819b7d1186ee4e36011e Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Thu, 25 May 2017 13:44:45 -0400 Subject: [PATCH 42/52] Fixed multi client bug in async server example. --- examples/async-server.rs | 75 +++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/examples/async-server.rs b/examples/async-server.rs index c005eb31d9..b4696947f8 100644 --- a/examples/async-server.rs +++ b/examples/async-server.rs @@ -2,14 +2,13 @@ extern crate websocket; extern crate futures; extern crate tokio_core; +use std::fmt::Debug; + use websocket::message::{Message, OwnedMessage}; use websocket::server::InvalidConnection; use websocket::async::Server; -use websocket::async::client::Client; -use websocket::async::WebSocketFuture; -use tokio_core::reactor::Core; -use tokio_core::net::TcpStream; +use tokio_core::reactor::{Handle, Core}; use futures::{Future, Sink, Stream}; fn main() { @@ -24,39 +23,45 @@ fn main() { // a stream of incoming connections let f = server.incoming() // we don't wanna save the stream if it drops - .map_err(|InvalidConnection { error, .. }| error.into()) - // negotiate with the client - .and_then(|upgrade| { + .map_err(|InvalidConnection { error, .. }| error) + .for_each(|upgrade| { // check if it has the protocol we want - let uses_proto = upgrade.protocols().iter().any(|s| s == "rust-websocket"); - - let f: WebSocketFuture>> = if uses_proto { - // accept the request to be a ws connection if it does - Box::new(upgrade.use_protocol("rust-websocket").accept().map(|(s, _)| Some(s))) - } else { + if !upgrade.protocols().iter().any(|s| s == "rust-websocket") { // reject it if it doesn't - Box::new(upgrade.reject().map(|_| None).map_err(|e| e.into())) - }; - f - }) - // get rid of the bad connections - .filter_map(|i| i) - // send a greeting! - .and_then(|s| s.send(Message::text("Hello World!").into())) - // simple echo server impl - .and_then(|s| { - let (sink, stream) = s.split(); - stream.filter_map(|m| { - println!("Message from Client: {:?}", m); - match m { - OwnedMessage::Ping(p) => Some(OwnedMessage::Pong(p)), - OwnedMessage::Pong(_) => None, - _ => Some(m), - } - }).forward(sink) - }) - // TODO: ?? - .collect(); + spawn_future(upgrade.reject(), "Upgrade Rejection", &handle); + return Ok(()); + } + + // accept the request to be a ws connection if it does + let f = upgrade + .use_protocol("rust-websocket") + .accept() + // send a greeting! + .and_then(|(s, _)| s.send(Message::text("Hello World!").into())) + // simple echo server impl + .and_then(|s| { + let (sink, stream) = s.split(); + stream.filter_map(|m| { + println!("Message from Client: {:?}", m); + match m { + OwnedMessage::Ping(p) => Some(OwnedMessage::Pong(p)), + OwnedMessage::Pong(_) => None, + _ => Some(m), + } + }).forward(sink) + }); + + spawn_future(f, "Client Status", &handle); + Ok(()) + }); core.run(f).unwrap(); } + +fn spawn_future(f: F, desc: &'static str, handle: &Handle) + where F: Future + 'static, + E: Debug +{ + handle.spawn(f.map_err(move |e| println!("{}: '{:?}'", desc, e)) + .map(move |_| println!("{}: Finished.", desc))); +} From 7ee2a0d327605b3afb09b9d7bdcacbb2c48a7e21 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Thu, 25 May 2017 15:30:35 -0400 Subject: [PATCH 43/52] Fixed http codec bug, server gives client IP in stream. --- examples/async-server.rs | 5 +++-- src/client/builder.rs | 2 +- src/codec/http.rs | 2 +- src/server/async.rs | 13 +++++++++---- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/examples/async-server.rs b/examples/async-server.rs index b4696947f8..960d3d7045 100644 --- a/examples/async-server.rs +++ b/examples/async-server.rs @@ -24,7 +24,8 @@ fn main() { let f = server.incoming() // we don't wanna save the stream if it drops .map_err(|InvalidConnection { error, .. }| error) - .for_each(|upgrade| { + .for_each(|(upgrade, addr)| { + println!("Got a connection from: {}", addr); // check if it has the protocol we want if !upgrade.protocols().iter().any(|s| s == "rust-websocket") { // reject it if it doesn't @@ -41,7 +42,7 @@ fn main() { // simple echo server impl .and_then(|s| { let (sink, stream) = s.split(); - stream.filter_map(|m| { + stream.take_while(|m| Ok(!m.is_close())).filter_map(|m| { println!("Message from Client: {:?}", m); match m { OwnedMessage::Ping(p) => Some(OwnedMessage::Pong(p)), diff --git a/src/client/builder.rs b/src/client/builder.rs index 73be8d697d..da70e4a0dc 100644 --- a/src/client/builder.rs +++ b/src/client/builder.rs @@ -631,7 +631,7 @@ impl<'u> ClientBuilder<'u> { message .ok_or(WebSocketError::ProtocolError( "Connection closed before handshake could complete.")) - .and_then(|message| builder.validate(&message).map(|_| (message, stream))) + .and_then(|message| builder.validate(&message).map(|()| (message, stream))) }) // output the final client and metadata diff --git a/src/codec/http.rs b/src/codec/http.rs index 6ea786f6fb..d5fd83f3b2 100644 --- a/src/codec/http.rs +++ b/src/codec/http.rs @@ -52,7 +52,7 @@ impl Decoder for HttpClientCodec { // TODO: this is ineffecient, but hyper does not give us a better way to parse match split_off_http(src) { Some(buf) => { - let mut reader = BufReader::new(&*src as &[u8]); + let mut reader = BufReader::with_capacity(&*buf as &[u8], buf.len()); let res = match parse_response(&mut reader) { Err(hyper::Error::Io(ref e)) if e.kind() == io::ErrorKind::UnexpectedEof => { return Ok(None) diff --git a/src/server/async.rs b/src/server/async.rs index ffaacb0a10..17e6604efa 100644 --- a/src/server/async.rs +++ b/src/server/async.rs @@ -1,5 +1,6 @@ use std::io; use std::net::ToSocketAddrs; +use std::net::SocketAddr; use server::{WsServer, NoTlsAcceptor}; use tokio_core::net::{TcpListener, TcpStream}; use futures::{Stream, Future}; @@ -15,7 +16,8 @@ use tokio_tls::{TlsAcceptorExt, TlsStream}; pub type Server = WsServer; -pub type Incoming = Box, Error = InvalidConnection>>; +pub type Incoming = Box, SocketAddr), + Error = InvalidConnection>>; pub enum AcceptError { Io(io::Error), @@ -43,7 +45,7 @@ impl WsServer { error: e.into(), } }) - .and_then(|(stream, _)| { + .and_then(|(stream, a)| { stream.into_ws() .map_err(|(stream, req, buf, err)| { InvalidConnection { @@ -53,6 +55,7 @@ impl WsServer { error: err, } }) + .map(move |u| (u, a)) }); Box::new(future) } @@ -85,7 +88,7 @@ impl WsServer { error: e.into(), } }) - .and_then(move |(stream, _)| { + .and_then(move |(stream, a)| { acceptor.accept_async(stream) .map_err(|e| { InvalidConnection { @@ -96,8 +99,9 @@ impl WsServer { error: io::Error::new(io::ErrorKind::Other, e).into(), } }) + .map(move |s| (s, a)) }) - .and_then(|stream| { + .and_then(|(stream, a)| { stream.into_ws() .map_err(|(stream, req, buf, err)| { InvalidConnection { @@ -107,6 +111,7 @@ impl WsServer { error: err, } }) + .map(move |u| (u, a)) }); Box::new(future) } From a406e72b3c346cba0fe0db5d0b2e39c943fa9083 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Thu, 25 May 2017 15:44:54 -0400 Subject: [PATCH 44/52] Added async autobahn server, better closing behaviour. --- examples/async-autobahn-server.rs | 55 +++++++++++++++++++++++++++++++ examples/async-server.rs | 10 ++++-- 2 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 examples/async-autobahn-server.rs diff --git a/examples/async-autobahn-server.rs b/examples/async-autobahn-server.rs new file mode 100644 index 0000000000..8ed043376d --- /dev/null +++ b/examples/async-autobahn-server.rs @@ -0,0 +1,55 @@ +extern crate websocket; +extern crate futures; +extern crate tokio_core; + +use websocket::message::OwnedMessage; +use websocket::server::InvalidConnection; +use websocket::async::Server; + +use tokio_core::reactor::Core; +use futures::{Future, Sink, Stream}; + +fn main() { + let mut core = Core::new().unwrap(); + let handle = core.handle(); + // bind to the server + let server = Server::bind("127.0.0.1:9002", &handle).unwrap(); + + // time to build the server's future + // this will be a struct containing everything the server is going to do + + // a stream of incoming connections + let f = server.incoming() + // we don't wanna save the stream if it drops + .map_err(|InvalidConnection { error, .. }| error) + .for_each(|(upgrade, addr)| { + // accept the request to be a ws connection + println!("Got a connection from: {}", addr); + let f = upgrade + .accept() + .and_then(|(s, _)| { + // simple echo server impl + let (sink, stream) = s.split(); + stream + .take_while(|m| Ok(!m.is_close())) + .filter_map(|m| { + match m { + OwnedMessage::Ping(p) => Some(OwnedMessage::Pong(p)), + OwnedMessage::Pong(_) => None, + _ => Some(m), + } + }) + .forward(sink) + .and_then(|(_, sink)| { + sink.send(OwnedMessage::Close(None)) + }) + }); + + handle.spawn(f.map_err(move |e| println!("{}: '{:?}'", addr, e)) + .map(move |_| println!("{} closed.", addr))); + Ok(()) + }); + + core.run(f).unwrap(); +} + diff --git a/examples/async-server.rs b/examples/async-server.rs index 960d3d7045..34714bc93e 100644 --- a/examples/async-server.rs +++ b/examples/async-server.rs @@ -42,14 +42,20 @@ fn main() { // simple echo server impl .and_then(|s| { let (sink, stream) = s.split(); - stream.take_while(|m| Ok(!m.is_close())).filter_map(|m| { + stream + .take_while(|m| Ok(!m.is_close())) + .filter_map(|m| { println!("Message from Client: {:?}", m); match m { OwnedMessage::Ping(p) => Some(OwnedMessage::Pong(p)), OwnedMessage::Pong(_) => None, _ => Some(m), } - }).forward(sink) + }) + .forward(sink) + .and_then(|(_, sink)| { + sink.send(OwnedMessage::Close(None)) + }) }); spawn_future(f, "Client Status", &handle); From 7000b3a231a945545042aa8477412faae957380e Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Thu, 25 May 2017 15:48:50 -0400 Subject: [PATCH 45/52] Updated to published tokio-io crate. --- Cargo.toml | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d5d5c5f40d..cd555323a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,23 +18,20 @@ license = "MIT" [dependencies] hyper = "^0.10.6" -unicase = "^1.0" -url = "^1.0" -bitflags = "^0.8" -rand = "^0.3" -byteorder = "^1.0" -sha1 = "^0.2" -base64 = "^0.5" -futures = { version = "^0.1", optional = true } -tokio-core = { version = "0.1.7", optional = true } -tokio-io = { version = "0.1.1", optional = true } -tokio-tls = { version = "^0.1", optional = true } -bytes = { version = "^0.4", optional = true } +unicase = "1.0" +url = "1.0" +bitflags = "0.8" +rand = "0.3" +byteorder = "1.0" +sha1 = "0.2" +base64 = "0.5" +futures = { version = "0.1", optional = true } +tokio-core = { version = "0.1", optional = true } +tokio-io = { version = "^0.1.2", optional = true } +tokio-tls = { version = "0.1", optional = true } +bytes = { version = "0.4", optional = true } native-tls = { version = "^0.1.2", optional = true } -[replace."tokio-io:0.1.1"] -git = "https://github.com/tokio-rs/tokio-io.git" - [features] default = ["sync", "sync-ssl", "async", "async-ssl"] sync = [] From bc8a0a42cc78b2490420321654f4b14a7132feba Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Thu, 25 May 2017 16:41:13 -0400 Subject: [PATCH 46/52] Updated doc examples and tests, added to travis. --- examples/async-autobahn-server.rs | 1 - scripts/autobahn-client.sh | 29 +++++++++++++--------- scripts/autobahn-server.sh | 40 ++++++++++++++++++++----------- src/client/builder.rs | 2 +- src/client/sync.rs | 22 +++++++---------- src/result.rs | 1 + src/server/sync.rs | 30 ++++++++++------------- src/server/upgrade/mod.rs | 1 - src/server/upgrade/sync.rs | 8 +++---- src/stream.rs | 2 +- 10 files changed, 72 insertions(+), 64 deletions(-) diff --git a/examples/async-autobahn-server.rs b/examples/async-autobahn-server.rs index 8ed043376d..e22d58717a 100644 --- a/examples/async-autobahn-server.rs +++ b/examples/async-autobahn-server.rs @@ -52,4 +52,3 @@ fn main() { core.run(f).unwrap(); } - diff --git a/scripts/autobahn-client.sh b/scripts/autobahn-client.sh index 0c05ac9e08..42f2a0440f 100755 --- a/scripts/autobahn-client.sh +++ b/scripts/autobahn-client.sh @@ -15,18 +15,25 @@ wstest -m fuzzingserver -s 'autobahn/fuzzingserver.json' & \ FUZZINGSERVER_PID=$! sleep 10 +function test_diff() { + DIFF=$(diff \ + <(jq -S 'del(."rust-websocket" | .. | .duration?)' 'autobahn/client-results.json') \ + <(jq -S 'del(."rust-websocket" | .. | .duration?)' 'autobahn/client/index.json') ) + + if [[ $DIFF ]]; then + echo 'Difference in results, either this is a regression or' \ + 'one should update autobahn/client-results.json with the new results.' \ + 'The results are:' + echo $DIFF + exit 64 + fi +} + cargo build --example autobahn-client cargo run --example autobahn-client +test_diff -DIFF=$(diff \ - <(jq -S 'del(."rust-websocket" | .. | .duration?)' 'autobahn/client-results.json') \ - <(jq -S 'del(."rust-websocket" | .. | .duration?)' 'autobahn/client/index.json') ) - -if [[ $DIFF ]]; then - echo Difference in results, either this is a regression or \ - one should update autobahn/client-results.json with the new results. \ - The results are: - echo $DIFF - exit 64 -fi +cargo build --example async-autobahn-client +cargo run --example async-autobahn-client +test_diff diff --git a/scripts/autobahn-server.sh b/scripts/autobahn-server.sh index af05e8fbf7..cb454e9ddc 100755 --- a/scripts/autobahn-server.sh +++ b/scripts/autobahn-server.sh @@ -12,23 +12,35 @@ function cleanup() { } trap cleanup TERM EXIT +function test_diff() { + DIFF=$(diff \ + <(jq -S 'del(."rust-websocket" | .. | .duration?)' 'autobahn/server-results.json') \ + <(jq -S 'del(."rust-websocket" | .. | .duration?)' 'autobahn/server/index.json') ) + + if [[ $DIFF ]]; then + echo Difference in results, either this is a regression or \ + one should update autobahn/server-results.json with the new results. \ + The results are: + echo $DIFF + exit 64 + fi +} + +# Test synchronous version cargo build --example autobahn-server -./target/debug/examples/autobahn-server & \ - WSSERVER_PID=$! +./target/debug/examples/autobahn-server & WSSERVER_PID=$! echo "Server PID: ${WSSERVER_PID}" sleep 10 - wstest -m fuzzingclient -s 'autobahn/fuzzingclient.json' +kill -9 ${WSSERVER_PID} +test_diff -DIFF=$(diff \ - <(jq -S 'del(."rust-websocket" | .. | .duration?)' 'autobahn/server-results.json') \ - <(jq -S 'del(."rust-websocket" | .. | .duration?)' 'autobahn/server/index.json') ) - -if [[ $DIFF ]]; then - echo Difference in results, either this is a regression or \ - one should update autobahn/server-results.json with the new results. \ - The results are: - echo $DIFF - exit 64 -fi +# Test asynchronous version +cargo build --example async-autobahn-server +./target/debug/examples/async-autobahn-server & WSSERVER_PID=$! +echo "Server PID: ${WSSERVER_PID}" +sleep 10 +wstest -m fuzzingclient -s 'autobahn/fuzzingclient.json' +kill -9 ${WSSERVER_PID} +test_diff diff --git a/src/client/builder.rs b/src/client/builder.rs index da70e4a0dc..092d8caa13 100644 --- a/src/client/builder.rs +++ b/src/client/builder.rs @@ -443,7 +443,7 @@ impl<'u> ClientBuilder<'u> { /// /// ```rust /// # use websocket::ClientBuilder; - /// use websocket::stream::ReadWritePair; + /// use websocket::sync::stream::ReadWritePair; /// use std::io::Cursor; /// /// let accept = b"HTTP/1.1 101 Switching Protocols\r diff --git a/src/client/sync.rs b/src/client/sync.rs index 0a0749e482..ff5ca980c7 100644 --- a/src/client/sync.rs +++ b/src/client/sync.rs @@ -165,11 +165,9 @@ impl Client /// /// client.send_message(&Message::text("Hello world!")).unwrap(); /// - /// let message: Message = client.recv_message().unwrap(); + /// let response = client.recv_message().unwrap(); /// ``` - pub fn recv_message(&mut self) -> WebSocketResult - where I: Iterator - { + pub fn recv_message(&mut self) -> WebSocketResult { self.receiver.recv_message(&mut self.stream) } @@ -260,7 +258,6 @@ impl Client /// ```rust,no_run /// # use websocket::ClientBuilder; /// use std::io::Cursor; - /// use websocket::Message; /// use websocket::ws::receiver::Receiver as ReceiverTrait; /// use websocket::receiver::Receiver; /// @@ -276,7 +273,7 @@ impl Client /// /* transform buf somehow */ /// /// let mut buf_reader = Cursor::new(&mut buf); - /// let message: Message = receiver.recv_message(&mut buf_reader).unwrap(); + /// let message = receiver.recv_message(&mut buf_reader).unwrap(); /// ``` pub fn reader_mut(&mut self) -> &mut Read { &mut self.stream @@ -299,14 +296,13 @@ impl Client ///```no_run ///# extern crate websocket; ///# fn main() { - ///use websocket::{ClientBuilder, Message}; + ///use websocket::ClientBuilder; /// ///let mut client = ClientBuilder::new("ws://127.0.0.1:1234").unwrap() /// .connect(None).unwrap(); /// ///for message in client.incoming_messages() { - /// let message: Message = message.unwrap(); - /// println!("Recv: {:?}", message); + /// println!("Recv: {:?}", message.unwrap()); ///} ///# } ///``` @@ -318,7 +314,7 @@ impl Client ///```no_run ///# extern crate websocket; ///# fn main() { - ///use websocket::{ClientBuilder, Message}; + ///use websocket::ClientBuilder; /// ///let mut client = ClientBuilder::new("ws://127.0.0.1:1234").unwrap() /// .connect_insecure().unwrap(); @@ -326,9 +322,8 @@ impl Client ///let (mut receiver, mut sender) = client.split().unwrap(); /// ///for message in receiver.incoming_messages() { - /// let message: Message = message.unwrap(); /// // Echo the message back - /// sender.send_message(&message).unwrap(); + /// sender.send_message(&message.unwrap()).unwrap(); ///} ///# } ///``` @@ -357,8 +352,7 @@ impl Client /// ///thread::spawn(move || { /// for message in receiver.incoming_messages() { - /// let message: Message = message.unwrap(); - /// println!("Recv: {:?}", message); + /// println!("Recv: {:?}", message.unwrap()); /// } ///}); /// diff --git a/src/result.rs b/src/result.rs index e64e0b59e6..0744381a29 100644 --- a/src/result.rs +++ b/src/result.rs @@ -17,6 +17,7 @@ use native_tls::HandshakeError as TlsHandshakeError; /// The type used for WebSocket results pub type WebSocketResult = Result; +#[cfg(feature="async")] pub mod async { use futures::Future; use super::WebSocketError; diff --git a/src/server/sync.rs b/src/server/sync.rs index 4ca4c6bdef..f6f998b7fc 100644 --- a/src/server/sync.rs +++ b/src/server/sync.rs @@ -4,7 +4,6 @@ use std::io; use std::convert::Into; #[cfg(feature="sync-ssl")] use native_tls::{TlsStream, TlsAcceptor}; -use stream::Stream; use server::{WsServer, OptionalTlsAcceptor, NoTlsAcceptor, InvalidConnection}; use server::upgrade::sync::{Upgrade, IntoWs, Buffer}; pub use server::upgrade::{Request, HyperIntoWsError}; @@ -33,7 +32,8 @@ pub type AcceptResult = Result, InvalidConnection>; ///extern crate websocket; ///# fn main() { ///use std::thread; -///use websocket::{Server, Message}; +///use websocket::Message; +///use websocket::sync::Server; /// ///let server = Server::bind("127.0.0.1:1234").unwrap(); /// @@ -54,14 +54,14 @@ pub type AcceptResult = Result, InvalidConnection>; ///# Secure Servers /// ```no_run ///extern crate websocket; -///extern crate openssl; +///extern crate native_tls; ///# fn main() { ///use std::thread; ///use std::io::Read; ///use std::fs::File; -///use websocket::{Server, Message}; -///use openssl::pkcs12::Pkcs12; -///use openssl::ssl::{SslMethod, SslAcceptorBuilder, SslStream}; +///use websocket::Message; +///use websocket::sync::Server; +///use native_tls::{Pkcs12, TlsAcceptor}; /// ///// In this example we retrieve our keypair and certificate chain from a PKCS #12 archive, ///// but but they can also be retrieved from, for example, individual PEM- or DER-formatted @@ -69,15 +69,9 @@ pub type AcceptResult = Result, InvalidConnection>; ///let mut file = File::open("identity.pfx").unwrap(); ///let mut pkcs12 = vec![]; ///file.read_to_end(&mut pkcs12).unwrap(); -///let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap(); -///let identity = pkcs12.parse("password123").unwrap(); +///let pkcs12 = Pkcs12::from_der(&pkcs12, "hacktheplanet").unwrap(); /// -///let acceptor = SslAcceptorBuilder::mozilla_intermediate(SslMethod::tls(), -/// &identity.pkey, -/// &identity.cert, -/// &identity.chain) -/// .unwrap() -/// .build(); +///let acceptor = TlsAcceptor::builder(pkcs12).unwrap().build().unwrap(); /// ///let server = Server::bind_secure("127.0.0.1:1234", acceptor).unwrap(); /// @@ -116,14 +110,16 @@ impl WsServer } /// Changes whether the Server is in nonblocking mode. + /// NOTE: It is strongly encouraged to use the `websocket::async` module instead + /// of this. It provides high level APIs for creating asynchronous servers. /// - /// If it is in nonblocking mode, accept() will return an error instead of blocking when there - /// are no incoming connections. + /// If it is in nonblocking mode, accept() will return an error instead of + /// blocking when there are no incoming connections. /// ///# Examples ///```no_run /// # extern crate websocket; - /// # use websocket::Server; + /// # use websocket::sync::Server; /// # fn main() { /// // Suppose we have to work in a single thread, but want to /// // accomplish two unrelated things: diff --git a/src/server/upgrade/mod.rs b/src/server/upgrade/mod.rs index b6ecf9e88d..5beb414673 100644 --- a/src/server/upgrade/mod.rs +++ b/src/server/upgrade/mod.rs @@ -1,7 +1,6 @@ //! Allows you to take an existing request or stream of data and convert it into a //! WebSocket client. use std::error::Error; -use std::net::TcpStream; use std::io; use std::fmt::{self, Formatter, Display}; use stream::Stream; diff --git a/src/server/upgrade/sync.rs b/src/server/upgrade/sync.rs index 7792d28fc9..e16bfb8679 100644 --- a/src/server/upgrade/sync.rs +++ b/src/server/upgrade/sync.rs @@ -119,8 +119,8 @@ impl WsUpgrade /// ```rust,no_run /// use std::net::TcpListener; /// use std::net::TcpStream; -/// use websocket::server::upgrade::IntoWs; -/// use websocket::Client; +/// use websocket::sync::server::upgrade::IntoWs; +/// use websocket::sync::Client; /// /// let listener = TcpListener::bind("127.0.0.1:80").unwrap(); /// @@ -221,8 +221,8 @@ impl IntoWs for RequestStreamPair /// # fn main() { /// use hyper::server::{Server, Request, Response}; /// use websocket::Message; -/// use websocket::server::upgrade::IntoWs; -/// use websocket::server::upgrade::from_hyper::HyperRequest; +/// use websocket::sync::server::upgrade::IntoWs; +/// use websocket::sync::server::upgrade::HyperRequest; /// /// Server::http("0.0.0.0:80").unwrap().handle(move |req: Request, res: Response| { /// match HyperRequest(req).into_ws() { diff --git a/src/stream.rs b/src/stream.rs index 13827485f1..0cc35b01c8 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -1,6 +1,6 @@ //! Provides the default stream type for WebSocket connections. -use std::io::{self, Read, Write}; +use std::io::{Read, Write}; /// Represents a stream that can be read from, and written to. /// This is an abstraction around readable and writable things to be able From 86aeb2a75070b11ee0fe371855ee484f5e47793e Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Fri, 26 May 2017 13:39:52 -0400 Subject: [PATCH 47/52] Added async rw impl for rwpair. --- src/stream.rs | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/stream.rs b/src/stream.rs index 0cc35b01c8..66004bfdcb 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -8,19 +8,44 @@ use std::io::{Read, Write}; pub trait Stream: Read + Write {} impl Stream for S where S: Read + Write {} +/// If you would like to combine an input stream and an output stream into a single +/// stream to talk websockets over then this is the struct for you! +/// +/// This is useful if you want to use different mediums for different directions. +pub struct ReadWritePair(pub R, pub W) + where R: Read, + W: Write; + #[cfg(feature="async")] pub mod async { + use std::io::{self, Read, Write}; + use futures::Poll; + pub use super::ReadWritePair; pub use tokio_core::net::TcpStream; pub use tokio_io::{AsyncWrite, AsyncRead}; pub use tokio_io::io::{ReadHalf, WriteHalf}; pub trait Stream: AsyncRead + AsyncWrite {} impl Stream for S where S: AsyncRead + AsyncWrite {} - // TODO: implement for a pair of async read/write? + + impl AsyncRead for ReadWritePair + where R: AsyncRead, + W: Write + { + } + impl AsyncWrite for ReadWritePair + where W: AsyncWrite, + R: Read + { + fn shutdown(&mut self) -> Poll<(), io::Error> { + self.1.shutdown() + } + } } #[cfg(feature="sync")] pub mod sync { + pub use super::ReadWritePair; use std::io::{self, Read, Write}; use std::ops::Deref; use std::fmt::Arguments; @@ -102,14 +127,6 @@ pub mod sync { } } - /// If you would like to combine an input stream and an output stream into a single - /// stream to talk websockets over then this is the struct for you! - /// - /// This is useful if you want to use different mediums for different directions. - pub struct ReadWritePair(pub R, pub W) - where R: Read, - W: Write; - impl Read for ReadWritePair where R: Read, W: Write From 3782f0d5e81cccc68046cab31a25f6658bc3548a Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Fri, 26 May 2017 16:44:06 -0400 Subject: [PATCH 48/52] Fixed bug in async SSL connections. --- examples/ssl-client.rs | 64 ++++++++++++++++++++++++++++++++++++++++++ src/client/builder.rs | 47 ++++++++++++++++--------------- 2 files changed, 89 insertions(+), 22 deletions(-) create mode 100644 examples/ssl-client.rs diff --git a/examples/ssl-client.rs b/examples/ssl-client.rs new file mode 100644 index 0000000000..fa43551ea6 --- /dev/null +++ b/examples/ssl-client.rs @@ -0,0 +1,64 @@ +extern crate websocket; +extern crate futures; +extern crate tokio_core; + +use std::thread; +use std::io::stdin; +use tokio_core::reactor::Core; +use futures::future::Future; +use futures::sink::Sink; +use futures::stream::Stream; +use futures::sync::mpsc; +use websocket::result::WebSocketError; +use websocket::{ClientBuilder, OwnedMessage}; + +const CONNECTION: &'static str = "wss://echo.websocket.org"; + +fn main() { + println!("Connecting to {}", CONNECTION); + let mut core = Core::new().unwrap(); + + // standard in isn't supported in mio yet, so we use a thread + // see https://github.com/carllerche/mio/issues/321 + let (usr_msg, stdin_ch) = mpsc::channel(0); + thread::spawn(move || { + let mut input = String::new(); + let mut stdin_sink = usr_msg.wait(); + loop { + input.clear(); + stdin().read_line(&mut input).unwrap(); + let trimmed = input.trim(); + + let (close, msg) = match trimmed { + "/close" => (true, OwnedMessage::Close(None)), + "/ping" => (false, OwnedMessage::Ping(b"PING".to_vec())), + _ => (false, OwnedMessage::Text(trimmed.to_string())), + }; + + stdin_sink.send(msg) + .expect("Sending message across stdin channel."); + + if close { + break; + } + } + }); + + let runner = ClientBuilder::new(CONNECTION) + .unwrap() + .async_connect_secure(None, &core.handle()) + .and_then(|(duplex, _)| { + let (sink, stream) = duplex.split(); + stream.filter_map(|message| { + println!("Received Message: {:?}", message); + match message { + OwnedMessage::Close(e) => Some(OwnedMessage::Close(e)), + OwnedMessage::Ping(d) => Some(OwnedMessage::Pong(d)), + _ => None, + } + }) + .select(stdin_ch.map_err(|_| WebSocketError::NoDataAvailable)) + .forward(sink) + }); + core.run(runner).unwrap(); +} diff --git a/src/client/builder.rs b/src/client/builder.rs index 092d8caa13..ad80bb1996 100644 --- a/src/client/builder.rs +++ b/src/client/builder.rs @@ -490,7 +490,7 @@ impl<'u> ClientBuilder<'u> { handle: &Handle, ) -> async::ClientNew> { // connect to the tcp stream - let tcp_stream = match self.async_tcpstream(handle) { + let tcp_stream = match self.async_tcpstream(None, handle) { Ok(t) => t, Err(e) => return future::err(e).boxed(), }; @@ -543,7 +543,7 @@ impl<'u> ClientBuilder<'u> { handle: &Handle, ) -> async::ClientNew> { // connect to the tcp stream - let tcp_stream = match self.async_tcpstream(handle) { + let tcp_stream = match self.async_tcpstream(Some(true), handle) { Ok(t) => t, Err(e) => return future::err(e).boxed(), }; @@ -565,14 +565,14 @@ impl<'u> ClientBuilder<'u> { }; // put it all together - let future = - tcp_stream.map_err(|e| e.into()) - .and_then(move |s| { - connector.connect_async(&host, s).map_err(|e| e.into()) - }) - .and_then(move |stream| { - builder.async_connect_on(stream) - }); + let future = tcp_stream.map_err(|e| e.into()) + .and_then(move |s| { + connector.connect_async(&host, s) + .map_err(|e| e.into()) + }) + .and_then(move |stream| { + builder.async_connect_on(stream) + }); Box::new(future) } @@ -580,7 +580,7 @@ impl<'u> ClientBuilder<'u> { // TODO: add conveniences like .response_to_pings, .send_close, etc. #[cfg(feature="async")] pub fn async_connect_insecure(self, handle: &Handle) -> async::ClientNew { - let tcp_stream = match self.async_tcpstream(handle) { + let tcp_stream = match self.async_tcpstream(Some(false), handle) { Ok(t) => t, Err(e) => return future::err(e).boxed(), }; @@ -645,20 +645,23 @@ impl<'u> ClientBuilder<'u> { } #[cfg(feature="async")] - fn async_tcpstream(&self, handle: &Handle) -> WebSocketResult { + fn async_tcpstream( + &self, + secure: Option, + handle: &Handle, + ) -> WebSocketResult { // get the address to connect to, return an error future if ther's a problem - let address = - match self.extract_host_port(Some(false)).and_then(|p| Ok(p.to_socket_addrs()?)) { - Ok(mut s) => { - match s.next() { - Some(a) => a, - None => { - return Err(WebSocketError::WebSocketUrlError(WSUrlErrorKind::NoHostName)); - } + let address = match self.extract_host_port(secure).and_then(|p| Ok(p.to_socket_addrs()?)) { + Ok(mut s) => { + match s.next() { + Some(a) => a, + None => { + return Err(WebSocketError::WebSocketUrlError(WSUrlErrorKind::NoHostName)); } } - Err(e) => return Err(e.into()), - }; + } + Err(e) => return Err(e.into()), + }; // connect a tcp stream Ok(async::TcpStream::connect(&address, handle)) From 23aa5932cfff7d05c245c4873aee3daa961c2a4f Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Fri, 26 May 2017 16:44:51 -0400 Subject: [PATCH 49/52] Added more docs and examples. --- src/codec/http.rs | 37 +++++++++++++++++++++++++++++++++++++ src/codec/mod.rs | 12 ++++++++++++ src/dataframe.rs | 5 ++++- src/lib.rs | 8 +++++++- src/message.rs | 47 ++++++++++++++++++++++++++++++++++++++++++++++- src/result.rs | 5 +++++ src/ws/sender.rs | 3 +++ 7 files changed, 114 insertions(+), 3 deletions(-) diff --git a/src/codec/http.rs b/src/codec/http.rs index d5fd83f3b2..6240cea332 100644 --- a/src/codec/http.rs +++ b/src/codec/http.rs @@ -1,3 +1,7 @@ +//! Send HTTP requests and responses asynchronously. +//! +//! This module has both an `HttpClientCodec` for an async HTTP client and an +//! `HttpServerCodec` for an async HTTP server. use std::io::{self, Write}; use std::error::Error; use std::fmt::{self, Formatter, Display}; @@ -15,6 +19,39 @@ use bytes::BytesMut; use bytes::BufMut; #[derive(Copy, Clone, Debug)] +///```rust,no_run +///# extern crate tokio_core; +///# extern crate tokio_io; +///# extern crate websocket; +///# extern crate hyper; +///# use websocket::codec::http::HttpClientCodec; +///# use websocket::async::futures::Future; +///# use websocket::async::futures::Sink; +///# use tokio_core::net::TcpStream; +///# use tokio_core::reactor::Core; +///# use tokio_io::AsyncRead; +///# use hyper::http::h1::Incoming; +///# use hyper::version::HttpVersion; +///# use hyper::header::Headers; +///# use hyper::method::Method; +///# use hyper::uri::RequestUri; +/// +///# fn main() { +///let core = Core::new().unwrap(); +/// +///let f = TcpStream::connect(&"crouton.net".parse().unwrap(), &core.handle()) +/// .and_then(|s| { +/// Ok(s.framed(HttpClientCodec)) +/// }) +/// .and_then(|s| { +/// s.send(Incoming { +/// version: HttpVersion::Http11, +/// subject: (Method::Get, RequestUri::AbsolutePath("/".to_string())), +/// headers: Headers::new(), +/// }) +/// }); +///# } +///``` pub struct HttpClientCodec; fn split_off_http(src: &mut BytesMut) -> Option { diff --git a/src/codec/mod.rs b/src/codec/mod.rs index f2066e6c07..ca6db82f99 100644 --- a/src/codec/mod.rs +++ b/src/codec/mod.rs @@ -1,2 +1,14 @@ +//! Useful `Codec` types for asynchronously encoding and decoding messages. +//! +//! This is intended to be used with the `Framed` type in the `tokio-io` crate. +//! This module contains an `http` codec which can be used to send and receive +//! hyper HTTP requests and responses asynchronously. +//! See it's module level documentation for more info. +//! +//! Second but most importantly this module contains a codec for asynchronously +//! encoding and decoding websocket messages (and dataframes if you want to go +//! more low level) in the `ws` module. +//! See it's module level documentation for more info. + pub mod http; pub mod ws; diff --git a/src/dataframe.rs b/src/dataframe.rs index 0bc5ac0603..9def50bcaa 100644 --- a/src/dataframe.rs +++ b/src/dataframe.rs @@ -37,7 +37,10 @@ impl DataFrame { } } - /// TODO: docs + /// Take the body and header of a dataframe and combine it into a single + /// Dataframe struct. A websocket message can be made up of many individual + /// dataframes, use the methods from the Message or OwnedMessage structs to + /// take many of these and create a websocket message. pub fn read_dataframe_body( header: DataFrameHeader, body: Vec, diff --git a/src/lib.rs b/src/lib.rs index 4bc27da719..a12356f1ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,7 +52,7 @@ extern crate tokio_io; #[cfg(feature="async")] extern crate bytes; #[cfg(feature="async")] -extern crate futures; +pub extern crate futures; #[cfg(feature="async-ssl")] extern crate tokio_tls; @@ -147,6 +147,12 @@ pub mod async { pub use client::async::Client; pub use result::async::WebSocketFuture; + + pub use futures; + pub use tokio_core::net::TcpStream; + pub use tokio_core::net::TcpListener; + pub use tokio_core::reactor::Core; + pub use tokio_core::reactor::Handle; } pub use self::message::Message; diff --git a/src/message.rs b/src/message.rs index a79cf8ea87..d9b6b1dadd 100644 --- a/src/message.rs +++ b/src/message.rs @@ -229,7 +229,14 @@ impl<'a> ws::Message for Message<'a> { } } -/// Represents a WebSocket message. +/// Represents an owned WebSocket message. +/// +/// `OwnedMessage`s are generated when the user receives a message (since the data +/// has to be copied out of the network buffer anyway). +/// If you would like to create a message out of borrowed data to use for sending +/// please use the `Message` struct (which contains a `Cow`). +/// +/// Note that `OwnedMessage` can `Message` can be converted into each other. #[derive(Eq, PartialEq, Clone, Debug)] pub enum OwnedMessage { /// A message containing UTF-8 text data @@ -249,6 +256,12 @@ pub enum OwnedMessage { } impl OwnedMessage { + /// Checks if this message is a close message. + /// + ///```rust + ///# use websocket::OwnedMessage; + ///assert!(OwnedMessage::Close(None).is_close()); + ///``` pub fn is_close(&self) -> bool { match *self { OwnedMessage::Close(_) => true, @@ -256,6 +269,15 @@ impl OwnedMessage { } } + /// Checks if this message is a control message. + /// Control messages are either `Close`, `Ping`, or `Pong`. + /// + ///```rust + ///# use websocket::OwnedMessage; + ///assert!(OwnedMessage::Ping(vec![]).is_control()); + ///assert!(OwnedMessage::Pong(vec![]).is_control()); + ///assert!(OwnedMessage::Close(None).is_control()); + ///``` pub fn is_control(&self) -> bool { match *self { OwnedMessage::Close(_) => true, @@ -265,10 +287,26 @@ impl OwnedMessage { } } + /// Checks if this message is a data message. + /// Data messages are either `Text` or `Binary`. + /// + ///```rust + ///# use websocket::OwnedMessage; + ///assert!(OwnedMessage::Text("1337".to_string()).is_data()); + ///assert!(OwnedMessage::Binary(vec![]).is_data()); + ///``` pub fn is_data(&self) -> bool { !self.is_control() } + /// Checks if this message is a ping message. + /// `Ping` messages can come at any time and usually generate a `Pong` message + /// response. + /// + ///```rust + ///# use websocket::OwnedMessage; + ///assert!(OwnedMessage::Ping("ping".to_string().into_bytes()).is_ping()); + ///``` pub fn is_ping(&self) -> bool { match *self { OwnedMessage::Ping(_) => true, @@ -276,6 +314,13 @@ impl OwnedMessage { } } + /// Checks if this message is a pong message. + /// `Pong` messages are usually sent only in response to `Ping` messages. + /// + ///```rust + ///# use websocket::OwnedMessage; + ///assert!(OwnedMessage::Pong("pong".to_string().into_bytes()).is_pong()); + ///``` pub fn is_pong(&self) -> bool { match *self { OwnedMessage::Pong(_) => true, diff --git a/src/result.rs b/src/result.rs index 0744381a29..723bb537d1 100644 --- a/src/result.rs +++ b/src/result.rs @@ -17,10 +17,15 @@ use native_tls::HandshakeError as TlsHandshakeError; /// The type used for WebSocket results pub type WebSocketResult = Result; +/// This module contains convenience types to make working with Futures and +/// websocket results easier. #[cfg(feature="async")] pub mod async { use futures::Future; use super::WebSocketError; + + /// The most common Future in this library, it is simply some result `I` or + /// a `WebSocketError`. This is analogous to the `WebSocketResult` type. pub type WebSocketFuture = Box>; } diff --git a/src/ws/sender.rs b/src/ws/sender.rs index 2e0656cefc..024b78ad4a 100644 --- a/src/ws/sender.rs +++ b/src/ws/sender.rs @@ -9,6 +9,9 @@ use result::WebSocketResult; /// A trait for sending data frames and messages. pub trait Sender { + /// Should the messages sent be masked. + /// See the [RFC](https://tools.ietf.org/html/rfc6455#section-5.3) + /// for more detail. fn is_masked(&self) -> bool; /// Sends a single data frame using this sender. From 876431892bd27108a20d01cbceb6c987fff01483 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Sun, 28 May 2017 15:11:25 -0400 Subject: [PATCH 50/52] Added even more docs and examples. --- src/client/async.rs | 59 +++++++++ src/client/builder.rs | 160 +++++++++++++++++++++++- src/client/mod.rs | 15 +++ src/codec/http.rs | 147 +++++++++++++++++++++- src/codec/ws.rs | 236 +++++++++++++++++++++++++++++++++++- src/lib.rs | 2 +- src/server/sync.rs | 4 +- src/server/upgrade/async.rs | 109 ++++++++++++++++- src/server/upgrade/mod.rs | 4 + src/server/upgrade/sync.rs | 5 + 10 files changed, 723 insertions(+), 18 deletions(-) diff --git a/src/client/async.rs b/src/client/async.rs index a1a9d1eee6..c497a51fd4 100644 --- a/src/client/async.rs +++ b/src/client/async.rs @@ -1,3 +1,49 @@ +//! Contains the asynchronous websocket client. +//! +//! The async client is simply a `Stream + Sink` of `OwnedMessage` structs. +//! This definition means that you don't have to learn any new APIs other than +//! futures-rs. +//! The client simply wraps around an `AsyncRead + AsyncWrite` stream and uses +//! a `MessageCodec` to chop up the bytes into websocket messages. +//! See the `codec` module for all the cool codecs this crate has. +//! +//! Since this is all asynchronous, you will not create a client from `ClientBuilder` +//! but instead you will create a `ClientNew` struct, which is a Future that +//! will eventually evaluate to a `Client`. +//! +//! # Example with Type Annotations +//! +//! ```rust,no_run +//! # extern crate tokio_core; +//! # extern crate futures; +//! # extern crate websocket; +//! use websocket::ClientBuilder; +//! use websocket::async::client::{Client, ClientNew}; +//! use websocket::async::TcpStream; +//! use websocket::futures::{Future, Stream, Sink}; +//! use websocket::Message; +//! use tokio_core::reactor::Core; +//! # fn main() { +//! +//! let mut core = Core::new().unwrap(); +//! +//! // create a Future of a client +//! let client_future: ClientNew = +//! ClientBuilder::new("ws://echo.websocket.org").unwrap() +//! .async_connect_insecure(&core.handle()); +//! +//! // send a message +//! let send_future = client_future +//! .and_then(|(client, headers)| { +//! // just to make it clear what type this is +//! let client: Client = client; +//! client.send(Message::text("hallo").into()) +//! }); +//! +//! core.run(send_future).unwrap(); +//! # } +//! ``` + pub use tokio_core::reactor::Handle; pub use tokio_io::codec::Framed; pub use tokio_core::net::TcpStream; @@ -11,6 +57,19 @@ use message::OwnedMessage; #[cfg(feature="async-ssl")] pub use tokio_tls::TlsStream; +/// An asynchronous websocket client. +/// +/// This is simply a `Stream` and `Sink` of `OwnedMessage`s. +/// See the docs for `Stream` and `Sink` to learn more about how to use +/// these futures. pub type Client = Framed>; +/// A future which will evaluate to a `Client` and a set of hyper `Headers`. +/// +/// The `Client` can send and receive websocket messages, and the Headers are +/// the headers that came back from the server handshake. +/// If the user used a protocol or attached some other headers check these response +/// headers to see if the server accepted the protocol or other custom header. +/// This crate will not automatically close the connection if the server refused +/// to use the user protocols given to it, you must check that the server accepted. pub type ClientNew = Box, Headers), Error = WebSocketError>>; diff --git a/src/client/builder.rs b/src/client/builder.rs index ad80bb1996..2f7ebde353 100644 --- a/src/client/builder.rs +++ b/src/client/builder.rs @@ -398,7 +398,7 @@ impl<'u> ClientBuilder<'u> { } /// Create an insecure (plain TCP) connection to the client. - /// In this case no `Box` will be used you will just get a TcpStream, + /// In this case no `Box` will be used, you will just get a TcpStream, /// giving you the ability to split the stream into a reader and writer /// (since SSL streams cannot be cloned). /// @@ -419,9 +419,9 @@ impl<'u> ClientBuilder<'u> { } /// Create an SSL connection to the sever. - /// This will only use an `SslStream`, this is useful + /// This will only use an `TlsStream`, this is useful /// when you want to be sure to connect over SSL or when you want access - /// to the `SslStream` functions (without having to go through a `Box`). + /// to the `TlsStream` functions (without having to go through a `Box`). #[cfg(feature="sync-ssl")] pub fn connect_secure( &mut self, @@ -434,7 +434,6 @@ impl<'u> ClientBuilder<'u> { self.connect_on(ssl_stream) } - // TODO: similar ability for server? /// Connects to a websocket server on any stream you would like. /// Possible streams: /// - Unix Sockets @@ -483,6 +482,50 @@ impl<'u> ClientBuilder<'u> { Ok(Client::unchecked(reader, response.headers, true, false)) } + /// Connect to a websocket server asynchronously. + /// + /// This will use a `Box` to represent either + /// an SSL connection or a normal TCP connection, what to use will be decided + /// using the protocol of the URL passed in (e.g. `ws://` or `wss://`) + /// + /// If you have non-default SSL circumstances, you can use the `ssl_config` + /// parameter to configure those. + /// + ///# Example + /// + /// ```rust,no_run + /// # extern crate rand; + /// # extern crate tokio_core; + /// # extern crate futures; + /// # extern crate websocket; + /// use websocket::ClientBuilder; + /// use websocket::futures::{Future, Stream, Sink}; + /// use websocket::Message; + /// use tokio_core::reactor::Core; + /// # use rand::Rng; + /// + /// # fn main() { + /// let mut core = Core::new().unwrap(); + /// + /// // let's randomly do either SSL or plaintext + /// let url = if rand::thread_rng().gen() { + /// "ws://echo.websocket.org" + /// } else { + /// "wss://echo.websocket.org" + /// }; + /// + /// // send a message and hear it come back + /// let echo_future = ClientBuilder::new(url).unwrap() + /// .async_connect(None, &core.handle()) + /// .and_then(|(s, _)| s.send(Message::text("hallo").into())) + /// .and_then(|s| s.into_future().map_err(|e| e.0)) + /// .map(|(m, _)| { + /// assert_eq!(m, Some(Message::text("hallo").into())) + /// }); + /// + /// core.run(echo_future).unwrap(); + /// # } + /// ``` #[cfg(feature="async-ssl")] pub fn async_connect( self, @@ -536,6 +579,41 @@ impl<'u> ClientBuilder<'u> { } } + /// Asynchronously create an SSL connection to a websocket sever. + /// + /// This method will only try to connect over SSL and fail otherwise, useful + /// when you want to be sure to connect over SSL or when you want access + /// to the `TlsStream` functions (without having to go through a `Box`). + /// + /// If you have non-default SSL circumstances, you can use the `ssl_config` + /// parameter to configure those. + /// + ///# Example + /// + /// ```rust + /// # extern crate tokio_core; + /// # extern crate futures; + /// # extern crate websocket; + /// use websocket::ClientBuilder; + /// use websocket::futures::{Future, Stream, Sink}; + /// use websocket::Message; + /// use tokio_core::reactor::Core; + /// # fn main() { + /// + /// let mut core = Core::new().unwrap(); + /// + /// // send a message and hear it come back + /// let echo_future = ClientBuilder::new("wss://echo.websocket.org").unwrap() + /// .async_connect_secure(None, &core.handle()) + /// .and_then(|(s, _)| s.send(Message::text("hallo").into())) + /// .and_then(|s| s.into_future().map_err(|e| e.0)) + /// .map(|(m, _)| { + /// assert_eq!(m, Some(Message::text("hallo").into())) + /// }); + /// + /// core.run(echo_future).unwrap(); + /// # } + /// ``` #[cfg(feature="async-ssl")] pub fn async_connect_secure( self, @@ -578,6 +656,38 @@ impl<'u> ClientBuilder<'u> { // TODO: add timeout option for connecting // TODO: add conveniences like .response_to_pings, .send_close, etc. + /// Asynchronously create an insecure (plain TCP) connection to the client. + /// + /// In this case no `Box` will be used, you will just get a `TcpStream`, + /// giving you less allocations on the heap and direct access to `TcpStream` + /// functions. + /// + ///# Example + /// + /// ```rust,no_run + /// # extern crate tokio_core; + /// # extern crate futures; + /// # extern crate websocket; + /// use websocket::ClientBuilder; + /// use websocket::futures::{Future, Stream, Sink}; + /// use websocket::Message; + /// use tokio_core::reactor::Core; + /// # fn main() { + /// + /// let mut core = Core::new().unwrap(); + /// + /// // send a message and hear it come back + /// let echo_future = ClientBuilder::new("ws://echo.websocket.org").unwrap() + /// .async_connect_insecure(&core.handle()) + /// .and_then(|(s, _)| s.send(Message::text("hallo").into())) + /// .and_then(|s| s.into_future().map_err(|e| e.0)) + /// .map(|(m, _)| { + /// assert_eq!(m, Some(Message::text("hallo").into())) + /// }); + /// + /// core.run(echo_future).unwrap(); + /// # } + /// ``` #[cfg(feature="async")] pub fn async_connect_insecure(self, handle: &Handle) -> async::ClientNew { let tcp_stream = match self.async_tcpstream(Some(false), handle) { @@ -600,6 +710,48 @@ impl<'u> ClientBuilder<'u> { Box::new(future) } + /// Asynchronously connects to a websocket server on any stream you would like. + /// Possible streams: + /// - Unix Sockets + /// - Bluetooth + /// - Logging Middle-ware + /// - SSH + /// + /// The stream must be `AsyncRead + AsyncWrite + Send + 'static`. + /// + /// # Example + /// + /// ```rust + /// use websocket::header::WebSocketProtocol; + /// use websocket::ClientBuilder; + /// use websocket::sync::stream::ReadWritePair; + /// use websocket::futures::Future; + /// use websocket::async::Core; + /// # use std::io::Cursor; + /// + /// let mut core = Core::new().unwrap(); + /// + /// let accept = b"\ + /// HTTP/1.1 101 Switching Protocols\r\n\ + /// Upgrade: websocket\r\n\ + /// Sec-WebSocket-Protocol: proto-metheus\r\n\ + /// Connection: Upgrade\r\n\ + /// Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\ + /// \r\n"; + /// + /// let input = Cursor::new(&accept[..]); + /// let output = Cursor::new(Vec::new()); + /// + /// let client = ClientBuilder::new("wss://test.ws").unwrap() + /// .key(b"the sample nonce".clone()) + /// .async_connect_on(ReadWritePair(input, output)) + /// .map(|(_, headers)| { + /// let proto: &WebSocketProtocol = headers.get().unwrap(); + /// assert_eq!(proto.0.first().unwrap(), "proto-metheus") + /// }); + /// + /// core.run(client).unwrap(); + /// ``` #[cfg(feature="async")] pub fn async_connect_on(self, stream: S) -> async::ClientNew where S: stream::async::Stream + Send + 'static diff --git a/src/client/mod.rs b/src/client/mod.rs index 8942db3228..8a4749b1ce 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,3 +1,18 @@ +//! Build and use asynchronously or synchronous websocket clients. +//! +//! This crate is split up into a synchronous and asynchronous half. +//! These can be turned on and off by switching the `sync` and `async` features +//! on and off (plus `sync-ssl` and `async-ssl` for SSL connections). +//! +//! In general pick a style you would like to write in and use `ClientBuilder` +//! to create your websocket connections. Use the `.async_connect` functions to create +//! async connections, and the normal `.connect` functions for synchronous clients. +//! The `ClientBuilder` creates both async and sync connections, the actual sync and +//! async clients live in the `client::sync` and `client::async` modules, respectively. +//! +//! Many of the useful things from this module will be hoisted and re-exported under the +//! `websocket::{sync, async}::client` module which will have all sync or all async things. + pub mod builder; pub use self::builder::{ClientBuilder, Url, ParseError}; diff --git a/src/codec/http.rs b/src/codec/http.rs index 6240cea332..6688e9b6fd 100644 --- a/src/codec/http.rs +++ b/src/codec/http.rs @@ -19,14 +19,18 @@ use bytes::BytesMut; use bytes::BufMut; #[derive(Copy, Clone, Debug)] +///A codec to be used with `tokio` codecs that can serialize HTTP requests and +///deserialize HTTP responses. One can use this on it's own without websockets to +///make a very bare async HTTP server. +/// +///# Example ///```rust,no_run ///# extern crate tokio_core; ///# extern crate tokio_io; ///# extern crate websocket; ///# extern crate hyper; -///# use websocket::codec::http::HttpClientCodec; -///# use websocket::async::futures::Future; -///# use websocket::async::futures::Sink; +///use websocket::async::HttpClientCodec; +///# use websocket::async::futures::{Future, Sink, Stream}; ///# use tokio_core::net::TcpStream; ///# use tokio_core::reactor::Core; ///# use tokio_io::AsyncRead; @@ -37,9 +41,10 @@ use bytes::BufMut; ///# use hyper::uri::RequestUri; /// ///# fn main() { -///let core = Core::new().unwrap(); +///let mut core = Core::new().unwrap(); +///let addr = "crouton.net".parse().unwrap(); /// -///let f = TcpStream::connect(&"crouton.net".parse().unwrap(), &core.handle()) +///let f = TcpStream::connect(&addr, &core.handle()) /// .and_then(|s| { /// Ok(s.framed(HttpClientCodec)) /// }) @@ -49,7 +54,12 @@ use bytes::BufMut; /// subject: (Method::Get, RequestUri::AbsolutePath("/".to_string())), /// headers: Headers::new(), /// }) -/// }); +/// }) +/// .map_err(|e| e.into()) +/// .and_then(|s| s.into_future().map_err(|(e, _)| e)) +/// .map(|(m, _)| println!("You got a crouton: {:?}", m)); +/// +///core.run(f).unwrap(); ///# } ///``` pub struct HttpClientCodec; @@ -105,6 +115,59 @@ impl Decoder for HttpClientCodec { } } +///A codec that can be used with streams implementing `AsyncRead + AsyncWrite` +///that can serialize HTTP responses and deserialize HTTP requests. Using this +///with an async `TcpStream` will give you a very bare async HTTP server. +/// +///This crate sends out one HTTP request / response in order to perform the websocket +///handshake then never talks HTTP again. Because of this an async HTTP implementation +///is needed. +/// +///# Example +/// +///```rust,no_run +///# extern crate tokio_core; +///# extern crate tokio_io; +///# extern crate websocket; +///# extern crate hyper; +///# use std::io; +///use websocket::async::HttpServerCodec; +///# use websocket::async::futures::{Future, Sink, Stream}; +///# use tokio_core::net::TcpStream; +///# use tokio_core::reactor::Core; +///# use tokio_io::AsyncRead; +///# use hyper::http::h1::Incoming; +///# use hyper::version::HttpVersion; +///# use hyper::header::Headers; +///# use hyper::method::Method; +///# use hyper::uri::RequestUri; +///# use hyper::status::StatusCode; +///# fn main() { +/// +///let mut core = Core::new().unwrap(); +///let addr = "nothing-to-see-here.com".parse().unwrap(); +/// +///let f = TcpStream::connect(&addr, &core.handle()) +/// .map(|s| s.framed(HttpServerCodec)) +/// .map_err(|e| e.into()) +/// .and_then(|s| s.into_future().map_err(|(e, _)| e)) +/// .and_then(|(m, s)| match m { +/// Some(ref m) if m.subject.0 == Method::Get => Ok(s), +/// _ => panic!(), +/// }) +/// .and_then(|stream| { +/// stream +/// .send(Incoming { +/// version: HttpVersion::Http11, +/// subject: StatusCode::NotFound, +/// headers: Headers::new(), +/// }) +/// .map_err(|e| e.into()) +/// }); +/// +///core.run(f).unwrap(); +///# } +///``` #[derive(Copy, Clone, Debug)] pub struct HttpServerCodec; @@ -148,9 +211,15 @@ impl Decoder for HttpServerCodec { } } +/// Any error that can happen during the writing or parsing of HTTP requests +/// and responses. This consists of HTTP parsing errors (the `Http` variant) and +/// errors that can occur when writing to IO (the `Io` variant). #[derive(Debug)] pub enum HttpCodecError { + /// An error that occurs during the writing or reading of HTTP data + /// from a socket. Io(io::Error), + /// An error that occurs during the parsing of an HTTP request or response. Http(hyper::Error), } @@ -187,3 +256,69 @@ impl From for HttpCodecError { HttpCodecError::Http(err) } } + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + use stream::ReadWritePair; + use tokio_core::reactor::Core; + use futures::{Stream, Sink, Future}; + use tokio_io::AsyncRead; + use hyper::version::HttpVersion; + use hyper::header::Headers; + + #[test] + fn test_client_http_codec() { + let mut core = Core::new().unwrap(); + let response = "HTTP/1.1 404 Not Found\r\n\r\npssst extra data here"; + let input = Cursor::new(response.as_bytes()); + let output = Cursor::new(Vec::new()); + + let f = ReadWritePair(input, output) + .framed(HttpClientCodec) + .send(Incoming { + version: HttpVersion::Http11, + subject: (Method::Get, RequestUri::AbsolutePath("/".to_string())), + headers: Headers::new(), + }) + .map_err(|e| e.into()) + .and_then(|s| s.into_future().map_err(|(e, _)| e)) + .and_then(|(m, _)| match m { + Some(ref m) if StatusCode::from_u16(m.subject.0) == + StatusCode::NotFound => Ok(()), + _ => Err(io::Error::new(io::ErrorKind::Other, "test failed").into()), + }); + core.run(f).unwrap(); + } + + #[test] + fn test_server_http_codec() { + let mut core = Core::new().unwrap(); + let request = "\ + GET / HTTP/1.0\r\n\ + Host: www.rust-lang.org\r\n\ + \r\n\ + ".as_bytes(); + let input = Cursor::new(request); + let output = Cursor::new(Vec::new()); + + let f = ReadWritePair(input, output) + .framed(HttpServerCodec) + .into_future() + .map_err(|(e, _)| e) + .and_then(|(m, s)| match m { + Some(ref m) if m.subject.0 == Method::Get => Ok(s), + _ => Err(io::Error::new(io::ErrorKind::Other, "test failed").into()), + }) + .and_then(|s| { + s.send(Incoming { + version: HttpVersion::Http11, + subject: StatusCode::NotFound, + headers: Headers::new(), + }) + .map_err(|e| e.into()) + }); + core.run(f).unwrap(); + } +} diff --git a/src/codec/ws.rs b/src/codec/ws.rs index 14f707c908..26d13ff82b 100644 --- a/src/codec/ws.rs +++ b/src/codec/ws.rs @@ -1,3 +1,12 @@ +//! Send websocket messages and dataframes asynchronously. +//! +//! This module provides codecs that can be be used with `tokio` to create +//! asynchronous streams that can serialize/deserialize websocket messages +//! (and dataframes for users that want low level control). +//! +//! For websocket messages, see the documentation for `MessageCodec`, for +//! dataframes see the documentation for `DataFrameCodec` + use std::borrow::Borrow; use std::marker::PhantomData; use std::io::Cursor; @@ -18,28 +27,61 @@ use result::WebSocketError; // TODO: IMPORTANT: check if frame_size is correct, // do not call .reserve with the entire size -/************** - * Dataframes * - **************/ - +/// Even though a websocket connection may look perfectly symmetrical +/// in reality there are small differences between clients and servers. +/// This type is passed to the codecs to inform them of what role they are in +/// (i.e. that of a Client or Server). +/// +/// For those familiar with the protocol, this decides wether the data should be +/// masked or not. #[derive(Clone,PartialEq,Eq,Debug)] pub enum Context { + /// Set the codec to act in `Server` mode, used when + /// implementing a websocket server. Server, + /// Set the codec to act in `Client` mode, used when + /// implementing a websocket client. Client, } +/************** + * Dataframes * + **************/ + +/// A codec for deconding and encoding websocket dataframes. +/// +/// This codec decodes dataframes into the crates default implementation +/// of `Dataframe` but can encode and send any struct that implements the +/// `ws::Dataframe` trait. The type of struct to encode is given by the `D` +/// type parameter in the struct. +/// +/// Using dataframes directly is meant for users who want low-level access to the +/// connection. If you don't want to do anything low-level please use the +/// `MessageCodec` codec instead, or better yet use the `ClientBuilder` to make +/// clients and the `Server` to make servers. pub struct DataFrameCodec { is_server: bool, frame_type: PhantomData, } impl DataFrameCodec { + /// Create a new `DataFrameCodec` struct using the crate's implementation + /// of dataframes for reading and writing dataframes. + /// + /// Use this method if you don't want to provide a custom implementation + /// for your dataframes. pub fn default(context: Context) -> Self { DataFrameCodec::new(context) } } impl DataFrameCodec { + /// Create a new `DataFrameCodec` struct using any implementation of + /// `ws::Dataframe` you want. This is useful if you want to manipulate + /// the websocket layer very specifically. + /// + /// If you only want to be able to send and receive the crate's + /// `DataFrame` struct use `.default(Context)` instead. pub fn new(context: Context) -> DataFrameCodec { DataFrameCodec { is_server: context == Context::Server, @@ -102,6 +144,56 @@ impl Encoder for DataFrameCodec * Messages * ************/ +/// A codec for asynchronously decoding and encoding websocket messages. +/// +/// This codec decodes messages into the `OwnedMessage` struct, so using this +/// the user will receive messages as `OwnedMessage`s. However it can encode +/// any type of message that implements the `ws::Message` trait (that type is +/// decided by the `M` type parameter) like `OwnedMessage` and `Message`. +/// +/// Warning: if you don't know what your doing or want a simple websocket connection +/// please use the `ClientBuilder` or the `Server` structs. You should only use this +/// after a websocket handshake has already been completed on the stream you are +/// using. +/// +///# Example +/// +///```rust +///# extern crate tokio_core; +///# extern crate tokio_io; +///# extern crate websocket; +///# extern crate hyper; +///# use std::io::{self, Cursor}; +///use websocket::async::{MessageCodec, MsgCodecCtx}; +///# use websocket::{Message, OwnedMessage}; +///# use websocket::ws::Message as MessageTrait; +///# use websocket::stream::ReadWritePair; +///# use websocket::async::futures::{Future, Sink, Stream}; +///# use tokio_core::net::TcpStream; +///# use tokio_core::reactor::Core; +///# use tokio_io::AsyncRead; +///# use hyper::http::h1::Incoming; +///# use hyper::version::HttpVersion; +///# use hyper::header::Headers; +///# use hyper::method::Method; +///# use hyper::uri::RequestUri; +///# use hyper::status::StatusCode; +///# fn main() { +/// +///let mut core = Core::new().unwrap(); +///let mut input = Vec::new(); +///Message::text("50 schmeckels").serialize(&mut input, false); +/// +///let f = ReadWritePair(Cursor::new(input), Cursor::new(vec![])) +/// .framed(MessageCodec::default(MsgCodecCtx::Client)) +/// .into_future() +/// .map_err(|e| e.0) +/// .map(|(m, _)| { +/// assert_eq!(m, Some(OwnedMessage::Text("50 schmeckels".to_string()))); +/// }); +/// +///core.run(f).unwrap(); +///# } pub struct MessageCodec where M: MessageTrait { @@ -111,6 +203,15 @@ pub struct MessageCodec } impl MessageCodec { + /// Create a new `MessageCodec` with a role of `context` (either `Client` + /// or `Server`) to read and write messages asynchronously. + /// + /// This will create the crate's default codec which sends and receives + /// `OwnedMessage` structs. The message data has to be sent to an intermediate + /// buffer anyway so sending owned data is preferable. + /// + /// If you have your own implementation of websocket messages, you can + /// use the `new` method to create a codec for that implementation. pub fn default(context: Context) -> Self { Self::new(context) } @@ -119,6 +220,11 @@ impl MessageCodec { impl MessageCodec where M: MessageTrait { + /// Creates a codec that can encode a custom implementation of a websocket + /// message. + /// + /// If you just want to use a normal codec without a specific implementation + /// of a websocket message, take a look at `MessageCodec::default`. pub fn new(context: Context) -> MessageCodec { MessageCodec { buffer: Vec::new(), @@ -185,3 +291,125 @@ impl Encoder for MessageCodec } // TODO: add tests to check boundary cases for reading dataframes + +#[cfg(test)] +mod tests { + use super::*; + use tokio_io::AsyncRead; + use tokio_core::reactor::Core; + use futures::{Stream, Sink, Future}; + use std::io::Cursor; + use stream::ReadWritePair; + use message::CloseData; + use message::Message; + + #[test] + fn owned_message_predicts_size() { + let messages = vec![ + OwnedMessage::Text("nilbog".to_string()), + OwnedMessage::Binary(vec![1, 2, 3, 4]), + OwnedMessage::Binary(vec![42; 256]), + OwnedMessage::Binary(vec![42; 65535]), + OwnedMessage::Binary(vec![42; 65555]), + OwnedMessage::Ping("beep".to_string().into_bytes()), + OwnedMessage::Pong("boop".to_string().into_bytes()), + OwnedMessage::Close(None), + OwnedMessage::Close(Some(CloseData { + status_code: 64, + reason: "because".to_string(), + })), + ]; + + for message in messages.into_iter() { + let masked_predicted = message.message_size(true); + let mut masked_buf = Vec::new(); + message.serialize(&mut masked_buf, true).unwrap(); + assert_eq!(masked_buf.len(), masked_predicted); + + let unmasked_predicted = message.message_size(false); + let mut unmasked_buf = Vec::new(); + message.serialize(&mut unmasked_buf, false).unwrap(); + assert_eq!(unmasked_buf.len(), unmasked_predicted); + } + } + + #[test] + fn cow_message_predicts_size() { + let messages = vec![ + Message::binary(vec![1, 2, 3, 4]), + Message::binary(vec![42; 256]), + Message::binary(vec![42; 65535]), + Message::binary(vec![42; 65555]), + Message::text("nilbog".to_string()), + Message::ping("beep".to_string().into_bytes()), + Message::pong("boop".to_string().into_bytes()), + Message::close(), + Message::close_because(64, "because"), + ]; + + for message in messages.iter() { + let masked_predicted = message.message_size(true); + let mut masked_buf = Vec::new(); + message.serialize(&mut masked_buf, true).unwrap(); + assert_eq!(masked_buf.len(), masked_predicted); + + let unmasked_predicted = message.message_size(false); + let mut unmasked_buf = Vec::new(); + message.serialize(&mut unmasked_buf, false).unwrap(); + assert_eq!(unmasked_buf.len(), unmasked_predicted); + } + } + + #[test] + fn message_codec_client_send_receive() { + let mut core = Core::new().unwrap(); + let mut input = Vec::new(); + Message::text("50 schmeckels").serialize(&mut input, false); + + let f = ReadWritePair(Cursor::new(input), Cursor::new(vec![])) + .framed(MessageCodec::new(Context::Client)) + .into_future() + .map_err(|e| e.0) + .map(|(m, s)| { + assert_eq!(m, Some(OwnedMessage::Text("50 schmeckels".to_string()))); + s + }) + .and_then(|s| s.send(Message::text("ethan bradberry"))) + .map(|s| { + let stream = s.into_parts().inner; + ReadWritePair(stream.1, stream.0) + .framed(MessageCodec::default(Context::Client)) + .into_future() + .map_err(|e| e.0) + .map(|(message, _)| { + assert_eq!(message, Some(Message::text("ethan bradberry").into())) + }); + }); + + core.run(f).unwrap(); + } + + #[test] + fn message_codec_server_send_receive() { + let mut core = Core::new().unwrap(); + let mut input = Vec::new(); + Message::text("50 schmeckels").serialize(&mut input, true); + + let f = ReadWritePair(Cursor::new(input.as_slice()), Cursor::new(vec![])) + .framed(MessageCodec::new(Context::Server)) + .into_future() + .map_err(|e| e.0) + .map(|(m, s)| { + assert_eq!(m, Some(OwnedMessage::Text("50 schmeckels".to_string()))); + s + }) + .and_then(|s| s.send(Message::text("ethan bradberry"))) + .map(|s| { + let mut written = vec![]; + Message::text("ethan bradberry").serialize(&mut written, false); + assert_eq!(written, s.into_parts().inner.1.into_inner()); + }); + + core.run(f).unwrap(); + } +} diff --git a/src/lib.rs b/src/lib.rs index a12356f1ae..80dd326b52 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -125,7 +125,7 @@ pub mod sync { pub mod async { pub use codec; pub use codec::ws::MessageCodec; - pub use codec::ws::Context as MessageContext; + pub use codec::ws::Context as MsgCodecCtx; pub use codec::http::HttpClientCodec; pub use codec::http::HttpServerCodec; diff --git a/src/server/sync.rs b/src/server/sync.rs index f6f998b7fc..ff02388e4c 100644 --- a/src/server/sync.rs +++ b/src/server/sync.rs @@ -92,13 +92,13 @@ pub type AcceptResult = Result, InvalidConnection>; /// # A Hyper Server /// This crates comes with hyper integration out of the box, you can create a hyper /// server and serve websocket and HTTP **on the same port!** -/// check out the docs over at `websocket::server::upgrade::from_hyper` for an example. +/// check out the docs over at `websocket::server::upgrade::sync::HyperRequest` for an example. /// /// # A Custom Server /// So you don't want to use any of our server implementations? That's O.K. /// All it takes is implementing the `IntoWs` trait for your server's streams, /// then calling `.into_ws()` on them. -/// check out the docs over at `websocket::server::upgrade` for more. +/// check out the docs over at `websocket::server::upgrade::sync` for more. pub type Server = WsServer; impl WsServer diff --git a/src/server/upgrade/async.rs b/src/server/upgrade/async.rs index c32df3eb6f..ad1c9afa31 100644 --- a/src/server/upgrade/async.rs +++ b/src/server/upgrade/async.rs @@ -1,3 +1,10 @@ +//! Allows taking an existing stream of data and asynchronously convert it to a +//! websocket client. +//! +//! This module contains the trait that transforms stream into +//! an intermediate struct called `Upgrade` and the `Upgrade` struct itself. +//! The `Upgrade` struct is used to inspect details of the websocket connection +//! (e.g. what protocols it wants to use) and decide wether to accept or reject it. use super::{HyperIntoWsError, WsUpgrade, Request, validate}; use std::io::{self, ErrorKind}; use tokio_io::codec::{Framed, FramedParts}; @@ -13,16 +20,63 @@ use codec::ws::{MessageCodec, Context}; use bytes::BytesMut; use client::async::ClientNew; +/// An asynchronous websocket upgrade. +/// +/// This struct is given when a connection is being upgraded to a websocket +/// request. It implements everything that a normal `WsUpgrade` struct does +/// along with the final functions to create websocket clients (although this +/// is done asynchronously). +/// +/// # Example +/// +/// ```rust,no_run +/// use websocket::async::{Core, TcpListener, TcpStream}; +/// use websocket::async::futures::{Stream, Future}; +/// use websocket::async::server::upgrade::IntoWs; +/// use websocket::sync::Client; +/// +/// let mut core = Core::new().unwrap(); +/// let handle = core.handle(); +/// let addr = "127.0.0.1:80".parse().unwrap(); +/// let listener = TcpListener::bind(&addr, &handle).unwrap(); +/// +/// let websocket_clients = listener +/// .incoming().map_err(|e| e.into()) +/// .and_then(|(stream, _)| stream.into_ws().map_err(|e| e.3)) +/// .map(|upgrade| { +/// if upgrade.protocols().iter().any(|p| p == "super-cool-proto") { +/// let accepted = upgrade +/// .use_protocol("super-cool-proto") +/// .accept() +/// .map(|_| ()).map_err(|_| ()); +/// +/// handle.spawn(accepted); +/// } else { +/// let rejected = upgrade.reject() +/// .map(|_| ()).map_err(|_| ()); +/// +/// handle.spawn(rejected); +/// } +/// }); +/// ``` pub type Upgrade = WsUpgrade; -/// TODO: docs, THIS IS THTE ASYNC ONE +/// These are the extra functions given to `WsUpgrade` with the `async` feature +/// turned on. A type alias for this specialization of `WsUpgrade` lives in this +/// module under the name `Upgrade`. impl WsUpgrade where S: Stream + 'static { + /// Asynchronously accept the websocket handshake, then create a client. + /// This will asynchronously send a response accepting the connection + /// and create a websocket client. pub fn accept(self) -> ClientNew { self.internal_accept(None) } + /// Asynchronously accept the websocket handshake, then create a client. + /// This will asynchronously send a response accepting the connection + /// with custom headers in the response and create a websocket client. pub fn accept_with(self, custom_headers: &Headers) -> ClientNew { self.internal_accept(Some(custom_headers)) } @@ -52,10 +106,17 @@ impl WsUpgrade Box::new(future) } + /// Asynchronously send a rejection message and deconstruct `self` + /// into it's original stream. The stream being returned is framed with the + /// `HttpServerCodec` since that was used to send the rejection message. pub fn reject(self) -> Send> { self.internal_reject(None) } + /// Asynchronously send a rejection message with custom headers and + /// deconstruct `self` into it's original stream. + /// The stream being returned is framed with the + /// `HttpServerCodec` since that was used to send the rejection message. pub fn reject_with(self, headers: &Headers) -> Send> { self.internal_reject(Some(headers)) } @@ -79,6 +140,52 @@ impl WsUpgrade } +/// Trait to take a stream or similar and attempt to recover the start of a +/// websocket handshake from it (asynchronously). +/// Should be used when a stream might contain a request for a websocket session. +/// +/// If an upgrade request can be parsed, one can accept or deny the handshake with +/// the `WsUpgrade` struct. +/// Otherwise the original stream is returned along with an error. +/// +/// Note: the stream is owned because the websocket client expects to own its stream. +/// +/// This is already implemented for all async streams, which means all types with +/// `AsyncRead + AsyncWrite + 'static` (`'static` because the future wants to own +/// the stream). +/// +/// # Example +/// +/// ```rust,no_run +/// use websocket::async::{Core, TcpListener, TcpStream}; +/// use websocket::async::futures::{Stream, Future}; +/// use websocket::async::server::upgrade::IntoWs; +/// use websocket::sync::Client; +/// +/// let mut core = Core::new().unwrap(); +/// let handle = core.handle(); +/// let addr = "127.0.0.1:80".parse().unwrap(); +/// let listener = TcpListener::bind(&addr, &handle).unwrap(); +/// +/// let websocket_clients = listener +/// .incoming().map_err(|e| e.into()) +/// .and_then(|(stream, _)| stream.into_ws().map_err(|e| e.3)) +/// .map(|upgrade| { +/// if upgrade.protocols().iter().any(|p| p == "super-cool-proto") { +/// let accepted = upgrade +/// .use_protocol("super-cool-proto") +/// .accept() +/// .map(|_| ()).map_err(|_| ()); +/// +/// handle.spawn(accepted); +/// } else { +/// let rejected = upgrade.reject() +/// .map(|_| ()).map_err(|_| ()); +/// +/// handle.spawn(rejected); +/// } +/// }); +/// ``` pub trait IntoWs { /// The type of stream this upgrade process is working with (TcpStream, etc.) type Stream: Stream; diff --git a/src/server/upgrade/mod.rs b/src/server/upgrade/mod.rs index 5beb414673..55637278fa 100644 --- a/src/server/upgrade/mod.rs +++ b/src/server/upgrade/mod.rs @@ -31,6 +31,10 @@ pub type Request = Incoming<(Method, RequestUri)>; /// /// Users should then call `accept` or `reject` to complete the handshake /// and start a session. +/// Note: if the stream in use is `AsyncRead + AsyncWrite`, then asynchronous +/// functions will be available when completing the handshake. +/// Otherwise if the stream is simply `Read + Write` blocking functions will be +/// available to complete the handshake. pub struct WsUpgrade where S: Stream { diff --git a/src/server/upgrade/sync.rs b/src/server/upgrade/sync.rs index e16bfb8679..7504b61ad7 100644 --- a/src/server/upgrade/sync.rs +++ b/src/server/upgrade/sync.rs @@ -37,8 +37,13 @@ pub struct Buffer { /// (the request should be a handshake). pub struct RequestStreamPair(pub S, pub Request); +/// The synchronous specialization of `WsUpgrade`. +/// See the `WsUpgrade` docs for usage and the extra synchronous methods +/// given by this specialization. pub type Upgrade = WsUpgrade>; +/// These methods are the synchronous ways of accepting and rejecting a websocket +/// handshake. impl WsUpgrade> where S: Stream { From 2307070adf62e276afb6fbd801a73ff838073ab4 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Sun, 28 May 2017 19:25:45 -0400 Subject: [PATCH 51/52] Finished docs and updated tests, removed warnings. --- autobahn/client-results.json | 12 +-- autobahn/server-results.json | 10 +-- scripts/autobahn-client.sh | 3 +- scripts/autobahn-server.sh | 3 +- src/client/builder.rs | 50 +++++++----- src/codec/ws.rs | 16 ++-- src/lib.rs | 40 +++++----- src/server/async.rs | 42 ++++++++-- src/server/mod.rs | 41 +++++++++- src/server/sync.rs | 143 +++++++++++++++++------------------ src/server/upgrade/mod.rs | 6 +- src/stream.rs | 98 +++++++++++++----------- 12 files changed, 277 insertions(+), 187 deletions(-) diff --git a/autobahn/client-results.json b/autobahn/client-results.json index 987591f7be..0ac6c056f8 100644 --- a/autobahn/client-results.json +++ b/autobahn/client-results.json @@ -1723,7 +1723,7 @@ "reportfile": "rust_websocket_case_3_2.json" }, "3.3": { - "behavior": "NON-STRICT", + "behavior": "OK", "behaviorClose": "OK", "duration": 2, "remoteCloseCode": null, @@ -2773,28 +2773,28 @@ "reportfile": "rust_websocket_case_6_3_2.json" }, "6.4.1": { - "behavior": "NON-STRICT", + "behavior": "OK", "behaviorClose": "OK", "duration": 2002, "remoteCloseCode": null, "reportfile": "rust_websocket_case_6_4_1.json" }, "6.4.2": { - "behavior": "NON-STRICT", + "behavior": "OK", "behaviorClose": "OK", "duration": 2002, "remoteCloseCode": null, "reportfile": "rust_websocket_case_6_4_2.json" }, "6.4.3": { - "behavior": "NON-STRICT", + "behavior": "OK", "behaviorClose": "OK", "duration": 2003, "remoteCloseCode": null, "reportfile": "rust_websocket_case_6_4_3.json" }, "6.4.4": { - "behavior": "NON-STRICT", + "behavior": "OK", "behaviorClose": "OK", "duration": 2002, "remoteCloseCode": null, @@ -3634,4 +3634,4 @@ "reportfile": "rust_websocket_case_9_8_6.json" } } -} \ No newline at end of file +} diff --git a/autobahn/server-results.json b/autobahn/server-results.json index 2ec851ec70..4b1ea8df0a 100644 --- a/autobahn/server-results.json +++ b/autobahn/server-results.json @@ -2773,28 +2773,28 @@ "reportfile": "rust_websocket_case_6_3_2.json" }, "6.4.1": { - "behavior": "NON-STRICT", + "behavior": "OK", "behaviorClose": "OK", "duration": 2002, "remoteCloseCode": null, "reportfile": "rust_websocket_case_6_4_1.json" }, "6.4.2": { - "behavior": "NON-STRICT", + "behavior": "OK", "behaviorClose": "OK", "duration": 2001, "remoteCloseCode": null, "reportfile": "rust_websocket_case_6_4_2.json" }, "6.4.3": { - "behavior": "NON-STRICT", + "behavior": "OK", "behaviorClose": "OK", "duration": 2003, "remoteCloseCode": null, "reportfile": "rust_websocket_case_6_4_3.json" }, "6.4.4": { - "behavior": "NON-STRICT", + "behavior": "OK", "behaviorClose": "OK", "duration": 2003, "remoteCloseCode": null, @@ -3634,4 +3634,4 @@ "reportfile": "rust_websocket_case_9_8_6.json" } } -} \ No newline at end of file +} diff --git a/scripts/autobahn-client.sh b/scripts/autobahn-client.sh index 42f2a0440f..a9ec1d011f 100755 --- a/scripts/autobahn-client.sh +++ b/scripts/autobahn-client.sh @@ -16,8 +16,9 @@ wstest -m fuzzingserver -s 'autobahn/fuzzingserver.json' & \ sleep 10 function test_diff() { + SAVED_RESULTS=$(sed 's/NON-STRICT/OK/g' autobahn/client-results.json) DIFF=$(diff \ - <(jq -S 'del(."rust-websocket" | .. | .duration?)' 'autobahn/client-results.json') \ + <(jq -S 'del(."rust-websocket" | .. | .duration?)' "$SAVED_RESULTS") \ <(jq -S 'del(."rust-websocket" | .. | .duration?)' 'autobahn/client/index.json') ) if [[ $DIFF ]]; then diff --git a/scripts/autobahn-server.sh b/scripts/autobahn-server.sh index cb454e9ddc..8a85bc98fa 100755 --- a/scripts/autobahn-server.sh +++ b/scripts/autobahn-server.sh @@ -13,8 +13,9 @@ function cleanup() { trap cleanup TERM EXIT function test_diff() { + SAVED_RESULTS=$(sed 's/NON-STRICT/OK/g' autobahn/server-results.json) DIFF=$(diff \ - <(jq -S 'del(."rust-websocket" | .. | .duration?)' 'autobahn/server-results.json') \ + <(jq -S 'del(."rust-websocket" | .. | .duration?)' "$SAVED_RESULTS") \ <(jq -S 'del(."rust-websocket" | .. | .duration?)' 'autobahn/server/index.json') ) if [[ $DIFF ]]; then diff --git a/src/client/builder.rs b/src/client/builder.rs index 2f7ebde353..708b63e2fc 100644 --- a/src/client/builder.rs +++ b/src/client/builder.rs @@ -1,26 +1,32 @@ //! Everything you need to create a client connection to a websocket. use std::borrow::Cow; -use std::net::TcpStream; -use std::net::ToSocketAddrs; pub use url::{Url, ParseError}; -use url::Position; -use hyper::version::HttpVersion; -use hyper::http::h1::Incoming; -use hyper::http::RawStatus; -use hyper::status::StatusCode; -use hyper::buffer::BufReader; -use hyper::method::Method; -use hyper::uri::RequestUri; -use hyper::http::h1::parse_response; -use hyper::header::{Headers, Header, HeaderFormat, Host, Connection, ConnectionOption, Upgrade, - Protocol, ProtocolName}; -use unicase::UniCase; use header::extensions::Extension; -use header::{WebSocketAccept, WebSocketKey, WebSocketVersion, WebSocketProtocol, - WebSocketExtensions, Origin}; -use result::{WSUrlErrorKind, WebSocketResult, WebSocketError}; -use stream::{self, Stream}; +use header::{WebSocketKey, WebSocketVersion, WebSocketProtocol, WebSocketExtensions, Origin}; +use hyper::header::{Headers, Header, HeaderFormat}; +use hyper::version::HttpVersion; + +#[cfg(any(feature="sync", feature="async"))] +mod common_imports { + pub use std::net::TcpStream; + pub use std::net::ToSocketAddrs; + pub use url::Position; + pub use hyper::http::h1::Incoming; + pub use hyper::http::RawStatus; + pub use hyper::status::StatusCode; + pub use hyper::buffer::BufReader; + pub use hyper::method::Method; + pub use hyper::uri::RequestUri; + pub use hyper::http::h1::parse_response; + pub use hyper::header::{Host, Connection, ConnectionOption, Upgrade, Protocol, ProtocolName}; + pub use unicase::UniCase; + pub use header::WebSocketAccept; + pub use result::{WSUrlErrorKind, WebSocketResult, WebSocketError}; + pub use stream::{self, Stream}; +} +#[cfg(any(feature="sync", feature="async"))] +use self::common_imports::*; #[cfg(feature="sync")] use super::sync::Client; @@ -29,7 +35,9 @@ use super::sync::Client; use stream::sync::NetworkStream; #[cfg(any(feature="sync-ssl", feature="async-ssl"))] -use native_tls::{TlsStream, TlsConnector}; +use native_tls::TlsConnector; +#[cfg(feature="sync-ssl")] +use native_tls::TlsStream; #[cfg(feature="async")] mod async_imports { @@ -819,6 +827,7 @@ impl<'u> ClientBuilder<'u> { Ok(async::TcpStream::connect(&address, handle)) } + #[cfg(any(feature="sync", feature="async"))] fn build_request(&mut self) -> String { // enter host if available (unix sockets don't have hosts) if let Some(host) = self.url.host_str() { @@ -855,6 +864,7 @@ impl<'u> ClientBuilder<'u> { resource } + #[cfg(any(feature="sync", feature="async"))] fn validate(&self, response: &Incoming) -> WebSocketResult<()> { let status = StatusCode::from_u16(response.subject.0); @@ -890,6 +900,7 @@ impl<'u> ClientBuilder<'u> { Ok(()) } + #[cfg(any(feature="sync", feature="async"))] fn extract_host_port(&self, secure: Option) -> WebSocketResult<(&str, u16)> { let port = match (self.url.port(), secure) { (Some(port), _) => port, @@ -906,6 +917,7 @@ impl<'u> ClientBuilder<'u> { Ok((host, port)) } + #[cfg(feature="sync")] fn establish_tcp(&mut self, secure: Option) -> WebSocketResult { Ok(TcpStream::connect(self.extract_host_port(secure)?)?) } diff --git a/src/codec/ws.rs b/src/codec/ws.rs index 26d13ff82b..df9680ae15 100644 --- a/src/codec/ws.rs +++ b/src/codec/ws.rs @@ -364,7 +364,7 @@ mod tests { fn message_codec_client_send_receive() { let mut core = Core::new().unwrap(); let mut input = Vec::new(); - Message::text("50 schmeckels").serialize(&mut input, false); + Message::text("50 schmeckels").serialize(&mut input, false).unwrap(); let f = ReadWritePair(Cursor::new(input), Cursor::new(vec![])) .framed(MessageCodec::new(Context::Client)) @@ -375,15 +375,17 @@ mod tests { s }) .and_then(|s| s.send(Message::text("ethan bradberry"))) - .map(|s| { - let stream = s.into_parts().inner; + .and_then(|s| { + let mut stream = s.into_parts().inner; + stream.1.set_position(0); + println!("buffer: {:?}", stream.1); ReadWritePair(stream.1, stream.0) - .framed(MessageCodec::default(Context::Client)) + .framed(MessageCodec::default(Context::Server)) .into_future() .map_err(|e| e.0) .map(|(message, _)| { assert_eq!(message, Some(Message::text("ethan bradberry").into())) - }); + }) }); core.run(f).unwrap(); @@ -393,7 +395,7 @@ mod tests { fn message_codec_server_send_receive() { let mut core = Core::new().unwrap(); let mut input = Vec::new(); - Message::text("50 schmeckels").serialize(&mut input, true); + Message::text("50 schmeckels").serialize(&mut input, true).unwrap(); let f = ReadWritePair(Cursor::new(input.as_slice()), Cursor::new(vec![])) .framed(MessageCodec::new(Context::Server)) @@ -406,7 +408,7 @@ mod tests { .and_then(|s| s.send(Message::text("ethan bradberry"))) .map(|s| { let mut written = vec![]; - Message::text("ethan bradberry").serialize(&mut written, false); + Message::text("ethan bradberry").serialize(&mut written, false).unwrap(); assert_eq!(written, s.into_parts().inner.1.into_inner()); }); diff --git a/src/lib.rs b/src/lib.rs index 80dd326b52..64b7685410 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,33 +5,25 @@ //! Rust-WebSocket is a WebSocket (RFC6455) library written in Rust. //! -//! # Clients -//! WebSocket clients make use of the `Client` object, which features two connection -//! functions: `connect()` and `connect_ssl_context()`. See the `Client` struct -//! documentation for more information. These both return a client-side `Request` -//! object which is sent to the server with the `send()` method. The `Request` can -//! be altered, typically using `Request.headers.set()` to add additional headers -//! or change existing ones before calling `send()`. +//! # Synchronous and Asynchronous +//! This crate has both async and sync implementations of websockets, you are free to +//! choose which one you would like to use by switching on the `async` or `sync` features +//! for this crate. By default both are switched on since they do not conflict with each +//! other. +//! +//! You'll find many modules with `::sync` and `::async` submodules that separate these +//! behaviours. Since it get's tedious to add these on when appropriate a top-level +//! convenience module called `websocket::sync` and `websocket::async` has been added that +//! groups all the sync and async stuff, respectively. //! -//! Calling `send()` on a `Request` will obtain a `Response`, which can be checked -//! with the `validate()` method, which will return `Ok(())` if the response is a -//! valid one. Data frames and messages can then be sent by obtaining a `Client` -//! object with `begin()`. +//! # Clients +//! To make a client use the `ClientBuilder` struct, this builder has methods +//! for creating both synchronous and asynchronous clients. //! //! # Servers //! WebSocket servers act similarly to the `TcpListener`, and listen for connections. //! See the `Server` struct documentation for more information. The `bind()` and //! `bind_secure()` functions will bind the server to the given `SocketAddr`. -//! `Server` implements Iterator and can be used to iterate over incoming `Request` -//! items. -//! -//! Requests can be validated using `validate()`, and other parts of the request may -//! be examined (e.g. the Host header and/or the Origin header). A call to `accept()` -//! or `fail()` will generate a `Response` which either accepts the connection, or -//! denies it respectively. -//! -//! A `Response` can then be altered if necessary, and is sent with the 'send()` -//! method, returning a `Client` ready to send and receive data frames or messages. //! //! # Extending Rust-WebSocket //! The `ws` module contains the traits and functions used by Rust-WebSockt at a lower @@ -95,6 +87,7 @@ pub mod client; pub mod server; pub mod stream; +/// A collection of handy synchronous-only parts of the crate. #[cfg(feature="sync")] pub mod sync { pub use sender; @@ -106,6 +99,7 @@ pub mod sync { pub use stream::sync::Stream; pub use stream::sync as stream; + /// A collection of handy synchronous-only parts of the `server` module. pub mod server { pub use server::sync::*; pub use server::upgrade::sync::Upgrade; @@ -114,6 +108,7 @@ pub mod sync { } pub use server::sync::Server; + /// A collection of handy synchronous-only parts of the `client` module. pub mod client { pub use client::sync::*; pub use client::builder::ClientBuilder; @@ -121,6 +116,7 @@ pub mod sync { pub use client::sync::Client; } +/// A collection of handy asynchronous-only parts of the crate. #[cfg(feature="async")] pub mod async { pub use codec; @@ -132,6 +128,7 @@ pub mod async { pub use stream::async::Stream; pub use stream::async as stream; + /// A collection of handy asynchronous-only parts of the `server` module. pub mod server { pub use server::async::*; pub use server::upgrade::async::Upgrade; @@ -140,6 +137,7 @@ pub mod async { } pub use server::async::Server; + /// A collection of handy asynchronous-only parts of the `client` module. pub mod client { pub use client::async::*; pub use client::builder::ClientBuilder; diff --git a/src/server/async.rs b/src/server/async.rs index 17e6604efa..832461b6b8 100644 --- a/src/server/async.rs +++ b/src/server/async.rs @@ -1,3 +1,4 @@ +//! The asynchronous implementation of a websocket server. use std::io; use std::net::ToSocketAddrs; use std::net::SocketAddr; @@ -14,17 +15,23 @@ use native_tls::TlsAcceptor; #[cfg(any(feature="async-ssl"))] use tokio_tls::{TlsAcceptorExt, TlsStream}; +/// The asynchronous specialization of a websocket server. +/// Use this struct to create asynchronous servers. pub type Server = WsServer; +/// A stream of websocket connections and addresses the server generates. +/// +/// Each item of the stream is the address of the incoming connection and an `Upgrade` +/// struct which lets the user decide wether to turn the connection into a websocket +/// connection or reject it. pub type Incoming = Box, SocketAddr), Error = InvalidConnection>>; -pub enum AcceptError { - Io(io::Error), - Upgrade(E), -} - +/// Asynchronous methods for creating an async server and accepting incoming connections. impl WsServer { + /// Bind a websocket server to an address. + /// Creating a websocket server can be done immediately so this does not + /// return a `Future` but a simple `Result`. pub fn bind(addr: A, handle: &Handle) -> io::Result { let tcp = ::std::net::TcpListener::bind(addr)?; let address = tcp.local_addr()?; @@ -34,6 +41,15 @@ impl WsServer { }) } + /// Turns the server into a stream of connection objects. + /// + /// Each item of the stream is the address of the incoming connection and an `Upgrade` + /// struct which lets the user decide wether to turn the connection into a websocket + /// connection or reject it. + /// + /// See the [`examples/async-server.rs`] + /// (https://github.com/cyderize/rust-websocket/blob/master/examples/async-server.rs) + /// example for a good echo server example. pub fn incoming(self) -> Incoming { let future = self.listener .incoming() @@ -61,8 +77,15 @@ impl WsServer { } } +/// Asynchronous methods for creating an async SSL server and accepting incoming connections. #[cfg(any(feature="async-ssl"))] impl WsServer { + /// Bind an SSL websocket server to an address. + /// Creating a websocket server can be done immediately so this does not + /// return a `Future` but a simple `Result`. + /// + /// Since this is an SSL server one needs to provide a `TlsAcceptor` that contains + /// the server's SSL information. pub fn bind_secure( addr: A, acceptor: TlsAcceptor, @@ -76,6 +99,15 @@ impl WsServer { }) } + /// Turns the server into a stream of connection objects. + /// + /// Each item of the stream is the address of the incoming connection and an `Upgrade` + /// struct which lets the user decide wether to turn the connection into a websocket + /// connection or reject it. + /// + /// See the [`examples/async-server.rs`] + /// (https://github.com/cyderize/rust-websocket/blob/master/examples/async-server.rs) + /// example for a good echo server example. pub fn incoming(self) -> Incoming> { let acceptor = self.ssl_acceptor; let future = self.listener diff --git a/src/server/mod.rs b/src/server/mod.rs index 9983cf705d..6db12ef484 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -47,9 +47,48 @@ pub struct InvalidConnection pub error: HyperIntoWsError, } +/// Represents a WebSocket server which can work with either normal +/// (non-secure) connections, or secure WebSocket connections. +/// +/// This is a convenient way to implement WebSocket servers, however +/// it is possible to use any sendable Reader and Writer to obtain +/// a WebSocketClient, so if needed, an alternative server implementation can be used. +/// +/// # Synchronous Servers +/// Synchronous implementations of a websocket server are available below, each method is +/// documented so the reader knows wether is it synchronous or asynchronous. +/// +/// To use the synchronous implementation, you must have the `sync` feature enabled +/// (it is enabled by default). +/// To use the synchronous SSL implementation, you must have the `sync-ssl` feature enabled +/// (it is enabled by default). +/// +/// # Asynchronous Servers +/// Asynchronous implementations of a websocket server are available below, each method is +/// documented so the reader knows wether is it synchronous or asynchronous. +/// Simply look out for the implementation of `Server` whose methods only return `Future`s +/// (it is also written in the docs if the method is async). +/// +/// To use the asynchronous implementation, you must have the `async` feature enabled +/// (it is enabled by default). +/// To use the asynchronous SSL implementation, you must have the `async-ssl` feature enabled +/// (it is enabled by default). +/// +/// # A Hyper Server +/// This crates comes with hyper integration out of the box, you can create a hyper +/// server and serve websocket and HTTP **on the same port!** +/// check out the docs over at `websocket::server::upgrade::sync::HyperRequest` for an example. +/// +/// # A Custom Server +/// So you don't want to use any of our server implementations? That's O.K. +/// All it takes is implementing the `IntoWs` trait for your server's streams, +/// then calling `.into_ws()` on them. +/// check out the docs over at `websocket::server::upgrade::sync` for more. +#[cfg(any(feature="sync", feature="async"))] pub struct WsServer where S: OptionalTlsAcceptor { listener: L, - ssl_acceptor: S, + /// The SSL acceptor given to the server + pub ssl_acceptor: S, } diff --git a/src/server/sync.rs b/src/server/sync.rs index ff02388e4c..3630af2f2a 100644 --- a/src/server/sync.rs +++ b/src/server/sync.rs @@ -26,81 +26,9 @@ pub type AcceptResult = Result, InvalidConnection>; /// This is a convenient way to implement WebSocket servers, however /// it is possible to use any sendable Reader and Writer to obtain /// a WebSocketClient, so if needed, an alternative server implementation can be used. -///# Non-secure Servers -/// -/// ```no_run -///extern crate websocket; -///# fn main() { -///use std::thread; -///use websocket::Message; -///use websocket::sync::Server; -/// -///let server = Server::bind("127.0.0.1:1234").unwrap(); -/// -///for connection in server.filter_map(Result::ok) { -/// // Spawn a new thread for each connection. -/// thread::spawn(move || { -/// let mut client = connection.accept().unwrap(); -/// -/// let message = Message::text("Hello, client!"); -/// let _ = client.send_message(&message); -/// -/// // ... -/// }); -///} -/// # } -/// ``` -/// -///# Secure Servers -/// ```no_run -///extern crate websocket; -///extern crate native_tls; -///# fn main() { -///use std::thread; -///use std::io::Read; -///use std::fs::File; -///use websocket::Message; -///use websocket::sync::Server; -///use native_tls::{Pkcs12, TlsAcceptor}; -/// -///// In this example we retrieve our keypair and certificate chain from a PKCS #12 archive, -///// but but they can also be retrieved from, for example, individual PEM- or DER-formatted -///// files. See the documentation for the `PKey` and `X509` types for more details. -///let mut file = File::open("identity.pfx").unwrap(); -///let mut pkcs12 = vec![]; -///file.read_to_end(&mut pkcs12).unwrap(); -///let pkcs12 = Pkcs12::from_der(&pkcs12, "hacktheplanet").unwrap(); -/// -///let acceptor = TlsAcceptor::builder(pkcs12).unwrap().build().unwrap(); -/// -///let server = Server::bind_secure("127.0.0.1:1234", acceptor).unwrap(); -/// -///for connection in server.filter_map(Result::ok) { -/// // Spawn a new thread for each connection. -/// thread::spawn(move || { -/// let mut client = connection.accept().unwrap(); -/// -/// let message = Message::text("Hello, client!"); -/// let _ = client.send_message(&message); -/// -/// // ... -/// }); -///} -/// # } -/// ``` -/// -/// # A Hyper Server -/// This crates comes with hyper integration out of the box, you can create a hyper -/// server and serve websocket and HTTP **on the same port!** -/// check out the docs over at `websocket::server::upgrade::sync::HyperRequest` for an example. -/// -/// # A Custom Server -/// So you don't want to use any of our server implementations? That's O.K. -/// All it takes is implementing the `IntoWs` trait for your server's streams, -/// then calling `.into_ws()` on them. -/// check out the docs over at `websocket::server::upgrade::sync` for more. pub type Server = WsServer; +/// Synchronous methods for creating a server and accepting incoming connections. impl WsServer where S: OptionalTlsAcceptor { @@ -116,7 +44,6 @@ impl WsServer /// If it is in nonblocking mode, accept() will return an error instead of /// blocking when there are no incoming connections. /// - ///# Examples ///```no_run /// # extern crate websocket; /// # use websocket::sync::Server; @@ -152,6 +79,10 @@ impl WsServer self.listener.set_nonblocking(nonblocking) } + /// Turns an existing synchronous server into an asynchronous one. + /// This will only work if the stream used for this server `S` already implements + /// `AsyncRead + AsyncWrite`. Useful if you would like some blocking things to happen + /// at the start of your server. #[cfg(feature="async")] pub fn into_async(self, handle: &Handle) -> io::Result> { let addr = self.listener.local_addr()?; @@ -162,9 +93,48 @@ impl WsServer } } +/// Synchronous methods for creating an SSL server and accepting incoming connections. #[cfg(feature="sync-ssl")] impl WsServer { /// Bind this Server to this socket, utilising the given SslContext + /// + /// # Secure Servers + /// ```no_run + /// extern crate websocket; + /// extern crate native_tls; + /// # fn main() { + /// use std::thread; + /// use std::io::Read; + /// use std::fs::File; + /// use websocket::Message; + /// use websocket::sync::Server; + /// use native_tls::{Pkcs12, TlsAcceptor}; + /// + /// // In this example we retrieve our keypair and certificate chain from a PKCS #12 archive, + /// // but but they can also be retrieved from, for example, individual PEM- or DER-formatted + /// // files. See the documentation for the `PKey` and `X509` types for more details. + /// let mut file = File::open("identity.pfx").unwrap(); + /// let mut pkcs12 = vec![]; + /// file.read_to_end(&mut pkcs12).unwrap(); + /// let pkcs12 = Pkcs12::from_der(&pkcs12, "hacktheplanet").unwrap(); + /// + /// let acceptor = TlsAcceptor::builder(pkcs12).unwrap().build().unwrap(); + /// + /// let server = Server::bind_secure("127.0.0.1:1234", acceptor).unwrap(); + /// + /// for connection in server.filter_map(Result::ok) { + /// // Spawn a new thread for each connection. + /// thread::spawn(move || { + /// let mut client = connection.accept().unwrap(); + /// + /// let message = Message::text("Hello, client!"); + /// let _ = client.send_message(&message); + /// + /// // ... + /// }); + /// } + /// # } + /// ``` pub fn bind_secure(addr: A, acceptor: TlsAcceptor) -> io::Result where A: ToSocketAddrs { @@ -225,6 +195,31 @@ impl Iterator for WsServer { impl WsServer { /// Bind this Server to this socket + /// + /// # Non-secure Servers + /// + /// ```no_run + /// extern crate websocket; + /// # fn main() { + /// use std::thread; + /// use websocket::Message; + /// use websocket::sync::Server; + /// + /// let server = Server::bind("127.0.0.1:1234").unwrap(); + /// + /// for connection in server.filter_map(Result::ok) { + /// // Spawn a new thread for each connection. + /// thread::spawn(move || { + /// let mut client = connection.accept().unwrap(); + /// + /// let message = Message::text("Hello, client!"); + /// let _ = client.send_message(&message); + /// + /// // ... + /// }); + /// } + /// # } + /// ``` pub fn bind(addr: A) -> io::Result { Ok(Server { listener: try!(TcpListener::bind(&addr)), diff --git a/src/server/upgrade/mod.rs b/src/server/upgrade/mod.rs index 55637278fa..41d847b1f9 100644 --- a/src/server/upgrade/mod.rs +++ b/src/server/upgrade/mod.rs @@ -12,10 +12,12 @@ use unicase::UniCase; use hyper::status::StatusCode; use hyper::http::h1::Incoming; use hyper::method::Method; -use hyper::version::HttpVersion; use hyper::uri::RequestUri; use hyper::header::{Headers, Upgrade, Protocol, ProtocolName, Connection, ConnectionOption}; +#[cfg(any(feature="sync", feature="async"))] +use hyper::version::HttpVersion; + #[cfg(feature="async")] pub mod async; @@ -122,6 +124,7 @@ impl WsUpgrade self.request.headers.get::().map(|o| &o.0 as &str) } + #[cfg(feature="sync")] fn send(&mut self, status: StatusCode) -> io::Result<()> { try!(write!(&mut self.stream, "{} {}\r\n", self.request.version, status)); try!(write!(&mut self.stream, "{}\r\n", self.headers)); @@ -227,6 +230,7 @@ impl From<::codec::http::HttpCodecError> for HyperIntoWsError { } } +#[cfg(any(feature="sync", feature="async"))] fn validate( method: &Method, version: &HttpVersion, diff --git a/src/stream.rs b/src/stream.rs index 66004bfdcb..2e86b4c1c9 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -1,6 +1,7 @@ //! Provides the default stream type for WebSocket connections. -use std::io::{Read, Write}; +use std::io::{self, Read, Write}; +use std::fmt::Arguments; /// Represents a stream that can be read from, and written to. /// This is an abstraction around readable and writable things to be able @@ -16,6 +17,51 @@ pub struct ReadWritePair(pub R, pub W) where R: Read, W: Write; +impl Read for ReadWritePair + where R: Read, + W: Write +{ + #[inline(always)] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.0.read(buf) + } + #[inline(always)] + fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { + self.0.read_to_end(buf) + } + #[inline(always)] + fn read_to_string(&mut self, buf: &mut String) -> io::Result { + self.0.read_to_string(buf) + } + #[inline(always)] + fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { + self.0.read_exact(buf) + } +} + +impl Write for ReadWritePair + where R: Read, + W: Write +{ + #[inline(always)] + fn write(&mut self, buf: &[u8]) -> io::Result { + self.1.write(buf) + } + #[inline(always)] + fn flush(&mut self) -> io::Result<()> { + self.1.flush() + } + #[inline(always)] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + self.1.write_all(buf) + } + #[inline(always)] + fn write_fmt(&mut self, fmt: Arguments) -> io::Result<()> { + self.1.write_fmt(fmt) + } +} + +/// A collection of traits and implementations for async streams. #[cfg(feature="async")] pub mod async { use std::io::{self, Read, Write}; @@ -25,6 +71,9 @@ pub mod async { pub use tokio_io::{AsyncWrite, AsyncRead}; pub use tokio_io::io::{ReadHalf, WriteHalf}; + /// A stream that can be read from and written to asynchronously. + /// This let's us abstract over many async streams like tcp, ssl, + /// udp, ssh, etc. pub trait Stream: AsyncRead + AsyncWrite {} impl Stream for S where S: AsyncRead + AsyncWrite {} @@ -33,6 +82,7 @@ pub mod async { W: Write { } + impl AsyncWrite for ReadWritePair where W: AsyncWrite, R: Read @@ -43,12 +93,12 @@ pub mod async { } } +/// A collection of traits and implementations for synchronous streams. #[cfg(feature="sync")] pub mod sync { pub use super::ReadWritePair; use std::io::{self, Read, Write}; use std::ops::Deref; - use std::fmt::Arguments; pub use std::net::TcpStream; pub use std::net::Shutdown; #[cfg(feature="sync-ssl")] @@ -126,48 +176,4 @@ pub mod sync { self.deref().as_tcp() } } - - impl Read for ReadWritePair - where R: Read, - W: Write - { - #[inline(always)] - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.0.read(buf) - } - #[inline(always)] - fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { - self.0.read_to_end(buf) - } - #[inline(always)] - fn read_to_string(&mut self, buf: &mut String) -> io::Result { - self.0.read_to_string(buf) - } - #[inline(always)] - fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { - self.0.read_exact(buf) - } - } - - impl Write for ReadWritePair - where R: Read, - W: Write - { - #[inline(always)] - fn write(&mut self, buf: &[u8]) -> io::Result { - self.1.write(buf) - } - #[inline(always)] - fn flush(&mut self) -> io::Result<()> { - self.1.flush() - } - #[inline(always)] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - self.1.write_all(buf) - } - #[inline(always)] - fn write_fmt(&mut self, fmt: Arguments) -> io::Result<()> { - self.1.write_fmt(fmt) - } - } } From b51db784d287013092169396698831953d1f0e59 Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Sun, 28 May 2017 19:28:57 -0400 Subject: [PATCH 52/52] fixup --- src/codec/http.rs | 3 ++- src/codec/ws.rs | 4 ++-- src/server/upgrade/mod.rs | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/codec/http.rs b/src/codec/http.rs index 6688e9b6fd..b801ee0705 100644 --- a/src/codec/http.rs +++ b/src/codec/http.rs @@ -299,7 +299,8 @@ mod tests { GET / HTTP/1.0\r\n\ Host: www.rust-lang.org\r\n\ \r\n\ - ".as_bytes(); + " + .as_bytes(); let input = Cursor::new(request); let output = Cursor::new(Vec::new()); diff --git a/src/codec/ws.rs b/src/codec/ws.rs index df9680ae15..5124b42be2 100644 --- a/src/codec/ws.rs +++ b/src/codec/ws.rs @@ -377,8 +377,8 @@ mod tests { .and_then(|s| s.send(Message::text("ethan bradberry"))) .and_then(|s| { let mut stream = s.into_parts().inner; - stream.1.set_position(0); - println!("buffer: {:?}", stream.1); + stream.1.set_position(0); + println!("buffer: {:?}", stream.1); ReadWritePair(stream.1, stream.0) .framed(MessageCodec::default(Context::Server)) .into_future() diff --git a/src/server/upgrade/mod.rs b/src/server/upgrade/mod.rs index 41d847b1f9..d1bb0ae42f 100644 --- a/src/server/upgrade/mod.rs +++ b/src/server/upgrade/mod.rs @@ -124,7 +124,7 @@ impl WsUpgrade self.request.headers.get::().map(|o| &o.0 as &str) } - #[cfg(feature="sync")] + #[cfg(feature="sync")] fn send(&mut self, status: StatusCode) -> io::Result<()> { try!(write!(&mut self.stream, "{} {}\r\n", self.request.version, status)); try!(write!(&mut self.stream, "{}\r\n", self.headers));