From 3a3657cfaf0c5b8723b7dfc9640c2c4c63fddab4 Mon Sep 17 00:00:00 2001 From: pyros2097 Date: Fri, 23 Feb 2018 12:39:19 +0530 Subject: [PATCH 01/84] Update qs_9.md --- guide/src/qs_9.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/src/qs_9.md b/guide/src/qs_9.md index dbca38384..70d1e018f 100644 --- a/guide/src/qs_9.md +++ b/guide/src/qs_9.md @@ -43,7 +43,7 @@ fn main() { ``` Simple websocket echo server example is available in -[examples directory](https://github.com/actix/actix-web/blob/master/examples/websocket.rs). +[examples directory](https://github.com/actix/actix-web/blob/master/examples/websocket). Example chat server with ability to chat over websocket connection or tcp connection is available in [websocket-chat directory](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/) From 25aabfb3e2ac291fc369b893ec5c6105dceb91fd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 23 Feb 2018 10:45:33 +0100 Subject: [PATCH 02/84] fix big ws frames --- src/ws/frame.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ws/frame.rs b/src/ws/frame.rs index 612fe2f0a..8771435fa 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -191,7 +191,7 @@ impl Frame { unsafe{buf.advance_mut(2)}; buf } else { - let mut buf = BytesMut::with_capacity(p_len + 8); + let mut buf = BytesMut::with_capacity(p_len + 10); buf.put_slice(&[one, two | 127]); { let buf_mut = unsafe{buf.bytes_mut()}; From fd31eb74c522f9b0809ac844ba753cf3768e645d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 24 Feb 2018 07:36:50 +0300 Subject: [PATCH 03/84] better ergonomics for ws client --- src/ws/client.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ws/client.rs b/src/ws/client.rs index 1d34b864b..2d385239d 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -128,7 +128,7 @@ impl WsClient { } /// Set supported websocket protocols - pub fn protocols(&mut self, protos: U) -> &mut Self + pub fn protocols(mut self, protos: U) -> Self where U: IntoIterator + 'static, V: AsRef { @@ -140,13 +140,13 @@ impl WsClient { } /// Set cookie for handshake request - pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { + pub fn cookie<'c>(mut self, cookie: Cookie<'c>) -> Self { self.request.cookie(cookie); self } /// Set request Origin - pub fn origin(&mut self, origin: V) -> &mut Self + pub fn origin(mut self, origin: V) -> Self where HeaderValue: HttpTryFrom { match HeaderValue::try_from(origin) { @@ -157,7 +157,7 @@ impl WsClient { } /// Set request header - pub fn header(&mut self, key: K, value: V) -> &mut Self + pub fn header(mut self, key: K, value: V) -> Self where HeaderName: HttpTryFrom, HeaderValue: HttpTryFrom { self.request.header(key, value); From a855c8b2c9ced44d6c57e6246707f7a42743eb53 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 24 Feb 2018 08:14:21 +0300 Subject: [PATCH 04/84] better ergonomics for WsClient::client() --- src/test.rs | 2 +- src/ws/client.rs | 159 ++++++++++++++++++++++++++++------------------- src/ws/mod.rs | 2 +- 3 files changed, 98 insertions(+), 65 deletions(-) diff --git a/src/test.rs b/src/test.rs index faad063f2..fe3422b54 100644 --- a/src/test.rs +++ b/src/test.rs @@ -182,7 +182,7 @@ impl TestServer { /// Connect to websocket server pub fn ws(&mut self) -> Result<(WsClientReader, WsClientWriter), WsClientError> { let url = self.url("/"); - self.system.run_until_complete(WsClient::new(url).connect().unwrap()) + self.system.run_until_complete(WsClient::new(url).connect()) } /// Create `GET` request diff --git a/src/ws/client.rs b/src/ws/client.rs index 2d385239d..2023fdd21 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -31,9 +31,6 @@ use super::Message; use super::frame::Frame; use super::proto::{CloseCode, OpCode}; -pub type WsClientFuture = - Future; - /// Websocket client error #[derive(Fail, Debug)] @@ -140,7 +137,7 @@ impl WsClient { } /// Set cookie for handshake request - pub fn cookie<'c>(mut self, cookie: Cookie<'c>) -> Self { + pub fn cookie(mut self, cookie: Cookie) -> Self { self.request.cookie(cookie); self } @@ -165,49 +162,46 @@ impl WsClient { } /// Connect to websocket server and do ws handshake - pub fn connect(&mut self) -> Result, WsClientError> { + pub fn connect(&mut self) -> WsHandshake { if let Some(e) = self.err.take() { - return Err(e) + WsHandshake::new(None, Some(e), &self.conn) } - if let Some(e) = self.http_err.take() { - return Err(e.into()) - } - - // origin - if let Some(origin) = self.origin.take() { - self.request.set_header(header::ORIGIN, origin); - } - - self.request.upgrade(); - self.request.set_header(header::UPGRADE, "websocket"); - self.request.set_header(header::CONNECTION, "upgrade"); - self.request.set_header("SEC-WEBSOCKET-VERSION", "13"); - - if let Some(protocols) = self.protocols.take() { - self.request.set_header("SEC-WEBSOCKET-PROTOCOL", protocols.as_str()); - } - let request = self.request.finish()?; - - if request.uri().host().is_none() { - return Err(WsClientError::InvalidUrl) - } - if let Some(scheme) = request.uri().scheme_part() { - if scheme != "http" && scheme != "https" && scheme != "ws" && scheme != "wss" { - return Err(WsClientError::InvalidUrl); - } + else if let Some(e) = self.http_err.take() { + WsHandshake::new(None, Some(e.into()), &self.conn) } else { - return Err(WsClientError::InvalidUrl); - } + // origin + if let Some(origin) = self.origin.take() { + self.request.set_header(header::ORIGIN, origin); + } - // get connection and start handshake - Ok(Box::new( - self.conn.send(Connect(request.uri().clone())) - .map_err(|_| WsClientError::Disconnected) - .and_then(|res| match res { - Ok(stream) => Either::A(WsHandshake::new(stream, request)), - Err(err) => Either::B(FutErr(err.into())), - }) - )) + self.request.upgrade(); + self.request.set_header(header::UPGRADE, "websocket"); + self.request.set_header(header::CONNECTION, "upgrade"); + self.request.set_header("SEC-WEBSOCKET-VERSION", "13"); + + if let Some(protocols) = self.protocols.take() { + self.request.set_header("SEC-WEBSOCKET-PROTOCOL", protocols.as_str()); + } + let request = match self.request.finish() { + Ok(req) => req, + Err(err) => return WsHandshake::new(None, Some(err.into()), &self.conn), + }; + + if request.uri().host().is_none() { + return WsHandshake::new(None, Some(WsClientError::InvalidUrl), &self.conn) + } + if let Some(scheme) = request.uri().scheme_part() { + if scheme != "http" && scheme != "https" && scheme != "ws" && scheme != "wss" { + return WsHandshake::new( + None, Some(WsClientError::InvalidUrl), &self.conn) + } + } else { + return WsHandshake::new(None, Some(WsClientError::InvalidUrl), &self.conn) + } + + // start handshake + WsHandshake::new(Some(request), None, &self.conn) + } } } @@ -220,39 +214,53 @@ struct WsInner { error_sent: bool, } -struct WsHandshake { +pub struct WsHandshake { inner: Option, - request: ClientRequest, + request: Option, sent: bool, key: String, + error: Option, + stream: Option, Error=WsClientError>>>, } impl WsHandshake { - fn new(conn: Connection, mut request: ClientRequest) -> WsHandshake { + fn new(request: Option, + err: Option, + conn: &Addr) -> WsHandshake + { // Generate a random key for the `Sec-WebSocket-Key` header. // a base64-encoded (see Section 4 of [RFC4648]) value that, // when decoded, is 16 bytes in length (RFC 6455) let sec_key: [u8; 16] = rand::random(); let key = base64::encode(&sec_key); - request.headers_mut().insert( - HeaderName::try_from("SEC-WEBSOCKET-KEY").unwrap(), - HeaderValue::try_from(key.as_str()).unwrap()); + if let Some(mut request) = request { + let stream = Box::new( + conn.send(Connect(request.uri().clone())) + .map(|res| res.map_err(|e| e.into())) + .map_err(|_| WsClientError::Disconnected)); - let inner = WsInner { - conn: conn, - writer: HttpClientWriter::new(SharedBytes::default()), - parser: HttpResponseParser::default(), - parser_buf: BytesMut::new(), - closed: false, - error_sent: false, - }; + request.headers_mut().insert( + HeaderName::try_from("SEC-WEBSOCKET-KEY").unwrap(), + HeaderValue::try_from(key.as_str()).unwrap()); - WsHandshake { - key: key, - inner: Some(inner), - request: request, - sent: false, + WsHandshake { + key: key, + inner: None, + request: Some(request), + sent: false, + error: err, + stream: Some(stream), + } + } else { + WsHandshake { + key: key, + inner: None, + request: None, + sent: false, + error: err, + stream: None, + } } } } @@ -262,11 +270,36 @@ impl Future for WsHandshake { type Error = WsClientError; fn poll(&mut self) -> Poll { + if let Some(err) = self.error.take() { + return Err(err) + } + + if self.stream.is_some() { + match self.stream.as_mut().unwrap().poll()? { + Async::Ready(result) => match result { + Ok(conn) => { + let inner = WsInner { + conn: conn, + writer: HttpClientWriter::new(SharedBytes::default()), + parser: HttpResponseParser::default(), + parser_buf: BytesMut::new(), + closed: false, + error_sent: false, + }; + self.stream.take(); + self.inner = Some(inner); + } + Err(err) => return Err(err), + }, + Async::NotReady => return Ok(Async::NotReady) + } + } + let mut inner = self.inner.take().unwrap(); if !self.sent { self.sent = true; - inner.writer.start(&mut self.request)?; + inner.writer.start(self.request.as_mut().unwrap())?; } if let Err(err) = inner.writer.poll_completed(&mut inner.conn, false) { return Err(err.into()) diff --git a/src/ws/mod.rs b/src/ws/mod.rs index d9bf0f103..91258cabe 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -65,7 +65,7 @@ use self::frame::Frame; use self::proto::{hash_key, OpCode}; pub use self::proto::CloseCode; pub use self::context::WebsocketContext; -pub use self::client::{WsClient, WsClientError, WsClientReader, WsClientWriter, WsClientFuture}; +pub use self::client::{WsClient, WsClientError, WsClientReader, WsClientWriter, WsHandshake}; const SEC_WEBSOCKET_ACCEPT: &str = "SEC-WEBSOCKET-ACCEPT"; const SEC_WEBSOCKET_KEY: &str = "SEC-WEBSOCKET-KEY"; From ea8e8e75a2fa6dde06e7d0a74d6ce279c28642b5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 24 Feb 2018 08:41:58 +0300 Subject: [PATCH 05/84] fix websocket example --- examples/websocket/src/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/websocket/src/client.rs b/examples/websocket/src/client.rs index e35c71bb1..8dc410a78 100644 --- a/examples/websocket/src/client.rs +++ b/examples/websocket/src/client.rs @@ -22,7 +22,7 @@ fn main() { Arbiter::handle().spawn( WsClient::new("http://127.0.0.1:8080/ws/") - .connect().unwrap() + .connect() .map_err(|e| { println!("Error: {}", e); () From 4e41e13baf77ee13e7948b6739b328d77e551ed6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 24 Feb 2018 07:29:35 +0300 Subject: [PATCH 06/84] refactor client payload processing --- CHANGES.md | 4 +- Cargo.toml | 1 + src/client/encoding.rs | 142 +++++++++++++++++++++++++++++++++++++++ src/client/mod.rs | 1 + src/client/parser.rs | 26 +++++--- src/client/pipeline.rs | 110 +++++++++++++++++++++++++----- src/client/request.rs | 46 ++++++++++++- src/error.rs | 2 + src/lib.rs | 2 + src/server/encoding.rs | 8 +-- tests/test_client.rs | 148 +++++++++++++++++++++++++++++++++++++++++ tests/test_server.rs | 127 +++-------------------------------- 12 files changed, 468 insertions(+), 149 deletions(-) create mode 100644 src/client/encoding.rs diff --git a/CHANGES.md b/CHANGES.md index b623c163f..a406bdfa0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,11 +16,11 @@ * Added http client -* Added basic websocket client +* Added websocket client * Added TestServer::ws(), test websockets client -* Added TestServer test http client +* Added TestServer http client support * Allow to override content encoding on application level diff --git a/Cargo.toml b/Cargo.toml index b7999b743..d8a4b93a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,6 +77,7 @@ tokio-tls = { version="0.1", optional = true } openssl = { version="0.10", optional = true } tokio-openssl = { version="0.2", optional = true } +backtrace="*" [dependencies.actix] version = "0.5" diff --git a/src/client/encoding.rs b/src/client/encoding.rs new file mode 100644 index 000000000..4764d67b1 --- /dev/null +++ b/src/client/encoding.rs @@ -0,0 +1,142 @@ +use std::io; +use std::io::{Read, Write}; +use bytes::{Bytes, BytesMut, BufMut}; + +use flate2::read::GzDecoder; +use flate2::write::DeflateDecoder; +use brotli2::write::BrotliDecoder; + +use headers::ContentEncoding; +use server::encoding::{Decoder, Wrapper}; + + +/// Payload wrapper with content decompression support +pub(crate) struct PayloadStream { + decoder: Decoder, + dst: BytesMut, +} + +impl PayloadStream { + pub fn new(enc: ContentEncoding) -> PayloadStream { + let dec = match enc { + ContentEncoding::Br => Decoder::Br( + Box::new(BrotliDecoder::new(BytesMut::with_capacity(8192).writer()))), + ContentEncoding::Deflate => Decoder::Deflate( + Box::new(DeflateDecoder::new(BytesMut::with_capacity(8192).writer()))), + ContentEncoding::Gzip => Decoder::Gzip(None), + _ => Decoder::Identity, + }; + PayloadStream{ decoder: dec, dst: BytesMut::new() } + } +} + +impl PayloadStream { + + pub fn feed_eof(&mut self) -> io::Result> { + match self.decoder { + Decoder::Br(ref mut decoder) => { + match decoder.finish() { + Ok(mut writer) => { + let b = writer.get_mut().take().freeze(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + }, + Err(err) => Err(err), + } + }, + Decoder::Gzip(ref mut decoder) => { + if let Some(ref mut decoder) = *decoder { + decoder.as_mut().get_mut().eof = true; + + loop { + self.dst.reserve(8192); + match decoder.read(unsafe{self.dst.bytes_mut()}) { + Ok(n) => { + if n == 0 { + return Ok(Some(self.dst.take().freeze())) + } else { + unsafe{self.dst.set_len(n)}; + } + } + Err(err) => return Err(err), + } + } + } else { + Ok(None) + } + }, + Decoder::Deflate(ref mut decoder) => { + match decoder.try_finish() { + Ok(_) => { + let b = decoder.get_mut().get_mut().take().freeze(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + }, + Err(err) => Err(err), + } + }, + Decoder::Identity => Ok(None), + } + } + + pub fn feed_data(&mut self, data: Bytes) -> io::Result> { + match self.decoder { + Decoder::Br(ref mut decoder) => { + match decoder.write(&data).and_then(|_| decoder.flush()) { + Ok(_) => { + let b = decoder.get_mut().get_mut().take().freeze(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + }, + Err(err) => Err(err) + } + }, + Decoder::Gzip(ref mut decoder) => { + if decoder.is_none() { + *decoder = Some( + Box::new(GzDecoder::new( + Wrapper{buf: BytesMut::from(data), eof: false}))); + } else { + let _ = decoder.as_mut().unwrap().write(&data); + } + + loop { + self.dst.reserve(8192); + match decoder.as_mut().as_mut().unwrap().read(unsafe{self.dst.bytes_mut()}) { + Ok(n) => { + if n == 0 { + return Ok(Some(self.dst.split_to(n).freeze())); + } else { + unsafe{self.dst.set_len(n)}; + } + } + Err(e) => return Err(e), + } + } + }, + Decoder::Deflate(ref mut decoder) => { + match decoder.write(&data).and_then(|_| decoder.flush()) { + Ok(_) => { + let b = decoder.get_mut().get_mut().take().freeze(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + }, + Err(e) => Err(e), + } + }, + Decoder::Identity => Ok(Some(data)), + } + } +} diff --git a/src/client/mod.rs b/src/client/mod.rs index f7b735437..8a8e9f500 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,5 +1,6 @@ //! Http client mod connector; +mod encoding; mod parser; mod request; mod response; diff --git a/src/client/parser.rs b/src/client/parser.rs index b4ce9b2b2..03ba23f99 100644 --- a/src/client/parser.rs +++ b/src/client/parser.rs @@ -83,20 +83,34 @@ impl HttpResponseParser { -> Poll, PayloadError> where T: IoStream { - if let Some(ref mut decoder) = self.decoder { + if self.decoder.is_some() { // read payload match utils::read_from_io(io, buf) { - Ok(Async::Ready(0)) => return Err(PayloadError::Incomplete), + Ok(Async::Ready(0)) => { + if buf.is_empty() { + return Err(PayloadError::Incomplete) + } + } Err(err) => return Err(err.into()), _ => (), } - decoder.decode(buf).map_err(|e| e.into()) + + match self.decoder.as_mut().unwrap().decode(buf) { + Ok(Async::Ready(Some(b))) => Ok(Async::Ready(Some(b))), + Ok(Async::Ready(None)) => { + self.decoder.take(); + Ok(Async::Ready(None)) + } + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(err) => Err(err.into()), + } } else { Ok(Async::Ready(None)) } } - fn parse_message(buf: &mut BytesMut) -> Poll<(ClientResponse, Option), ParseError> { + fn parse_message(buf: &mut BytesMut) -> Poll<(ClientResponse, Option), ParseError> + { // Parse http message let bytes_ptr = buf.as_ref().as_ptr() as usize; let mut headers: [httparse::Header; MAX_HEADERS] = @@ -160,10 +174,6 @@ impl HttpResponseParser { }; if let Some(decoder) = decoder { - //let info = PayloadInfo { - //tx: PayloadType::new(&hdrs, psender), - // decoder: decoder, - //}; Ok(Async::Ready( (ClientResponse::new( ClientMessage{status: status, version: version, diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index d0f339d7f..c2b3f7bdc 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -1,5 +1,6 @@ use std::{io, mem}; use bytes::{Bytes, BytesMut}; +use http::header::CONTENT_ENCODING; use futures::{Async, Future, Poll}; use futures::unsync::oneshot; @@ -8,6 +9,7 @@ use actix::prelude::*; use error::Error; use body::{Body, BodyStream}; use context::{Frame, ActorHttpContext}; +use headers::ContentEncoding; use error::PayloadError; use server::WriterState; use server::shared::SharedBytes; @@ -15,6 +17,7 @@ use super::{ClientRequest, ClientResponse}; use super::{Connect, Connection, ClientConnector, ClientConnectorError}; use super::HttpClientWriter; use super::{HttpResponseParser, HttpResponseParserError}; +use super::encoding::PayloadStream; /// A set of errors that can occur during sending request and reading response #[derive(Fail, Debug)] @@ -114,11 +117,13 @@ impl Future for SendRequest { body: body, conn: stream, writer: writer, - parser: HttpResponseParser::default(), + parser: Some(HttpResponseParser::default()), parser_buf: BytesMut::new(), disconnected: false, - running: RunningState::Running, drain: None, + decompress: None, + should_decompress: self.req.response_decompress(), + write_state: RunningState::Running, }); self.state = State::Send(pl); }, @@ -150,11 +155,13 @@ pub(crate) struct Pipeline { body: IoBody, conn: Connection, writer: HttpClientWriter, - parser: HttpResponseParser, + parser: Option, parser_buf: BytesMut, disconnected: bool, - running: RunningState, drain: Option>, + decompress: Option, + should_decompress: bool, + write_state: RunningState, } enum IoBody { @@ -163,7 +170,7 @@ enum IoBody { Done, } -#[derive(PartialEq)] +#[derive(Debug, PartialEq)] enum RunningState { Running, Paused, @@ -189,25 +196,90 @@ impl Pipeline { #[inline] pub fn parse(&mut self) -> Poll { - self.parser.parse(&mut self.conn, &mut self.parser_buf) + match self.parser.as_mut().unwrap().parse(&mut self.conn, &mut self.parser_buf) { + Ok(Async::Ready(resp)) => { + // check content-encoding + if self.should_decompress { + if let Some(enc) = resp.headers().get(CONTENT_ENCODING) { + if let Ok(enc) = enc.to_str() { + match ContentEncoding::from(enc) { + ContentEncoding::Auto | ContentEncoding::Identity => (), + enc => self.decompress = Some(PayloadStream::new(enc)), + } + } + } + } + + Ok(Async::Ready(resp)) + } + val => val, + } } #[inline] pub fn poll(&mut self) -> Poll, PayloadError> { - self.poll_write() - .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e).as_str()))?; - Ok(self.parser.parse_payload(&mut self.conn, &mut self.parser_buf)?) + let mut need_run = false; + + // need write? + match self.poll_write() + .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))? + { + Async::NotReady => need_run = true, + _ => (), + } + + // need read? + if self.parser.is_some() { + loop { + match self.parser.as_mut().unwrap() + .parse_payload(&mut self.conn, &mut self.parser_buf)? + { + Async::Ready(Some(b)) => { + if let Some(ref mut decompress) = self.decompress { + match decompress.feed_data(b) { + Ok(Some(b)) => return Ok(Async::Ready(Some(b))), + Ok(None) => return Ok(Async::NotReady), + Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => + continue, + Err(err) => return Err(err.into()), + } + } else { + return Ok(Async::Ready(Some(b))) + } + }, + Async::Ready(None) => { + let _ = self.parser.take(); + break + } + Async::NotReady => return Ok(Async::NotReady), + } + } + } + + // eof + if let Some(mut decompress) = self.decompress.take() { + let res = decompress.feed_eof(); + if let Some(b) = res? { + return Ok(Async::Ready(Some(b))) + } + } + + if need_run { + Ok(Async::NotReady) + } else { + Ok(Async::Ready(None)) + } } #[inline] pub fn poll_write(&mut self) -> Poll<(), Error> { - if self.running == RunningState::Done { + if self.write_state == RunningState::Done { return Ok(Async::Ready(())) } let mut done = false; - if self.drain.is_none() && self.running != RunningState::Paused { + if self.drain.is_none() && self.write_state != RunningState::Paused { 'outter: loop { let result = match mem::replace(&mut self.body, IoBody::Done) { IoBody::Payload(mut body) => { @@ -243,6 +315,7 @@ impl Pipeline { match frame { Frame::Chunk(None) => { // info.context = Some(ctx); + self.disconnected = true; self.writer.write_eof()?; break 'outter }, @@ -253,7 +326,7 @@ impl Pipeline { } self.body = IoBody::Actor(ctx); if self.drain.is_some() { - self.running.resume(); + self.write_state.resume(); break } res.unwrap() @@ -270,6 +343,7 @@ impl Pipeline { } }, IoBody::Done => { + self.disconnected = true; done = true; break } @@ -277,11 +351,11 @@ impl Pipeline { match result { WriterState::Pause => { - self.running.pause(); + self.write_state.pause(); break } WriterState::Done => { - self.running.resume() + self.write_state.resume() }, } } @@ -290,14 +364,18 @@ impl Pipeline { // flush io but only if we need to match self.writer.poll_completed(&mut self.conn, false) { Ok(Async::Ready(_)) => { - self.running.resume(); + if self.disconnected { + self.write_state = RunningState::Done; + } else { + self.write_state.resume(); + } // resolve drain futures if let Some(tx) = self.drain.take() { let _ = tx.send(()); } // restart io processing - if !done { + if !done || self.write_state == RunningState::Done { self.poll_write() } else { Ok(Async::NotReady) diff --git a/src/client/request.rs b/src/client/request.rs index 37d95fa74..fd1d40c5f 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -4,7 +4,7 @@ use std::io::Write; use actix::{Addr, Unsync}; use cookie::{Cookie, CookieJar}; use bytes::{BytesMut, BufMut}; -use http::{HeaderMap, Method, Version, Uri, HttpTryFrom, Error as HttpError}; +use http::{uri, HeaderMap, Method, Version, Uri, HttpTryFrom, Error as HttpError}; use http::header::{self, HeaderName, HeaderValue}; use serde_json; use serde::Serialize; @@ -25,6 +25,7 @@ pub struct ClientRequest { chunked: bool, upgrade: bool, encoding: ContentEncoding, + response_decompress: bool, } impl Default for ClientRequest { @@ -39,6 +40,7 @@ impl Default for ClientRequest { chunked: false, upgrade: false, encoding: ContentEncoding::Auto, + response_decompress: true, } } } @@ -89,6 +91,7 @@ impl ClientRequest { request: Some(ClientRequest::default()), err: None, cookies: None, + default_headers: true, } } @@ -158,6 +161,12 @@ impl ClientRequest { self.encoding } + /// Decompress response payload + #[inline] + pub fn response_decompress(&self) -> bool { + self.response_decompress + } + /// Get body os this response #[inline] pub fn body(&self) -> &Body { @@ -216,6 +225,7 @@ pub struct ClientRequestBuilder { request: Option, err: Option, cookies: Option, + default_headers: bool, } impl ClientRequestBuilder { @@ -409,6 +419,22 @@ impl ClientRequestBuilder { self } + /// Do not add default request headers. + /// By default `Accept-Encoding` header is set. + pub fn no_default_headers(&mut self) -> &mut Self { + self.default_headers = false; + self + } + + /// Disable automatic decompress response body + pub fn disable_decompress(&mut self) -> &mut Self { + if let Some(parts) = parts(&mut self.request, &self.err) { + parts.response_decompress = false; + } + self + } + + /// This method calls provided closure with builder reference if value is true. pub fn if_true(&mut self, value: bool, f: F) -> &mut Self where F: FnOnce(&mut ClientRequestBuilder) @@ -437,6 +463,23 @@ impl ClientRequestBuilder { return Err(e) } + if self.default_headers { + // enable br only for https + let https = + if let Some(parts) = parts(&mut self.request, &self.err) { + parts.uri.scheme_part() + .map(|s| s == &uri::Scheme::HTTPS).unwrap_or(true) + } else { + true + }; + + if https { + self.header(header::ACCEPT_ENCODING, "br, gzip, deflate"); + } else { + self.header(header::ACCEPT_ENCODING, "gzip, deflate"); + } + } + let mut request = self.request.take().expect("cannot reuse request builder"); // set cookies @@ -482,6 +525,7 @@ impl ClientRequestBuilder { request: self.request.take(), err: self.err.take(), cookies: self.cookies.take(), + default_headers: self.default_headers, } } } diff --git a/src/error.rs b/src/error.rs index 513c0f4d0..a37727cbd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -237,6 +237,8 @@ pub enum PayloadError { impl From for PayloadError { fn from(err: IoError) -> PayloadError { + use backtrace; + println!("IO ERROR {:?}", backtrace::Backtrace::new()); PayloadError::Io(err) } } diff --git a/src/lib.rs b/src/lib.rs index c9819aef3..ab683b6d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,6 +97,8 @@ extern crate openssl; #[cfg(feature="openssl")] extern crate tokio_openssl; +extern crate backtrace; + mod application; mod body; mod context; diff --git a/src/server/encoding.rs b/src/server/encoding.rs index 964754ab0..d3c78f405 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -128,7 +128,7 @@ impl PayloadWriter for PayloadType { } } -enum Decoder { +pub(crate) enum Decoder { Deflate(Box>>), Gzip(Option>>), Br(Box>>), @@ -137,9 +137,9 @@ enum Decoder { // should go after write::GzDecoder get implemented #[derive(Debug)] -struct Wrapper { - buf: BytesMut, - eof: bool, +pub(crate) struct Wrapper { + pub buf: BytesMut, + pub eof: bool, } impl io::Read for Wrapper { diff --git a/tests/test_client.rs b/tests/test_client.rs index 02a18f40b..cac1ab78e 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -2,8 +2,14 @@ extern crate actix; extern crate actix_web; extern crate bytes; extern crate futures; +extern crate flate2; + +use std::io::Read; use bytes::Bytes; +use futures::Future; +use futures::stream::once; +use flate2::read::GzDecoder; use actix_web::*; @@ -57,3 +63,145 @@ fn test_simple() { let bytes = srv.execute(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } + +#[test] +fn test_no_decompress() { + let mut srv = test::TestServer::new( + |app| app.handler(|_| httpcodes::HTTPOk.build().body(STR))); + + let request = srv.get().disable_decompress().finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + + // POST + let request = srv.post().disable_decompress().finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + + let bytes = srv.execute(response.body()).unwrap(); + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_client_gzip_encoding() { + let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(httpcodes::HTTPOk + .build() + .content_encoding(headers::ContentEncoding::Deflate) + .body(bytes)) + }).responder()} + )); + + // client request + let request = srv.post() + .content_encoding(headers::ContentEncoding::Gzip) + .body(STR).unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_client_brotli_encoding() { + let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(httpcodes::HTTPOk + .build() + .content_encoding(headers::ContentEncoding::Deflate) + .body(bytes)) + }).responder()} + )); + + // client request + let request = srv.client(Method::POST, "/") + .content_encoding(headers::ContentEncoding::Br) + .body(STR).unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_client_deflate_encoding() { + let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(httpcodes::HTTPOk + .build() + .content_encoding(headers::ContentEncoding::Br) + .body(bytes)) + }).responder()} + )); + + // client request + let request = srv.post() + .content_encoding(headers::ContentEncoding::Deflate) + .body(STR).unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_client_streaming_explicit() { + let mut srv = test::TestServer::new( + |app| app.handler( + |req: HttpRequest| req.body() + .map_err(Error::from) + .and_then(|body| { + Ok(httpcodes::HTTPOk.build() + .chunked() + .content_encoding(headers::ContentEncoding::Identity) + .body(body)?)}) + .responder())); + + let body = once(Ok(Bytes::from_static(STR.as_ref()))); + + let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_body_streaming_implicit() { + let mut srv = test::TestServer::new( + |app| app.handler(|_| { + let body = once(Ok(Bytes::from_static(STR.as_ref()))); + httpcodes::HTTPOk.build() + .content_encoding(headers::ContentEncoding::Gzip) + .body(Body::Streaming(Box::new(body)))})); + + let request = srv.get().finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} diff --git a/tests/test_server.rs b/tests/test_server.rs index 2cbeba8fc..ad2028a2b 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -123,7 +123,7 @@ fn test_body_gzip() { .content_encoding(headers::ContentEncoding::Gzip) .body(STR))); - let request = srv.get().finish().unwrap(); + let request = srv.get().disable_decompress().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -138,7 +138,7 @@ fn test_body_gzip() { } #[test] -fn test_body_streaming_implicit() { +fn test_body_chunked_implicit() { let mut srv = test::TestServer::new( |app| app.handler(|_| { let body = once(Ok(Bytes::from_static(STR.as_ref()))); @@ -146,7 +146,7 @@ fn test_body_streaming_implicit() { .content_encoding(headers::ContentEncoding::Gzip) .body(Body::Streaming(Box::new(body)))})); - let request = srv.get().finish().unwrap(); + let request = srv.get().disable_decompress().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -169,7 +169,7 @@ fn test_body_br_streaming() { .content_encoding(headers::ContentEncoding::Br) .body(Body::Streaming(Box::new(body)))})); - let request = srv.get().finish().unwrap(); + let request = srv.get().disable_decompress().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -252,6 +252,7 @@ fn test_body_length() { let body = once(Ok(Bytes::from_static(STR.as_ref()))); httpcodes::HTTPOk.build() .content_length(STR.len() as u64) + .content_encoding(headers::ContentEncoding::Identity) .body(Body::Streaming(Box::new(body)))})); let request = srv.get().finish().unwrap(); @@ -264,7 +265,7 @@ fn test_body_length() { } #[test] -fn test_body_streaming_explicit() { +fn test_body_chunked_explicit() { let mut srv = test::TestServer::new( |app| app.handler(|_| { let body = once(Ok(Bytes::from_static(STR.as_ref()))); @@ -273,7 +274,7 @@ fn test_body_streaming_explicit() { .content_encoding(headers::ContentEncoding::Gzip) .body(Body::Streaming(Box::new(body)))})); - let request = srv.get().finish().unwrap(); + let request = srv.get().disable_decompress().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -297,7 +298,7 @@ fn test_body_deflate() { .body(STR))); // client request - let request = srv.get().finish().unwrap(); + let request = srv.get().disable_decompress().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -321,7 +322,7 @@ fn test_body_brotli() { .body(STR))); // client request - let request = srv.get().finish().unwrap(); + let request = srv.get().disable_decompress().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -363,34 +364,6 @@ fn test_gzip_encoding() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[test] -fn test_client_gzip_encoding() { - let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(httpcodes::HTTPOk - .build() - .content_encoding(headers::ContentEncoding::Deflate) - .body(bytes)) - }).responder()} - )); - - // client request - let request = srv.post() - .content_encoding(headers::ContentEncoding::Gzip) - .body(STR).unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - let mut e = DeflateDecoder::new(Vec::new()); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - #[test] fn test_deflate_encoding() { let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { @@ -419,35 +392,6 @@ fn test_deflate_encoding() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[test] -fn test_client_deflate_encoding() { - let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(httpcodes::HTTPOk - .build() - .content_encoding(headers::ContentEncoding::Br) - .body(bytes)) - }).responder()} - )); - - // client request - let request = srv.post() - .content_encoding(headers::ContentEncoding::Deflate) - .body(STR).unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode brotli - let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - #[test] fn test_brotli_encoding() { let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { @@ -476,35 +420,6 @@ fn test_brotli_encoding() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[test] -fn test_client_brotli_encoding() { - let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(httpcodes::HTTPOk - .build() - .content_encoding(headers::ContentEncoding::Deflate) - .body(bytes)) - }).responder()} - )); - - // client request - let request = srv.client(Method::POST, "/") - .content_encoding(headers::ContentEncoding::Br) - .body(STR).unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode brotli - let mut e = DeflateDecoder::new(Vec::with_capacity(2048)); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - #[test] fn test_h2() { let srv = test::TestServer::new(|app| app.handler(|_|{ @@ -545,30 +460,6 @@ fn test_h2() { // assert_eq!(_res.unwrap(), Bytes::from_static(STR.as_ref())); } -#[test] -fn test_client_streaming_explicit() { - let mut srv = test::TestServer::new( - |app| app.handler( - |req: HttpRequest| req.body() - .map_err(Error::from) - .and_then(|body| { - Ok(httpcodes::HTTPOk.build() - .chunked() - .content_encoding(headers::ContentEncoding::Identity) - .body(body)?)}) - .responder())); - - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - - let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - #[test] fn test_application() { let mut srv = test::TestServer::with_factory( From 141b992450da0e2447319982dbf632d94baeefed Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 25 Feb 2018 11:21:45 +0300 Subject: [PATCH 07/84] Make payload and httprequest a stream --- src/httprequest.rs | 15 ++++++-- src/json.rs | 5 ++- src/multipart.rs | 2 +- src/payload.rs | 88 +++++++++------------------------------------- src/ws/mod.rs | 10 +++--- 5 files changed, 37 insertions(+), 83 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index aa0eb9f0e..e46a7c3ee 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -15,7 +15,7 @@ use http::{header, Uri, Method, Version, HeaderMap, Extensions}; use info::ConnectionInfo; use param::Params; use router::Router; -use payload::{Payload, ReadAny}; +use payload::Payload; use json::JsonBody; use multipart::Multipart; use helpers::SharedHttpMessage; @@ -604,6 +604,15 @@ impl Clone for HttpRequest { } } +impl Stream for HttpRequest { + type Item = Bytes; + type Error = PayloadError; + + fn poll(&mut self) -> Poll, PayloadError> { + self.payload_mut().poll() + } +} + impl fmt::Debug for HttpRequest { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let res = write!(f, "\nHttpRequest {:?} {}:{}\n", @@ -706,7 +715,7 @@ impl Future for UrlEncoded { /// Future that resolves to a complete request body. pub struct RequestBody { - pl: ReadAny, + pl: Payload, body: BytesMut, limit: usize, req: Option>, @@ -716,7 +725,7 @@ impl RequestBody { /// Create `RequestBody` for request. pub fn from_request(req: &HttpRequest) -> RequestBody { - let pl = req.payload().readany(); + let pl = req.payload().clone(); RequestBody { pl: pl, body: BytesMut::new(), diff --git a/src/json.rs b/src/json.rs index 8bcda5c90..86e612048 100644 --- a/src/json.rs +++ b/src/json.rs @@ -111,7 +111,7 @@ impl JsonBody { } } -impl Future for JsonBody { +impl Future for JsonBody { type Item = T; type Error = JsonPayloadError; @@ -134,8 +134,7 @@ impl Future for JsonBody { } let limit = self.limit; - let fut = req.payload().readany() - .from_err() + let fut = req.from_err() .fold(BytesMut::new(), move |mut body, chunk| { if (body.len() + chunk.len()) > limit { Err(JsonPayloadError::Overflow) diff --git a/src/multipart.rs b/src/multipart.rs index 9da15ba59..0fbc906d9 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -482,7 +482,7 @@ impl InnerField { if *size == 0 { Ok(Async::Ready(None)) } else { - match payload.readany().poll() { + match payload.poll() { Ok(Async::NotReady) => Ok(Async::NotReady), Ok(Async::Ready(None)) => Ok(Async::Ready(None)), Ok(Async::Ready(Some(mut chunk))) => { diff --git a/src/payload.rs b/src/payload.rs index 97e59a488..8d5bd7206 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -1,42 +1,16 @@ //! Payload stream -use std::{fmt, cmp}; +use std::cmp; use std::rc::{Rc, Weak}; use std::cell::RefCell; use std::collections::VecDeque; -use std::ops::{Deref, DerefMut}; use bytes::{Bytes, BytesMut}; use futures::{Future, Async, Poll, Stream}; use futures::task::{Task, current as current_task}; -use body::BodyStream; use error::PayloadError; pub(crate) const DEFAULT_BUFFER_SIZE: usize = 65_536; // max buffer size 64k -/// Just Bytes object -#[derive(PartialEq, Message)] -pub struct PayloadItem(pub Bytes); - -impl Deref for PayloadItem { - type Target = Bytes; - - fn deref(&self) -> &Bytes { - &self.0 - } -} - -impl DerefMut for PayloadItem { - fn deref_mut(&mut self) -> &mut Bytes { - &mut self.0 - } -} - -impl fmt::Debug for PayloadItem { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&self.0, f) - } -} - /// Buffered stream of bytes chunks /// /// Payload stores chunks in a vector. First chunk can be received with `.readany()` method. @@ -88,12 +62,6 @@ impl Payload { self.inner.borrow().len() == 0 } - /// Get first available chunk of data. - #[inline] - pub fn readany(&self) -> ReadAny { - ReadAny(Rc::clone(&self.inner)) - } - /// Get exact number of bytes #[inline] pub fn readexactly(&self, size: usize) -> ReadExactly { @@ -135,20 +103,14 @@ impl Payload { pub fn set_buffer_size(&self, size: usize) { self.inner.borrow_mut().set_buffer_size(size) } - - /// Convert payload into compatible `HttpResponse` body stream - #[inline] - pub fn stream(self) -> BodyStream { - Box::new(self.map(|i| i.0).map_err(|e| e.into())) - } } impl Stream for Payload { - type Item = PayloadItem; + type Item = Bytes; type Error = PayloadError; #[inline] - fn poll(&mut self) -> Poll, PayloadError> { + fn poll(&mut self) -> Poll, PayloadError> { self.inner.borrow_mut().readany(false) } } @@ -159,22 +121,6 @@ impl Clone for Payload { } } -/// Get first available chunk of data -pub struct ReadAny(Rc>); - -impl Stream for ReadAny { - type Item = Bytes; - type Error = PayloadError; - - fn poll(&mut self) -> Poll, Self::Error> { - match self.0.borrow_mut().readany(false)? { - Async::Ready(Some(item)) => Ok(Async::Ready(Some(item.0))), - Async::Ready(None) => Ok(Async::Ready(None)), - Async::NotReady => Ok(Async::NotReady), - } - } -} - /// Get exact number of bytes pub struct ReadExactly(Rc>, usize); @@ -325,10 +271,10 @@ impl Inner { self.len } - fn readany(&mut self, notify: bool) -> Poll, PayloadError> { + fn readany(&mut self, notify: bool) -> Poll, PayloadError> { if let Some(data) = self.items.pop_front() { self.len -= data.len(); - Ok(Async::Ready(Some(PayloadItem(data)))) + Ok(Async::Ready(Some(data))) } else if let Some(err) = self.err.take() { Err(err) } else if self.eof { @@ -486,12 +432,12 @@ mod tests { #[test] fn test_basic() { Core::new().unwrap().run(lazy(|| { - let (_, payload) = Payload::new(false); + let (_, mut payload) = Payload::new(false); assert!(!payload.eof()); assert!(payload.is_empty()); assert_eq!(payload.len(), 0); - assert_eq!(Async::NotReady, payload.readany().poll().ok().unwrap()); + assert_eq!(Async::NotReady, payload.poll().ok().unwrap()); let res: Result<(), ()> = Ok(()); result(res) @@ -501,9 +447,9 @@ mod tests { #[test] fn test_eof() { Core::new().unwrap().run(lazy(|| { - let (mut sender, payload) = Payload::new(false); + let (mut sender, mut payload) = Payload::new(false); - assert_eq!(Async::NotReady, payload.readany().poll().ok().unwrap()); + assert_eq!(Async::NotReady, payload.poll().ok().unwrap()); assert!(!payload.eof()); sender.feed_data(Bytes::from("data")); @@ -512,12 +458,12 @@ mod tests { assert!(!payload.eof()); assert_eq!(Async::Ready(Some(Bytes::from("data"))), - payload.readany().poll().ok().unwrap()); + payload.poll().ok().unwrap()); assert!(payload.is_empty()); assert!(payload.eof()); assert_eq!(payload.len(), 0); - assert_eq!(Async::Ready(None), payload.readany().poll().ok().unwrap()); + assert_eq!(Async::Ready(None), payload.poll().ok().unwrap()); let res: Result<(), ()> = Ok(()); result(res) })).unwrap(); @@ -526,12 +472,12 @@ mod tests { #[test] fn test_err() { Core::new().unwrap().run(lazy(|| { - let (mut sender, payload) = Payload::new(false); + let (mut sender, mut payload) = Payload::new(false); - assert_eq!(Async::NotReady, payload.readany().poll().ok().unwrap()); + assert_eq!(Async::NotReady, payload.poll().ok().unwrap()); sender.set_error(PayloadError::Incomplete); - payload.readany().poll().err().unwrap(); + payload.poll().err().unwrap(); let res: Result<(), ()> = Ok(()); result(res) })).unwrap(); @@ -540,7 +486,7 @@ mod tests { #[test] fn test_readany() { Core::new().unwrap().run(lazy(|| { - let (mut sender, payload) = Payload::new(false); + let (mut sender, mut payload) = Payload::new(false); sender.feed_data(Bytes::from("line1")); @@ -552,7 +498,7 @@ mod tests { assert_eq!(payload.len(), 10); assert_eq!(Async::Ready(Some(Bytes::from("line1"))), - payload.readany().poll().ok().unwrap()); + payload.poll().ok().unwrap()); assert!(!payload.is_empty()); assert_eq!(payload.len(), 5); @@ -625,7 +571,7 @@ mod tests { assert_eq!(payload.len(), 4); assert_eq!(Async::Ready(Some(Bytes::from("data"))), - payload.readany().poll().ok().unwrap()); + payload.poll().ok().unwrap()); let res: Result<(), ()> = Ok(()); result(res) diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 91258cabe..93d0b61aa 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -50,7 +50,7 @@ use futures::{Async, Poll, Stream}; use actix::{Actor, AsyncContext, Handler}; use body::Binary; -use payload::ReadAny; +use payload::Payload; use error::{Error, WsHandshakeError}; use httprequest::HttpRequest; use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder}; @@ -86,12 +86,12 @@ pub enum Message { } /// Do websocket handshake and start actor -pub fn start(mut req: HttpRequest, actor: A) -> Result +pub fn start(req: HttpRequest, actor: A) -> Result where A: Actor> + Handler, S: 'static { let mut resp = handshake(&req)?; - let stream = WsStream::new(req.payload_mut().readany()); + let stream = WsStream::new(req.payload().clone()); let mut ctx = WebsocketContext::new(req, actor); ctx.add_message_stream(stream); @@ -166,14 +166,14 @@ pub fn handshake(req: &HttpRequest) -> Result WsStream { + pub fn new(payload: Payload) -> WsStream { WsStream { rx: payload, buf: BytesMut::new(), closed: false, From ab5ed27bf1fe9d7b266b389672b34f8cd9914964 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 25 Feb 2018 11:43:00 +0300 Subject: [PATCH 08/84] refactor and simplify content encoding --- src/client/encoding.rs | 142 ----------------------- src/client/mod.rs | 1 - src/client/pipeline.rs | 2 +- src/server/encoding.rs | 248 ++++++++++++++++++++--------------------- src/server/h1writer.rs | 8 +- src/server/h2writer.rs | 8 +- 6 files changed, 129 insertions(+), 280 deletions(-) delete mode 100644 src/client/encoding.rs diff --git a/src/client/encoding.rs b/src/client/encoding.rs deleted file mode 100644 index 4764d67b1..000000000 --- a/src/client/encoding.rs +++ /dev/null @@ -1,142 +0,0 @@ -use std::io; -use std::io::{Read, Write}; -use bytes::{Bytes, BytesMut, BufMut}; - -use flate2::read::GzDecoder; -use flate2::write::DeflateDecoder; -use brotli2::write::BrotliDecoder; - -use headers::ContentEncoding; -use server::encoding::{Decoder, Wrapper}; - - -/// Payload wrapper with content decompression support -pub(crate) struct PayloadStream { - decoder: Decoder, - dst: BytesMut, -} - -impl PayloadStream { - pub fn new(enc: ContentEncoding) -> PayloadStream { - let dec = match enc { - ContentEncoding::Br => Decoder::Br( - Box::new(BrotliDecoder::new(BytesMut::with_capacity(8192).writer()))), - ContentEncoding::Deflate => Decoder::Deflate( - Box::new(DeflateDecoder::new(BytesMut::with_capacity(8192).writer()))), - ContentEncoding::Gzip => Decoder::Gzip(None), - _ => Decoder::Identity, - }; - PayloadStream{ decoder: dec, dst: BytesMut::new() } - } -} - -impl PayloadStream { - - pub fn feed_eof(&mut self) -> io::Result> { - match self.decoder { - Decoder::Br(ref mut decoder) => { - match decoder.finish() { - Ok(mut writer) => { - let b = writer.get_mut().take().freeze(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - }, - Err(err) => Err(err), - } - }, - Decoder::Gzip(ref mut decoder) => { - if let Some(ref mut decoder) = *decoder { - decoder.as_mut().get_mut().eof = true; - - loop { - self.dst.reserve(8192); - match decoder.read(unsafe{self.dst.bytes_mut()}) { - Ok(n) => { - if n == 0 { - return Ok(Some(self.dst.take().freeze())) - } else { - unsafe{self.dst.set_len(n)}; - } - } - Err(err) => return Err(err), - } - } - } else { - Ok(None) - } - }, - Decoder::Deflate(ref mut decoder) => { - match decoder.try_finish() { - Ok(_) => { - let b = decoder.get_mut().get_mut().take().freeze(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - }, - Err(err) => Err(err), - } - }, - Decoder::Identity => Ok(None), - } - } - - pub fn feed_data(&mut self, data: Bytes) -> io::Result> { - match self.decoder { - Decoder::Br(ref mut decoder) => { - match decoder.write(&data).and_then(|_| decoder.flush()) { - Ok(_) => { - let b = decoder.get_mut().get_mut().take().freeze(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - }, - Err(err) => Err(err) - } - }, - Decoder::Gzip(ref mut decoder) => { - if decoder.is_none() { - *decoder = Some( - Box::new(GzDecoder::new( - Wrapper{buf: BytesMut::from(data), eof: false}))); - } else { - let _ = decoder.as_mut().unwrap().write(&data); - } - - loop { - self.dst.reserve(8192); - match decoder.as_mut().as_mut().unwrap().read(unsafe{self.dst.bytes_mut()}) { - Ok(n) => { - if n == 0 { - return Ok(Some(self.dst.split_to(n).freeze())); - } else { - unsafe{self.dst.set_len(n)}; - } - } - Err(e) => return Err(e), - } - } - }, - Decoder::Deflate(ref mut decoder) => { - match decoder.write(&data).and_then(|_| decoder.flush()) { - Ok(_) => { - let b = decoder.get_mut().get_mut().take().freeze(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - }, - Err(e) => Err(e), - } - }, - Decoder::Identity => Ok(Some(data)), - } - } -} diff --git a/src/client/mod.rs b/src/client/mod.rs index 8a8e9f500..f7b735437 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,6 +1,5 @@ //! Http client mod connector; -mod encoding; mod parser; mod request; mod response; diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index c2b3f7bdc..46705134d 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -13,11 +13,11 @@ use headers::ContentEncoding; use error::PayloadError; use server::WriterState; use server::shared::SharedBytes; +use server::encoding::PayloadStream; use super::{ClientRequest, ClientResponse}; use super::{Connect, Connection, ClientConnector, ClientConnectorError}; use super::HttpClientWriter; use super::{HttpResponseParser, HttpResponseParserError}; -use super::encoding::PayloadStream; /// A set of errors that can occur during sending request and reading response #[derive(Fail, Debug)] diff --git a/src/server/encoding.rs b/src/server/encoding.rs index d3c78f405..9137bb420 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -169,16 +169,14 @@ impl io::Write for Wrapper { } } -/// Payload wrapper with content decompression support -pub(crate) struct EncodedPayload { - inner: PayloadSender, +/// Payload stream with decompression support +pub(crate) struct PayloadStream { decoder: Decoder, dst: BytesMut, - error: bool, } -impl EncodedPayload { - pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload { +impl PayloadStream { + pub fn new(enc: ContentEncoding) -> PayloadStream { let dec = match enc { ContentEncoding::Br => Decoder::Br( Box::new(BrotliDecoder::new(BytesMut::with_capacity(8192).writer()))), @@ -187,32 +185,25 @@ impl EncodedPayload { ContentEncoding::Gzip => Decoder::Gzip(None), _ => Decoder::Identity, }; - EncodedPayload{ inner: inner, decoder: dec, error: false, dst: BytesMut::new() } + PayloadStream{ decoder: dec, dst: BytesMut::new() } } } -impl PayloadWriter for EncodedPayload { +impl PayloadStream { - fn set_error(&mut self, err: PayloadError) { - self.inner.set_error(err) - } - - fn feed_eof(&mut self) { - if self.error { - return - } - let err = match self.decoder { + pub fn feed_eof(&mut self) -> io::Result> { + match self.decoder { Decoder::Br(ref mut decoder) => { match decoder.finish() { Ok(mut writer) => { let b = writer.get_mut().take().freeze(); if !b.is_empty() { - self.inner.feed_data(b); + Ok(Some(b)) + } else { + Ok(None) } - self.inner.feed_eof(); - return }, - Err(err) => Some(err), + Err(err) => Err(err), } }, Decoder::Gzip(ref mut decoder) => { @@ -224,20 +215,16 @@ impl PayloadWriter for EncodedPayload { match decoder.read(unsafe{self.dst.bytes_mut()}) { Ok(n) => { if n == 0 { - self.inner.feed_eof(); - return + return Ok(Some(self.dst.take().freeze())) } else { unsafe{self.dst.set_len(n)}; - self.inner.feed_data(self.dst.split_to(n).freeze()); } } - Err(err) => { - break Some(err); - } + Err(err) => return Err(err), } } } else { - return + Ok(None) } }, Decoder::Deflate(ref mut decoder) => { @@ -245,45 +232,33 @@ impl PayloadWriter for EncodedPayload { Ok(_) => { let b = decoder.get_mut().get_mut().take().freeze(); if !b.is_empty() { - self.inner.feed_data(b); + Ok(Some(b)) + } else { + Ok(None) } - self.inner.feed_eof(); - return }, - Err(err) => Some(err), + Err(err) => Err(err), } }, - Decoder::Identity => { - self.inner.feed_eof(); - return - } - }; - - self.error = true; - self.decoder = Decoder::Identity; - if let Some(err) = err { - self.set_error(PayloadError::Io(err)); - } else { - self.set_error(PayloadError::Incomplete); + Decoder::Identity => Ok(None), } } - fn feed_data(&mut self, data: Bytes) { - if self.error { - return - } + pub fn feed_data(&mut self, data: Bytes) -> io::Result> { match self.decoder { Decoder::Br(ref mut decoder) => { - if decoder.write(&data).is_ok() && decoder.flush().is_ok() { - let b = decoder.get_mut().get_mut().take().freeze(); - if !b.is_empty() { - self.inner.feed_data(b); - } - return + match decoder.write(&data).and_then(|_| decoder.flush()) { + Ok(_) => { + let b = decoder.get_mut().get_mut().take().freeze(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + }, + Err(err) => Err(err) } - trace!("Error decoding br encoding"); - } - + }, Decoder::Gzip(ref mut decoder) => { if decoder.is_none() { *decoder = Some( @@ -298,41 +273,82 @@ impl PayloadWriter for EncodedPayload { match decoder.as_mut().as_mut().unwrap().read(unsafe{self.dst.bytes_mut()}) { Ok(n) => { if n == 0 { - return + return Ok(Some(self.dst.split_to(n).freeze())); } else { unsafe{self.dst.set_len(n)}; - self.inner.feed_data(self.dst.split_to(n).freeze()); } } - Err(e) => { - if e.kind() == io::ErrorKind::WouldBlock { - return - } - break - } + Err(e) => return Err(e), } } - } - + }, Decoder::Deflate(ref mut decoder) => { - if decoder.write(&data).is_ok() && decoder.flush().is_ok() { - let b = decoder.get_mut().get_mut().take().freeze(); - if !b.is_empty() { + match decoder.write(&data).and_then(|_| decoder.flush()) { + Ok(_) => { + let b = decoder.get_mut().get_mut().take().freeze(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + }, + Err(e) => Err(e), + } + }, + Decoder::Identity => Ok(Some(data)), + } + } +} + +/// Payload wrapper with content decompression support +pub(crate) struct EncodedPayload { + inner: PayloadSender, + error: bool, + payload: PayloadStream, +} + +impl EncodedPayload { + pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload { + EncodedPayload{ inner: inner, error: false, payload: PayloadStream::new(enc) } + } +} + +impl PayloadWriter for EncodedPayload { + + fn set_error(&mut self, err: PayloadError) { + self.inner.set_error(err) + } + + fn feed_eof(&mut self) { + if !self.error { + match self.payload.feed_eof() { + Err(err) => { + self.error = true; + self.set_error(PayloadError::Io(err)); + }, + Ok(value) => { + if let Some(b) = value { self.inner.feed_data(b); } - return + self.inner.feed_eof(); } - trace!("Error decoding deflate encoding"); } - Decoder::Identity => { - self.inner.feed_data(data); - return - } - }; + } + } - self.error = true; - self.decoder = Decoder::Identity; - self.set_error(PayloadError::EncodingCorrupted); + fn feed_data(&mut self, data: Bytes) { + if self.error { + return + } + + match self.payload.feed_data(data) { + Ok(Some(b)) => self.inner.feed_data(b), + Ok(None) => (), + Err(e) => { + self.error = true; + self.set_error(e.into()); + } + } } fn capacity(&self) -> usize { @@ -340,18 +356,23 @@ impl PayloadWriter for EncodedPayload { } } -pub(crate) struct PayloadEncoder(ContentEncoder); +pub(crate) enum ContentEncoder { + Deflate(DeflateEncoder), + Gzip(GzEncoder), + Br(BrotliEncoder), + Identity(TransferEncoding), +} -impl PayloadEncoder { +impl ContentEncoder { - pub fn empty(bytes: SharedBytes) -> PayloadEncoder { - PayloadEncoder(ContentEncoder::Identity(TransferEncoding::eof(bytes))) + pub fn empty(bytes: SharedBytes) -> ContentEncoder { + ContentEncoder::Identity(TransferEncoding::eof(bytes)) } - pub fn new(buf: SharedBytes, - req: &HttpMessage, - resp: &mut HttpResponse, - response_encoding: ContentEncoding) -> PayloadEncoder + pub fn for_server(buf: SharedBytes, + req: &HttpMessage, + resp: &mut HttpResponse, + response_encoding: ContentEncoding) -> ContentEncoder { let version = resp.version().unwrap_or_else(|| req.version); let mut body = resp.replace_body(Body::Empty); @@ -440,7 +461,7 @@ impl PayloadEncoder { } TransferEncoding::eof(buf) } else { - PayloadEncoder::streaming_encoding(buf, version, resp) + ContentEncoder::streaming_encoding(buf, version, resp) } } }; @@ -451,18 +472,16 @@ impl PayloadEncoder { resp.replace_body(body); } - PayloadEncoder( - match encoding { - ContentEncoding::Deflate => ContentEncoder::Deflate( - DeflateEncoder::new(transfer, Compression::default())), - ContentEncoding::Gzip => ContentEncoder::Gzip( - GzEncoder::new(transfer, Compression::default())), - ContentEncoding::Br => ContentEncoder::Br( - BrotliEncoder::new(transfer, 5)), - ContentEncoding::Identity => ContentEncoder::Identity(transfer), - ContentEncoding::Auto => unreachable!() - } - ) + match encoding { + ContentEncoding::Deflate => ContentEncoder::Deflate( + DeflateEncoder::new(transfer, Compression::default())), + ContentEncoding::Gzip => ContentEncoder::Gzip( + GzEncoder::new(transfer, Compression::default())), + ContentEncoding::Br => ContentEncoder::Br( + BrotliEncoder::new(transfer, 5)), + ContentEncoding::Identity => ContentEncoder::Identity(transfer), + ContentEncoding::Auto => unreachable!() + } } fn streaming_encoding(buf: SharedBytes, version: Version, @@ -527,33 +546,6 @@ impl PayloadEncoder { } } -impl PayloadEncoder { - - #[inline] - pub fn is_eof(&self) -> bool { - self.0.is_eof() - } - - #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] - #[inline(always)] - pub fn write(&mut self, payload: Binary) -> Result<(), io::Error> { - self.0.write(payload) - } - - #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] - #[inline(always)] - pub fn write_eof(&mut self) -> Result<(), io::Error> { - self.0.write_eof() - } -} - -pub(crate) enum ContentEncoder { - Deflate(DeflateEncoder), - Gzip(GzEncoder), - Br(BrotliEncoder), - Identity(TransferEncoding), -} - impl ContentEncoder { #[inline] diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index aa9c819d7..b2b79c5f9 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -12,7 +12,7 @@ use httprequest::HttpMessage; use httpresponse::HttpResponse; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; use super::shared::SharedBytes; -use super::encoding::PayloadEncoder; +use super::encoding::ContentEncoder; const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific @@ -28,7 +28,7 @@ bitflags! { pub(crate) struct H1Writer { flags: Flags, stream: T, - encoder: PayloadEncoder, + encoder: ContentEncoder, written: u64, headers_size: u32, buffer: SharedBytes, @@ -40,7 +40,7 @@ impl H1Writer { H1Writer { flags: Flags::empty(), stream: stream, - encoder: PayloadEncoder::empty(buf.clone()), + encoder: ContentEncoder::empty(buf.clone()), written: 0, headers_size: 0, buffer: buf, @@ -101,7 +101,7 @@ impl Writer for H1Writer { encoding: ContentEncoding) -> io::Result { // prepare task - self.encoder = PayloadEncoder::new(self.buffer.clone(), req, msg, encoding); + self.encoder = ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding); if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { self.flags.insert(Flags::STARTED | Flags::KEEPALIVE); } else { diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index 00a981915..466f6520b 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -11,7 +11,7 @@ use body::{Body, Binary}; use headers::ContentEncoding; use httprequest::HttpMessage; use httpresponse::HttpResponse; -use super::encoding::PayloadEncoder; +use super::encoding::ContentEncoder; use super::shared::SharedBytes; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; @@ -28,7 +28,7 @@ bitflags! { pub(crate) struct H2Writer { respond: SendResponse, stream: Option>, - encoder: PayloadEncoder, + encoder: ContentEncoder, flags: Flags, written: u64, buffer: SharedBytes, @@ -40,7 +40,7 @@ impl H2Writer { H2Writer { respond: respond, stream: None, - encoder: PayloadEncoder::empty(buf.clone()), + encoder: ContentEncoder::empty(buf.clone()), flags: Flags::empty(), written: 0, buffer: buf, @@ -113,7 +113,7 @@ impl Writer for H2Writer { -> io::Result { // prepare response self.flags.insert(Flags::STARTED); - self.encoder = PayloadEncoder::new(self.buffer.clone(), req, msg, encoding); + self.encoder = ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding); if let Body::Empty = *msg.body() { self.flags.insert(Flags::EOF); } From a2b98b31e8951a15ed94acc207d04c2bb17f1da3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 25 Feb 2018 20:34:26 +0300 Subject: [PATCH 09/84] refactor payload related futures for HttpRequest --- examples/basics/src/main.rs | 2 +- examples/json/src/main.rs | 10 +- guide/src/qs_7.md | 20 ++-- src/httprequest.rs | 178 ++++++++++++++++++------------------ src/json.rs | 4 +- src/lib.rs | 4 +- src/multipart.rs | 2 +- 7 files changed, 109 insertions(+), 111 deletions(-) diff --git a/examples/basics/src/main.rs b/examples/basics/src/main.rs index f52b09544..b93f5f20b 100644 --- a/examples/basics/src/main.rs +++ b/examples/basics/src/main.rs @@ -22,7 +22,7 @@ fn index(mut req: HttpRequest) -> Result { println!("{:?}", req); // example of ... - if let Ok(ch) = req.payload_mut().readany().poll() { + if let Ok(ch) = req.poll() { if let futures::Async::Ready(Some(d)) = ch { println!("{}", String::from_utf8_lossy(d.as_ref())); } diff --git a/examples/json/src/main.rs b/examples/json/src/main.rs index 719d74853..3247e5d6c 100644 --- a/examples/json/src/main.rs +++ b/examples/json/src/main.rs @@ -34,9 +34,9 @@ fn index(req: HttpRequest) -> Box> { const MAX_SIZE: usize = 262_144; // max payload size is 256k /// This handler manually load request payload and parse serde json -fn index_manual(mut req: HttpRequest) -> Box> { - // readany() returns asynchronous stream of Bytes objects - req.payload_mut().readany() +fn index_manual(req: HttpRequest) -> Box> { + // HttpRequest is stream of Bytes objects + req // `Future::from_err` acts like `?` in that it coerces the error type from // the future into the final error type .from_err() @@ -63,8 +63,8 @@ fn index_manual(mut req: HttpRequest) -> Box Box> { - req.payload_mut().readany().concat2() +fn index_mjsonrust(req: HttpRequest) -> Box> { + req.concat2() .from_err() .and_then(|body| { // body is loaded, now we can deserialize json-rust diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 3a96529a0..81990361e 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -109,7 +109,7 @@ struct MyObj {name: String, number: i32} fn index(mut req: HttpRequest) -> Box> { // `concat2` will asynchronously read each chunk of the request body and // return a single, concatenated, chunk - req.payload_mut().readany().concat2() + req.payload_mut().concat2() // `Future::from_err` acts like `?` in that it coerces the error type from // the future into the final error type .from_err() @@ -256,13 +256,13 @@ fn index(mut req: HttpRequest) -> Box> { ## Streaming request -Actix uses [*Payload*](../actix_web/payload/struct.Payload.html) object as request payload stream. -*HttpRequest* provides several methods, which can be used for payload access. -At the same time *Payload* implements *Stream* trait, so it could be used with various -stream combinators. Also *Payload* provides several convenience methods that return -future object that resolve to Bytes object. - -* *readany()* method returns *Stream* of *Bytes* objects. +*HttpRequest* is a stream of `Bytes` objects. It could be used to read request +body payload. At the same time actix uses +[*Payload*](../actix_web/payload/struct.Payload.html) object. +*HttpRequest* provides several methods, which can be used for +payload access.At the same time *Payload* implements *Stream* trait, so it +could be used with various stream combinators. Also *Payload* provides +several convenience methods that return future object that resolve to Bytes object. * *readexactly()* method returns *Future* that resolves when specified number of bytes get received. @@ -283,9 +283,7 @@ use futures::{Future, Stream}; fn index(mut req: HttpRequest) -> Box> { - req.payload() - .readany() - .from_err() + req.from_err() .fold((), |_, chunk| { println!("Chunk: {:?}", chunk); result::<_, error::PayloadError>(Ok(())) diff --git a/src/httprequest.rs b/src/httprequest.rs index e46a7c3ee..279b7d979 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -5,7 +5,7 @@ use std::net::SocketAddr; use std::collections::HashMap; use bytes::{Bytes, BytesMut}; use cookie::Cookie; -use futures::{Async, Future, Stream, Poll}; +use futures::{Future, Stream, Poll}; use http_range::HttpRange; use serde::de::DeserializeOwned; use mime::Mime; @@ -155,8 +155,8 @@ impl HttpRequest { HttpRequest(self.0.clone(), None, None) } - // get mutable reference for inner message - // mutable reference should not be returned as result for request's method + /// get mutable reference for inner message + /// mutable reference should not be returned as result for request's method #[inline(always)] #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] pub(crate) fn as_mut(&self) -> &mut HttpMessage { @@ -480,8 +480,8 @@ impl HttpRequest { /// } /// # fn main() {} /// ``` - pub fn body(&self) -> RequestBody { - RequestBody::from_request(self) + pub fn body(self) -> RequestBody { + RequestBody::from(self) } /// Return stream to http payload processes as multipart. @@ -518,7 +518,7 @@ impl HttpRequest { /// } /// # fn main() {} /// ``` - pub fn multipart(&mut self) -> Multipart { + pub fn multipart(self) -> Multipart { Multipart::from_request(self) } @@ -549,10 +549,8 @@ impl HttpRequest { /// } /// # fn main() {} /// ``` - pub fn urlencoded(&self) -> UrlEncoded { - UrlEncoded::from(self.payload().clone(), - self.headers(), - self.chunked().unwrap_or(false)) + pub fn urlencoded(self) -> UrlEncoded { + UrlEncoded::from(self) } /// Parse `application/json` encoded body. @@ -585,7 +583,7 @@ impl HttpRequest { /// } /// # fn main() {} /// ``` - pub fn json(&self) -> JsonBody { + pub fn json(self) -> JsonBody { JsonBody::from_request(self) } } @@ -638,49 +636,24 @@ impl fmt::Debug for HttpRequest { /// Future that resolves to a parsed urlencoded values. pub struct UrlEncoded { - pl: Payload, - body: BytesMut, - error: Option, + req: Option>, + limit: usize, + fut: Option, Error=UrlencodedError>>>, } impl UrlEncoded { - pub fn from(pl: Payload, headers: &HeaderMap, chunked: bool) -> UrlEncoded { - let mut encoded = UrlEncoded { - pl: pl, - body: BytesMut::new(), - error: None - }; - - if chunked { - encoded.error = Some(UrlencodedError::Chunked); - } else if let Some(len) = headers.get(header::CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - if len > 262_144 { - encoded.error = Some(UrlencodedError::Overflow); - } - } else { - encoded.error = Some(UrlencodedError::UnknownLength); - } - } else { - encoded.error = Some(UrlencodedError::UnknownLength); - } + pub fn from(req: HttpRequest) -> UrlEncoded { + UrlEncoded { + req: Some(req.clone_without_state()), + limit: 262_144, + fut: None, } + } - // check content type - if encoded.error.is_none() { - if let Some(content_type) = headers.get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - if content_type.to_lowercase() == "application/x-www-form-urlencoded" { - return encoded - } - } - } - encoded.error = Some(UrlencodedError::ContentType); - return encoded - } - - encoded + /// Change max size of payload. By default max size is 256Kb + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self } } @@ -689,48 +662,76 @@ impl Future for UrlEncoded { type Error = UrlencodedError; fn poll(&mut self) -> Poll { - if let Some(err) = self.error.take() { - return Err(err) - } + if let Some(req) = self.req.take() { + if req.chunked().unwrap_or(false) { + return Err(UrlencodedError::Chunked) + } else if let Some(len) = req.headers().get(header::CONTENT_LENGTH) { + if let Ok(s) = len.to_str() { + if let Ok(len) = s.parse::() { + if len > 262_144 { + return Err(UrlencodedError::Overflow); + } + } else { + return Err(UrlencodedError::UnknownLength) + } + } else { + return Err(UrlencodedError::UnknownLength) + } + } - loop { - return match self.pl.poll() { - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(None)) => { + // check content type + let mut err = true; + if let Some(content_type) = req.headers().get(header::CONTENT_TYPE) { + if let Ok(content_type) = content_type.to_str() { + if content_type.to_lowercase() == "application/x-www-form-urlencoded" { + err = false; + } + } + } + if err { + return Err(UrlencodedError::ContentType); + } + + // future + let limit = self.limit; + let fut = req.from_err() + .fold(BytesMut::new(), move |mut body, chunk| { + if (body.len() + chunk.len()) > limit { + Err(UrlencodedError::Overflow) + } else { + body.extend_from_slice(&chunk); + Ok(body) + } + }) + .map(|body| { let mut m = HashMap::new(); - for (k, v) in form_urlencoded::parse(&self.body) { + for (k, v) in form_urlencoded::parse(&body) { m.insert(k.into(), v.into()); } - Ok(Async::Ready(m)) - }, - Ok(Async::Ready(Some(item))) => { - self.body.extend_from_slice(&item); - continue - }, - Err(err) => Err(err.into()), - } + m + }); + self.fut = Some(Box::new(fut)); } + + self.fut.as_mut().expect("UrlEncoded could not be used second time").poll() } } /// Future that resolves to a complete request body. pub struct RequestBody { - pl: Payload, - body: BytesMut, limit: usize, req: Option>, + fut: Option>>, } impl RequestBody { /// Create `RequestBody` for request. - pub fn from_request(req: &HttpRequest) -> RequestBody { - let pl = req.payload().clone(); + pub fn from(req: HttpRequest) -> RequestBody { RequestBody { - pl: pl, - body: BytesMut::new(), limit: 262_144, - req: Some(req.clone_without_state()) + req: Some(req.clone_without_state()), + fut: None, } } @@ -760,25 +761,24 @@ impl Future for RequestBody { return Err(PayloadError::UnknownLength); } } + + // future + let limit = self.limit; + self.fut = Some(Box::new( + req.from_err() + .fold(BytesMut::new(), move |mut body, chunk| { + if (body.len() + chunk.len()) > limit { + Err(PayloadError::Overflow) + } else { + body.extend_from_slice(&chunk); + Ok(body) + } + }) + .map(|body| body.freeze()) + )); } - loop { - return match self.pl.poll() { - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(None)) => { - Ok(Async::Ready(self.body.take().freeze())) - }, - Ok(Async::Ready(Some(chunk))) => { - if (self.body.len() + chunk.len()) > self.limit { - Err(PayloadError::Overflow) - } else { - self.body.extend_from_slice(&chunk); - continue - } - }, - Err(err) => Err(err), - } - } + self.fut.as_mut().expect("UrlEncoded could not be used second time").poll() } } diff --git a/src/json.rs b/src/json.rs index 86e612048..7f13db848 100644 --- a/src/json.rs +++ b/src/json.rs @@ -86,10 +86,10 @@ pub struct JsonBody{ impl JsonBody { /// Create `JsonBody` for request. - pub fn from_request(req: &HttpRequest) -> Self { + pub fn from_request(req: HttpRequest) -> Self { JsonBody{ limit: 262_144, - req: Some(req.clone()), + req: Some(req), fut: None, ct: "application/json", } diff --git a/src/lib.rs b/src/lib.rs index ab683b6d9..23c3f4982 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,11 +32,11 @@ //! * Supported *HTTP/1.x* and *HTTP/2.0* protocols //! * Streaming and pipelining //! * Keep-alive and slow requests handling -//! * `WebSockets` +//! * WebSockets server/client //! * Transparent content compression/decompression (br, gzip, deflate) //! * Configurable request routing //! * Multipart streams -//! * Middlewares (`Logger`, `Session`, `DefaultHeaders`) +//! * Middlewares (`Logger`, `Session`, `CORS`, `DefaultHeaders`) //! * Graceful server shutdown //! * Built on top of [Actix](https://github.com/actix/actix). diff --git a/src/multipart.rs b/src/multipart.rs index 0fbc906d9..f97ccf3db 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -85,7 +85,7 @@ impl Multipart { } /// Create multipart instance for request. - pub fn from_request(req: &mut HttpRequest) -> Multipart { + pub fn from_request(req: HttpRequest) -> Multipart { match Multipart::boundary(req.headers()) { Ok(boundary) => Multipart::new(boundary, req.payload().clone()), Err(err) => From 6ef9c60361d00b66437e88e0b4b449ac909f7a9b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 25 Feb 2018 21:26:58 +0300 Subject: [PATCH 10/84] add Read and AsyncRead impl to HttpRequest --- src/error.rs | 3 +-- src/httprequest.rs | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/error.rs b/src/error.rs index a37727cbd..b63206ff1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -96,8 +96,7 @@ impl From for Error { /// Compatibility for `failure::Error` impl ResponseError for failure::Compat - where T: fmt::Display + fmt::Debug + Sync + Send + 'static -{ } + where T: fmt::Display + fmt::Debug + Sync + Send + 'static { } impl From for Error { fn from(err: failure::Error) -> Error { diff --git a/src/httprequest.rs b/src/httprequest.rs index 279b7d979..0d721c87f 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -1,16 +1,18 @@ //! HTTP Request message related code. -use std::{str, fmt, mem}; +use std::{io, cmp, str, fmt, mem}; use std::rc::Rc; use std::net::SocketAddr; use std::collections::HashMap; use bytes::{Bytes, BytesMut}; use cookie::Cookie; -use futures::{Future, Stream, Poll}; +use futures::{Async, Future, Stream, Poll}; use http_range::HttpRange; use serde::de::DeserializeOwned; use mime::Mime; +use failure; use url::{Url, form_urlencoded}; use http::{header, Uri, Method, Version, HeaderMap, Extensions}; +use tokio_io::AsyncRead; use info::ConnectionInfo; use param::Params; @@ -611,6 +613,38 @@ impl Stream for HttpRequest { } } +impl io::Read for HttpRequest { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self.payload_mut().poll() { + Ok(Async::Ready(Some(mut b))) => { + let i = cmp::min(b.len(), buf.len()); + buf.copy_from_slice(&b.split_to(i)[..i]); + + if !b.is_empty() { + self.payload_mut().unread_data(b); + } + + if i < buf.len() { + match self.read(&mut buf[i..]) { + Ok(n) => Ok(i + n), + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Ok(i), + Err(e) => Err(e), + } + } else { + Ok(i) + } + } + Ok(Async::Ready(None)) => Ok(0), + Ok(Async::NotReady) => + Err(io::Error::new(io::ErrorKind::WouldBlock, "Not ready")), + Err(e) => + Err(io::Error::new(io::ErrorKind::Other, failure::Error::from(e).compat())), + } + } +} + +impl AsyncRead for HttpRequest {} + impl fmt::Debug for HttpRequest { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let res = write!(f, "\nHttpRequest {:?} {}:{}\n", From 0a3b776aa7bd3789fb2f61c2a1d504d6cba52e2a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 26 Feb 2018 05:55:07 +0300 Subject: [PATCH 11/84] refactor multipart stream --- examples/multipart/src/main.rs | 2 +- src/error.rs | 3 + src/httprequest.rs | 5 +- src/json.rs | 12 +- src/multipart.rs | 315 ++++++++++++++------------------- src/payload.rs | 142 +++++++++++++++ 6 files changed, 293 insertions(+), 186 deletions(-) diff --git a/examples/multipart/src/main.rs b/examples/multipart/src/main.rs index 7da6145a9..343dde167 100644 --- a/examples/multipart/src/main.rs +++ b/examples/multipart/src/main.rs @@ -11,7 +11,7 @@ use futures::{Future, Stream}; use futures::future::{result, Either}; -fn index(mut req: HttpRequest) -> Box> +fn index(req: HttpRequest) -> Box> { println!("{:?}", req); diff --git a/src/error.rs b/src/error.rs index b63206ff1..458850a0f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -294,6 +294,9 @@ pub enum MultipartError { /// Multipart boundary is not found #[fail(display="Multipart boundary is not found")] Boundary, + /// Multipart stream is incomplete + #[fail(display="Multipart stream is incomplete")] + Incomplete, /// Error during field parsing #[fail(display="{}", _0)] Parse(#[cause] ParseError), diff --git a/src/httprequest.rs b/src/httprequest.rs index 0d721c87f..3ca4fdf96 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -520,8 +520,9 @@ impl HttpRequest { /// } /// # fn main() {} /// ``` - pub fn multipart(self) -> Multipart { - Multipart::from_request(self) + pub fn multipart(self) -> Multipart> { + let boundary = Multipart::boundary(self.headers()); + Multipart::new(boundary, self) } /// Parse `application/x-www-form-urlencoded` encoded body. diff --git a/src/json.rs b/src/json.rs index 7f13db848..341bc32dd 100644 --- a/src/json.rs +++ b/src/json.rs @@ -188,27 +188,31 @@ mod tests { #[test] fn test_json_body() { - let mut req = HttpRequest::default(); + let req = HttpRequest::default(); let mut json = req.json::(); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - let mut json = req.json::().content_type("text/json"); + let mut req = HttpRequest::default(); req.headers_mut().insert(header::CONTENT_TYPE, header::HeaderValue::from_static("application/json")); + let mut json = req.json::().content_type("text/json"); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - let mut json = req.json::().limit(100); + let mut req = HttpRequest::default(); req.headers_mut().insert(header::CONTENT_TYPE, header::HeaderValue::from_static("application/json")); req.headers_mut().insert(header::CONTENT_LENGTH, header::HeaderValue::from_static("10000")); + let mut json = req.json::().limit(100); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow); + let mut req = HttpRequest::default(); + req.headers_mut().insert(header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json")); req.headers_mut().insert(header::CONTENT_LENGTH, header::HeaderValue::from_static("16")); req.payload_mut().unread_data(Bytes::from_static(b"{\"name\": \"test\"}")); let mut json = req.json::(); assert_eq!(json.poll().ok().unwrap(), Async::Ready(MyObject{name: "test".to_owned()})); } - } diff --git a/src/multipart.rs b/src/multipart.rs index f97ccf3db..d3790a9a4 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -9,12 +9,11 @@ use httparse; use bytes::Bytes; use http::HttpTryFrom; use http::header::{self, HeaderMap, HeaderName, HeaderValue}; -use futures::{Async, Future, Stream, Poll}; +use futures::{Async, Stream, Poll}; use futures::task::{Task, current as current_task}; use error::{ParseError, PayloadError, MultipartError}; -use payload::Payload; -use httprequest::HttpRequest; +use payload::PayloadHelper; const MAX_HEADERS: usize = 32; @@ -24,27 +23,24 @@ const MAX_HEADERS: usize = 32; /// Stream implementation. /// `MultipartItem::Field` contains multipart field. `MultipartItem::Multipart` /// is used for nested multipart streams. -#[derive(Debug)] -pub struct Multipart { +pub struct Multipart { safety: Safety, error: Option, - inner: Option>>, + inner: Option>>>, } /// -#[derive(Debug)] -pub enum MultipartItem { +pub enum MultipartItem { /// Multipart field - Field(Field), + Field(Field), /// Nested multipart stream - Nested(Multipart), + Nested(Multipart), } -#[derive(Debug)] -enum InnerMultipartItem { +enum InnerMultipartItem { None, - Field(Rc>), - Multipart(Rc>), + Field(Rc>>), + Multipart(Rc>>), } #[derive(PartialEq, Debug)] @@ -59,57 +55,14 @@ enum InnerState { Headers, } -#[derive(Debug)] -struct InnerMultipart { - payload: PayloadRef, +struct InnerMultipart { + payload: PayloadRef, boundary: String, state: InnerState, - item: InnerMultipartItem, + item: InnerMultipartItem, } -impl Multipart { - - /// Create multipart instance for boundary. - pub fn new(boundary: String, payload: Payload) -> Multipart { - Multipart { - error: None, - safety: Safety::new(), - inner: Some(Rc::new(RefCell::new( - InnerMultipart { - payload: PayloadRef::new(payload), - boundary: boundary, - state: InnerState::FirstBoundary, - item: InnerMultipartItem::None, - }))) - } - } - - /// Create multipart instance for request. - pub fn from_request(req: HttpRequest) -> Multipart { - match Multipart::boundary(req.headers()) { - Ok(boundary) => Multipart::new(boundary, req.payload().clone()), - Err(err) => - Multipart { - error: Some(err), - safety: Safety::new(), - inner: None, - } - } - } - - // /// Create multipart instance for client response. - // pub fn from_response(resp: &mut ClientResponse) -> Multipart { - // match Multipart::boundary(resp.headers()) { - // Ok(boundary) => Multipart::new(boundary, resp.payload().clone()), - // Err(err) => - // Multipart { - // error: Some(err), - // safety: Safety::new(), - // inner: None, - // } - // } - // } - +impl Multipart<()> { /// Extract boundary info from headers. pub fn boundary(headers: &HeaderMap) -> Result { if let Some(content_type) = headers.get(header::CONTENT_TYPE) { @@ -132,8 +85,34 @@ impl Multipart { } } -impl Stream for Multipart { - type Item = MultipartItem; +impl Multipart where S: Stream { + + /// Create multipart instance for boundary. + pub fn new(boundary: Result, stream: S) -> Multipart { + match boundary { + Ok(boundary) => Multipart { + error: None, + safety: Safety::new(), + inner: Some(Rc::new(RefCell::new( + InnerMultipart { + payload: PayloadRef::new(PayloadHelper::new(stream)), + boundary: boundary, + state: InnerState::FirstBoundary, + item: InnerMultipartItem::None, + }))) + }, + Err(err) => + Multipart { + error: Some(err), + safety: Safety::new(), + inner: None, + } + } + } +} + +impl Stream for Multipart where S: Stream { + type Item = MultipartItem; type Error = MultipartError; fn poll(&mut self) -> Poll, Self::Error> { @@ -147,13 +126,14 @@ impl Stream for Multipart { } } -impl InnerMultipart { +impl InnerMultipart where S: Stream { - fn read_headers(payload: &mut Payload) -> Poll + fn read_headers(payload: &mut PayloadHelper) -> Poll { - match payload.readuntil(b"\r\n\r\n").poll()? { + match payload.readuntil(b"\r\n\r\n")? { Async::NotReady => Ok(Async::NotReady), - Async::Ready(bytes) => { + Async::Ready(None) => Err(MultipartError::Incomplete), + Async::Ready(Some(bytes)) => { let mut hdrs = [httparse::EMPTY_HEADER; MAX_HEADERS]; match httparse::parse_headers(&bytes, &mut hdrs) { Ok(httparse::Status::Complete((_, hdrs))) => { @@ -179,12 +159,14 @@ impl InnerMultipart { } } - fn read_boundary(payload: &mut Payload, boundary: &str) -> Poll + fn read_boundary(payload: &mut PayloadHelper, boundary: &str) + -> Poll { // TODO: need to read epilogue - match payload.readline().poll()? { + match payload.readline()? { Async::NotReady => Ok(Async::NotReady), - Async::Ready(chunk) => { + Async::Ready(None) => Err(MultipartError::Incomplete), + Async::Ready(Some(chunk)) => { if chunk.len() == boundary.len() + 4 && &chunk[..2] == b"--" && &chunk[2..boundary.len()+2] == boundary.as_bytes() @@ -203,39 +185,42 @@ impl InnerMultipart { } } - fn skip_until_boundary(payload: &mut Payload, boundary: &str) -> Poll + fn skip_until_boundary(payload: &mut PayloadHelper, boundary: &str) + -> Poll { let mut eof = false; loop { - if let Async::Ready(chunk) = payload.readline().poll()? { - if chunk.is_empty() { - //ValueError("Could not find starting boundary %r" - //% (self._boundary)) - } - if chunk.len() < boundary.len() { - continue - } - if &chunk[..2] == b"--" && &chunk[2..chunk.len()-2] == boundary.as_bytes() { - break; - } else { - if chunk.len() < boundary.len() + 2{ + match payload.readline()? { + Async::Ready(Some(chunk)) => { + if chunk.is_empty() { + //ValueError("Could not find starting boundary %r" + //% (self._boundary)) + } + if chunk.len() < boundary.len() { continue } - let b: &[u8] = boundary.as_ref(); - if &chunk[..boundary.len()] == b && - &chunk[boundary.len()..boundary.len()+2] == b"--" { - eof = true; - break; + if &chunk[..2] == b"--" && &chunk[2..chunk.len()-2] == boundary.as_bytes() { + break; + } else { + if chunk.len() < boundary.len() + 2{ + continue } - } - } else { - return Ok(Async::NotReady) + let b: &[u8] = boundary.as_ref(); + if &chunk[..boundary.len()] == b && + &chunk[boundary.len()..boundary.len()+2] == b"--" { + eof = true; + break; + } + } + }, + Async::NotReady => return Ok(Async::NotReady), + Async::Ready(None) => return Err(MultipartError::Incomplete), } } Ok(Async::Ready(eof)) } - fn poll(&mut self, safety: &Safety) -> Poll, MultipartError> { + fn poll(&mut self, safety: &Safety) -> Poll>, MultipartError> { if self.state == InnerState::Eof { Ok(Async::Ready(None)) } else { @@ -247,25 +232,18 @@ impl InnerMultipart { let stop = match self.item { InnerMultipartItem::Field(ref mut field) => { match field.borrow_mut().poll(safety)? { - Async::NotReady => { - return Ok(Async::NotReady) - } - Async::Ready(Some(_)) => - continue, - Async::Ready(None) => - true, + Async::NotReady => return Ok(Async::NotReady), + Async::Ready(Some(_)) => continue, + Async::Ready(None) => true, } - } + }, InnerMultipartItem::Multipart(ref mut multipart) => { match multipart.borrow_mut().poll(safety)? { - Async::NotReady => - return Ok(Async::NotReady), - Async::Ready(Some(_)) => - continue, - Async::Ready(None) => - true, + Async::NotReady => return Ok(Async::NotReady), + Async::Ready(Some(_)) => continue, + Async::Ready(None) => true, } - } + }, _ => false, }; if stop { @@ -281,25 +259,22 @@ impl InnerMultipart { match self.state { // read until first boundary InnerState::FirstBoundary => { - if let Async::Ready(eof) = - InnerMultipart::skip_until_boundary(payload, &self.boundary)? - { - if eof { - self.state = InnerState::Eof; - return Ok(Async::Ready(None)); - } else { - self.state = InnerState::Headers; - } - } else { - return Ok(Async::NotReady) + match InnerMultipart::skip_until_boundary(payload, &self.boundary)? { + Async::Ready(eof) => { + if eof { + self.state = InnerState::Eof; + return Ok(Async::Ready(None)); + } else { + self.state = InnerState::Headers; + } + }, + Async::NotReady => return Ok(Async::NotReady), } - } + }, // read boundary InnerState::Boundary => { match InnerMultipart::read_boundary(payload, &self.boundary)? { - Async::NotReady => { - return Ok(Async::NotReady) - } + Async::NotReady => return Ok(Async::NotReady), Async::Ready(eof) => { if eof { self.state = InnerState::Eof; @@ -375,7 +350,7 @@ impl InnerMultipart { } } -impl Drop for InnerMultipart { +impl Drop for InnerMultipart { fn drop(&mut self) { // InnerMultipartItem::Field has to be dropped first because of Safety. self.item = InnerMultipartItem::None; @@ -383,17 +358,17 @@ impl Drop for InnerMultipart { } /// A single field in a multipart stream -pub struct Field { +pub struct Field { ct: mime::Mime, headers: HeaderMap, - inner: Rc>, + inner: Rc>>, safety: Safety, } -impl Field { +impl Field where S: Stream { fn new(safety: Safety, headers: HeaderMap, - ct: mime::Mime, inner: Rc>) -> Self { + ct: mime::Mime, inner: Rc>>) -> Self { Field { ct: ct, headers: headers, @@ -411,7 +386,7 @@ impl Field { } } -impl Stream for Field { +impl Stream for Field where S: Stream { type Item = Bytes; type Error = MultipartError; @@ -424,7 +399,7 @@ impl Stream for Field { } } -impl fmt::Debug for Field { +impl fmt::Debug for Field { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let res = write!(f, "\nMultipartField: {}\n", self.ct); let _ = write!(f, " boundary: {}\n", self.inner.borrow().boundary); @@ -441,18 +416,17 @@ impl fmt::Debug for Field { } } -#[derive(Debug)] -struct InnerField { - payload: Option, +struct InnerField { + payload: Option>, boundary: String, eof: bool, length: Option, } -impl InnerField { +impl InnerField where S: Stream { - fn new(payload: PayloadRef, boundary: String, headers: &HeaderMap) - -> Result + fn new(payload: PayloadRef, boundary: String, headers: &HeaderMap) + -> Result, PayloadError> { let len = if let Some(len) = headers.get(header::CONTENT_LENGTH) { if let Ok(s) = len.to_str() { @@ -477,14 +451,15 @@ impl InnerField { /// Reads body part content chunk of the specified size. /// The body part must has `Content-Length` header with proper value. - fn read_len(payload: &mut Payload, size: &mut u64) -> Poll, MultipartError> + fn read_len(payload: &mut PayloadHelper, size: &mut u64) + -> Poll, MultipartError> { if *size == 0 { Ok(Async::Ready(None)) } else { - match payload.poll() { + match payload.readany() { Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(None)) => Ok(Async::Ready(None)), + Ok(Async::Ready(None)) => Err(MultipartError::Incomplete), Ok(Async::Ready(Some(mut chunk))) => { let len = cmp::min(chunk.len() as u64, *size); *size -= len; @@ -501,16 +476,19 @@ impl InnerField { /// Reads content chunk of body part with unknown length. /// The `Content-Length` header for body part is not necessary. - fn read_stream(payload: &mut Payload, boundary: &str) -> Poll, MultipartError> + fn read_stream(payload: &mut PayloadHelper, boundary: &str) + -> Poll, MultipartError> { - match payload.readuntil(b"\r").poll()? { + match payload.readuntil(b"\r")? { Async::NotReady => Ok(Async::NotReady), - Async::Ready(mut chunk) => { + Async::Ready(None) => Err(MultipartError::Incomplete), + Async::Ready(Some(mut chunk)) => { if chunk.len() == 1 { payload.unread_data(chunk); - match payload.readexactly(boundary.len() + 4).poll()? { + match payload.readexactly(boundary.len() + 4)? { Async::NotReady => Ok(Async::NotReady), - Async::Ready(chunk) => { + Async::Ready(None) => Err(MultipartError::Incomplete), + Async::Ready(Some(chunk)) => { if &chunk[..2] == b"\r\n" && &chunk[2..4] == b"--" && &chunk[4..] == boundary.as_bytes() { @@ -535,24 +513,6 @@ impl InnerField { if self.payload.is_none() { return Ok(Async::Ready(None)) } - if self.eof { - if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) { - match payload.readline().poll()? { - Async::NotReady => - return Ok(Async::NotReady), - Async::Ready(chunk) => { - assert_eq!( - chunk.as_ref(), b"\r\n", - "reader did not read all the data or it is malformed"); - } - } - } else { - return Ok(Async::NotReady); - } - - self.payload.take(); - return Ok(Async::Ready(None)) - } let result = if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) { let res = if let Some(ref mut len) = self.length { @@ -566,12 +526,13 @@ impl InnerField { Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)), Async::Ready(None) => { self.eof = true; - match payload.readline().poll()? { + match payload.readline()? { Async::NotReady => Async::NotReady, - Async::Ready(chunk) => { - assert_eq!( - chunk.as_ref(), b"\r\n", - "reader did not read all the data or it is malformed"); + Async::Ready(None) => Async::Ready(None), + Async::Ready(Some(line)) => { + if line.as_ref() != b"\r\n" { + warn!("multipart field did not read all the data or it is malformed"); + } Async::Ready(None) } } @@ -588,25 +549,22 @@ impl InnerField { } } -#[derive(Debug)] -struct PayloadRef { - task: Option, - payload: Rc, +struct PayloadRef { + payload: Rc>, } -impl PayloadRef { - fn new(payload: Payload) -> PayloadRef { +impl PayloadRef where S: Stream { + fn new(payload: PayloadHelper) -> PayloadRef { PayloadRef { - task: None, payload: Rc::new(payload), } } - fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut Payload> + fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut PayloadHelper> where 'a: 'b { if s.current() { - let payload: &mut Payload = unsafe { + let payload: &mut PayloadHelper = unsafe { &mut *(self.payload.as_ref() as *const _ as *mut _)}; Some(payload) } else { @@ -615,10 +573,9 @@ impl PayloadRef { } } -impl Clone for PayloadRef { - fn clone(&self) -> PayloadRef { +impl Clone for PayloadRef { + fn clone(&self) -> PayloadRef { PayloadRef { - task: Some(current_task()), payload: Rc::clone(&self.payload), } } @@ -733,7 +690,7 @@ mod tests { sender.feed_data(bytes); let mut multipart = Multipart::new( - "abbc761f78ff4d7cb7573b5a23f96ef0".to_owned(), payload); + Ok("abbc761f78ff4d7cb7573b5a23f96ef0".to_owned()), payload); match multipart.poll() { Ok(Async::Ready(Some(item))) => { match item { diff --git a/src/payload.rs b/src/payload.rs index 8d5bd7206..4cc0eaf68 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -411,6 +411,148 @@ impl Inner { } } +pub struct PayloadHelper { + len: usize, + items: VecDeque, + stream: S, +} + +impl PayloadHelper where S: Stream { + + pub fn new(stream: S) -> Self { + PayloadHelper { + len: 0, + items: VecDeque::new(), + stream: stream, + } + } + + fn poll_stream(&mut self) -> Poll { + self.stream.poll().map(|res| { + match res { + Async::Ready(Some(data)) => { + self.len += data.len(); + self.items.push_back(data); + Async::Ready(true) + }, + Async::Ready(None) => Async::Ready(false), + Async::NotReady => Async::NotReady, + } + }) + } + + pub fn len(&self) -> usize { + self.len + } + + pub fn readany(&mut self) -> Poll, PayloadError> { + if let Some(data) = self.items.pop_front() { + self.len -= data.len(); + Ok(Async::Ready(Some(data))) + } else { + match self.poll_stream()? { + Async::Ready(true) => self.readany(), + Async::Ready(false) => Ok(Async::Ready(None)), + Async::NotReady => Ok(Async::NotReady), + } + } + } + + pub fn readexactly(&mut self, size: usize) -> Poll, PayloadError> { + if size <= self.len { + let mut buf = BytesMut::with_capacity(size); + while buf.len() < size { + let mut chunk = self.items.pop_front().unwrap(); + let rem = cmp::min(size - buf.len(), chunk.len()); + self.len -= rem; + buf.extend_from_slice(&chunk.split_to(rem)); + if !chunk.is_empty() { + self.items.push_front(chunk); + return Ok(Async::Ready(Some(buf.freeze()))) + } + } + } + + match self.poll_stream()? { + Async::Ready(true) => self.readexactly(size), + Async::Ready(false) => Ok(Async::Ready(None)), + Async::NotReady => Ok(Async::NotReady), + } + } + + pub fn readuntil(&mut self, line: &[u8]) -> Poll, PayloadError> { + let mut idx = 0; + let mut num = 0; + let mut offset = 0; + let mut found = false; + let mut length = 0; + + for no in 0..self.items.len() { + { + let chunk = &self.items[no]; + for (pos, ch) in chunk.iter().enumerate() { + if *ch == line[idx] { + idx += 1; + if idx == line.len() { + num = no; + offset = pos+1; + length += pos+1; + found = true; + break; + } + } else { + idx = 0 + } + } + if !found { + length += chunk.len() + } + } + + if found { + let mut buf = BytesMut::with_capacity(length); + if num > 0 { + for _ in 0..num { + buf.extend_from_slice(&self.items.pop_front().unwrap()); + } + } + if offset > 0 { + let mut chunk = self.items.pop_front().unwrap(); + buf.extend_from_slice(&chunk.split_to(offset)); + if !chunk.is_empty() { + self.items.push_front(chunk) + } + } + self.len -= length; + return Ok(Async::Ready(Some(buf.freeze()))) + } + } + + match self.poll_stream()? { + Async::Ready(true) => self.readuntil(line), + Async::Ready(false) => Ok(Async::Ready(None)), + Async::NotReady => Ok(Async::NotReady), + } + } + + pub fn readline(&mut self) -> Poll, PayloadError> { + self.readuntil(b"\n") + } + + pub fn unread_data(&mut self, data: Bytes) { + self.len += data.len(); + self.items.push_front(data); + } + + pub fn remaining(&mut self) -> Bytes { + self.items.iter_mut() + .fold(BytesMut::new(), |mut b, c| { + b.extend_from_slice(c); + b + }).freeze() + } +} + #[cfg(test)] mod tests { use super::*; From 56ae565688739649f27d35866058433361a29a3c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 26 Feb 2018 08:02:58 -0800 Subject: [PATCH 12/84] fix guide examples --- guide/src/qs_7.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 81990361e..2b063f57c 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -106,10 +106,10 @@ use futures::{Future, Stream}; #[derive(Serialize, Deserialize)] struct MyObj {name: String, number: i32} -fn index(mut req: HttpRequest) -> Box> { +fn index(req: HttpRequest) -> Box> { // `concat2` will asynchronously read each chunk of the request body and // return a single, concatenated, chunk - req.payload_mut().concat2() + req.concat2() // `Future::from_err` acts like `?` in that it coerces the error type from // the future into the final error type .from_err() @@ -170,12 +170,14 @@ Enabling chunked encoding for *HTTP/2.0* responses is forbidden. ```rust # extern crate actix_web; +# extern crate futures; +# use futures::Stream; use actix_web::*; fn index(req: HttpRequest) -> HttpResponse { HttpResponse::Ok() .chunked() - .body(Body::Streaming(payload::Payload::empty().stream())).unwrap() + .body(Body::Streaming(Box::new(payload::Payload::empty().from_err()))).unwrap() } # fn main() {} ``` From 644f1a951893bd0b1cc10d29cc97d542dc453878 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 26 Feb 2018 13:58:23 -0800 Subject: [PATCH 13/84] refactor ws frame parser --- Cargo.toml | 1 - src/client/parser.rs | 6 +- src/error.rs | 30 +++++- src/handler.rs | 1 - src/lib.rs | 6 +- src/multipart.rs | 4 +- src/payload.rs | 35 ++++++- src/ws/client.rs | 242 ++++++++++++++++++------------------------- src/ws/frame.rs | 147 +++++++++++++++----------- src/ws/mod.rs | 139 +++++++++---------------- tests/test_ws.rs | 3 +- 11 files changed, 304 insertions(+), 310 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d8a4b93a9..b7999b743 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,7 +77,6 @@ tokio-tls = { version="0.1", optional = true } openssl = { version="0.10", optional = true } tokio-openssl = { version="0.2", optional = true } -backtrace="*" [dependencies.actix] version = "0.5" diff --git a/src/client/parser.rs b/src/client/parser.rs index 03ba23f99..6a0ee1080 100644 --- a/src/client/parser.rs +++ b/src/client/parser.rs @@ -151,7 +151,9 @@ impl HttpResponseParser { } } - let decoder = if let Some(len) = hdrs.get(header::CONTENT_LENGTH) { + let decoder = if status == StatusCode::SWITCHING_PROTOCOLS { + Some(Decoder::eof()) + } else if let Some(len) = hdrs.get(header::CONTENT_LENGTH) { // Content-Length if let Ok(s) = len.to_str() { if let Ok(len) = s.parse::() { @@ -167,8 +169,6 @@ impl HttpResponseParser { } else if chunked(&hdrs)? { // Chunked encoding Some(Decoder::chunked()) - } else if hdrs.contains_key(header::UPGRADE) { - Some(Decoder::eof()) } else { None }; diff --git a/src/error.rs b/src/error.rs index 458850a0f..f94362031 100644 --- a/src/error.rs +++ b/src/error.rs @@ -236,8 +236,6 @@ pub enum PayloadError { impl From for PayloadError { fn from(err: IoError) -> PayloadError { - use backtrace; - println!("IO ERROR {:?}", backtrace::Backtrace::new()); PayloadError::Io(err) } } @@ -391,6 +389,34 @@ impl ResponseError for WsHandshakeError { } } +/// Websocket errors +#[derive(Fail, Debug)] +pub enum WsError { + /// Received an unmasked frame from client + #[fail(display="Received an unmasked frame from client")] + UnmaskedFrame, + /// Received a masked frame from server + #[fail(display="Received a masked frame from server")] + MaskedFrame, + /// Encountered invalid opcode + #[fail(display="Invalid opcode: {}", _0)] + InvalidOpcode(u8), + /// Invalid control frame length + #[fail(display="Invalid control frame length: {}", _0)] + InvalidLength(usize), + /// Payload error + #[fail(display="Payload error: {}", _0)] + Payload(#[cause] PayloadError), +} + +impl ResponseError for WsError {} + +impl From for WsError { + fn from(err: PayloadError) -> WsError { + WsError::Payload(err) + } +} + /// A set of errors that can occur during parsing urlencoded payloads #[derive(Fail, Debug)] pub enum UrlencodedError { diff --git a/src/handler.rs b/src/handler.rs index 857c95398..253024e61 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -395,7 +395,6 @@ impl Handler for NormalizePath { } } } else if p.ends_with('/') { - println!("=== {:?}", p); // try to remove trailing slash let p = p.as_ref().trim_right_matches('/'); if router.has_route(p) { diff --git a/src/lib.rs b/src/lib.rs index 23c3f4982..7ec675657 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,7 @@ //! * Supported *HTTP/1.x* and *HTTP/2.0* protocols //! * Streaming and pipelining //! * Keep-alive and slow requests handling -//! * WebSockets server/client +//! * *WebSockets* server/client //! * Transparent content compression/decompression (br, gzip, deflate) //! * Configurable request routing //! * Multipart streams @@ -44,7 +44,7 @@ specialization, // for impl ErrorResponse for std::error::Error ))] #![cfg_attr(feature = "cargo-clippy", allow( - decimal_literal_representation,))] + decimal_literal_representation,suspicious_arithmetic_impl,))] #[macro_use] extern crate log; @@ -97,8 +97,6 @@ extern crate openssl; #[cfg(feature="openssl")] extern crate tokio_openssl; -extern crate backtrace; - mod application; mod body; mod context; diff --git a/src/multipart.rs b/src/multipart.rs index d3790a9a4..457fe4beb 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -492,10 +492,10 @@ impl InnerField where S: Stream { if &chunk[..2] == b"\r\n" && &chunk[2..4] == b"--" && &chunk[4..] == boundary.as_bytes() { - payload.unread_data(chunk); + payload.unread_data(chunk.freeze()); Ok(Async::Ready(None)) } else { - Ok(Async::Ready(Some(chunk))) + Ok(Async::Ready(Some(chunk.freeze()))) } } } diff --git a/src/payload.rs b/src/payload.rs index 4cc0eaf68..e4ba819d1 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -297,9 +297,9 @@ impl Inner { buf.extend_from_slice(&chunk.split_to(rem)); if !chunk.is_empty() { self.items.push_front(chunk); - return Ok(Async::Ready(buf.freeze())) } } + return Ok(Async::Ready(buf.freeze())) } if let Some(err) = self.err.take() { @@ -423,7 +423,7 @@ impl PayloadHelper where S: Stream { PayloadHelper { len: 0, items: VecDeque::new(), - stream: stream, + stream, } } @@ -445,6 +445,10 @@ impl PayloadHelper where S: Stream { self.len } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn readany(&mut self) -> Poll, PayloadError> { if let Some(data) = self.items.pop_front() { self.len -= data.len(); @@ -458,7 +462,7 @@ impl PayloadHelper where S: Stream { } } - pub fn readexactly(&mut self, size: usize) -> Poll, PayloadError> { + pub fn readexactly(&mut self, size: usize) -> Poll, PayloadError> { if size <= self.len { let mut buf = BytesMut::with_capacity(size); while buf.len() < size { @@ -468,13 +472,34 @@ impl PayloadHelper where S: Stream { buf.extend_from_slice(&chunk.split_to(rem)); if !chunk.is_empty() { self.items.push_front(chunk); - return Ok(Async::Ready(Some(buf.freeze()))) + } + } + return Ok(Async::Ready(Some(buf))) + } + + match self.poll_stream()? { + Async::Ready(true) => self.readexactly(size), + Async::Ready(false) => Ok(Async::Ready(None)), + Async::NotReady => Ok(Async::NotReady), + } + } + + pub fn copy(&mut self, size: usize) -> Poll, PayloadError> { + if size <= self.len { + let mut buf = BytesMut::with_capacity(size); + for chunk in &self.items { + if buf.len() < size { + let rem = cmp::min(size - buf.len(), chunk.len()); + buf.extend_from_slice(&chunk[..rem]); + } + if buf.len() == size { + return Ok(Async::Ready(Some(buf))) } } } match self.poll_stream()? { - Async::Ready(true) => self.readexactly(size), + Async::Ready(true) => self.copy(size), Async::Ready(false) => Ok(Async::Ready(None)), Async::NotReady => Ok(Async::NotReady), } diff --git a/src/ws/client.rs b/src/ws/client.rs index 2023fdd21..369b56315 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -8,24 +8,28 @@ use std::cell::UnsafeCell; use base64; use rand; use cookie::Cookie; -use bytes::BytesMut; +use bytes::{Bytes, BytesMut}; use http::{HttpTryFrom, StatusCode, Error as HttpError}; use http::header::{self, HeaderName, HeaderValue}; use sha1::Sha1; use futures::{Async, Future, Poll, Stream}; use futures::future::{Either, err as FutErr}; +use futures::unsync::mpsc::{unbounded, UnboundedSender}; use tokio_core::net::TcpStream; +use byteorder::{ByteOrder, NetworkEndian}; use actix::prelude::*; -use body::Binary; -use error::UrlParseError; +use body::{Body, Binary}; +use error::{WsError, UrlParseError}; +use payload::PayloadHelper; use server::shared::SharedBytes; use server::{utils, IoStream}; -use client::{ClientRequest, ClientRequestBuilder, +use client::{ClientRequest, ClientRequestBuilder, ClientResponse, HttpResponseParser, HttpResponseParserError, HttpClientWriter}; -use client::{Connect, Connection, ClientConnector, ClientConnectorError}; +use client::{Connect, Connection, ClientConnector, ClientConnectorError, + SendRequest, SendRequestError}; use super::Message; use super::frame::Frame; @@ -52,7 +56,9 @@ pub enum WsClientError { #[fail(display="Response parsing error")] ResponseParseError(HttpResponseParserError), #[fail(display="{}", _0)] - Connector(ClientConnectorError), + SendRequest(SendRequestError), + #[fail(display="{}", _0)] + Protocol(#[cause] WsError), #[fail(display="{}", _0)] Io(io::Error), #[fail(display="Disconnected")] @@ -71,9 +77,15 @@ impl From for WsClientError { } } -impl From for WsClientError { - fn from(err: ClientConnectorError) -> WsClientError { - WsClientError::Connector(err) +impl From for WsClientError { + fn from(err: SendRequestError) -> WsClientError { + WsClientError::SendRequest(err) + } +} + +impl From for WsClientError { + fn from(err: WsError) -> WsClientError { + WsClientError::Protocol(err) } } @@ -206,21 +218,17 @@ impl WsClient { } struct WsInner { - conn: Connection, - writer: HttpClientWriter, - parser: HttpResponseParser, - parser_buf: BytesMut, + tx: UnboundedSender, + rx: PayloadHelper, closed: bool, - error_sent: bool, } pub struct WsHandshake { inner: Option, - request: Option, - sent: bool, + request: Option, + tx: Option>, key: String, error: Option, - stream: Option, Error=WsClientError>>>, } impl WsHandshake { @@ -235,31 +243,29 @@ impl WsHandshake { let key = base64::encode(&sec_key); if let Some(mut request) = request { - let stream = Box::new( - conn.send(Connect(request.uri().clone())) - .map(|res| res.map_err(|e| e.into())) - .map_err(|_| WsClientError::Disconnected)); - request.headers_mut().insert( HeaderName::try_from("SEC-WEBSOCKET-KEY").unwrap(), HeaderValue::try_from(key.as_str()).unwrap()); + let (tx, rx) = unbounded(); + request.set_body(Body::Streaming( + Box::new(rx.map_err(|_| io::Error::new( + io::ErrorKind::Other, "disconnected").into())))); + WsHandshake { key: key, inner: None, - request: Some(request), - sent: false, + request: Some(request.with_connector(conn.clone())), + tx: Some(tx), error: err, - stream: Some(stream), } } else { WsHandshake { key: key, inner: None, request: None, - sent: false, + tx: None, error: err, - stream: None, } } } @@ -274,94 +280,67 @@ impl Future for WsHandshake { return Err(err) } - if self.stream.is_some() { - match self.stream.as_mut().unwrap().poll()? { - Async::Ready(result) => match result { - Ok(conn) => { - let inner = WsInner { - conn: conn, - writer: HttpClientWriter::new(SharedBytes::default()), - parser: HttpResponseParser::default(), - parser_buf: BytesMut::new(), - closed: false, - error_sent: false, - }; - self.stream.take(); - self.inner = Some(inner); - } - Err(err) => return Err(err), - }, - Async::NotReady => return Ok(Async::NotReady) + let resp = match self.request.as_mut().unwrap().poll()? { + Async::Ready(response) => { + self.request.take(); + response + }, + Async::NotReady => return Ok(Async::NotReady) + }; + + // verify response + if resp.status() != StatusCode::SWITCHING_PROTOCOLS { + return Err(WsClientError::InvalidResponseStatus) + } + // Check for "UPGRADE" to websocket header + let has_hdr = if let Some(hdr) = resp.headers().get(header::UPGRADE) { + if let Ok(s) = hdr.to_str() { + s.to_lowercase().contains("websocket") + } else { + false } + } else { + false + }; + if !has_hdr { + return Err(WsClientError::InvalidUpgradeHeader) + } + // Check for "CONNECTION" header + let has_hdr = if let Some(conn) = resp.headers().get(header::CONNECTION) { + if let Ok(s) = conn.to_str() { + s.to_lowercase().contains("upgrade") + } else { false } + } else { false }; + if !has_hdr { + return Err(WsClientError::InvalidConnectionHeader) } - let mut inner = self.inner.take().unwrap(); - - if !self.sent { - self.sent = true; - inner.writer.start(self.request.as_mut().unwrap())?; - } - if let Err(err) = inner.writer.poll_completed(&mut inner.conn, false) { - return Err(err.into()) + let match_key = if let Some(key) = resp.headers().get( + HeaderName::try_from("SEC-WEBSOCKET-ACCEPT").unwrap()) + { + // field is constructed by concatenating /key/ + // with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455) + const WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + let mut sha1 = Sha1::new(); + sha1.update(self.key.as_ref()); + sha1.update(WS_GUID); + key.as_bytes() == base64::encode(&sha1.digest().bytes()).as_bytes() + } else { + false + }; + if !match_key { + return Err(WsClientError::InvalidChallengeResponse) } - match inner.parser.parse(&mut inner.conn, &mut inner.parser_buf) { - Ok(Async::Ready(resp)) => { - // verify response - if resp.status() != StatusCode::SWITCHING_PROTOCOLS { - return Err(WsClientError::InvalidResponseStatus) - } - // Check for "UPGRADE" to websocket header - let has_hdr = if let Some(hdr) = resp.headers().get(header::UPGRADE) { - if let Ok(s) = hdr.to_str() { - s.to_lowercase().contains("websocket") - } else { - false - } - } else { - false - }; - if !has_hdr { - return Err(WsClientError::InvalidUpgradeHeader) - } - // Check for "CONNECTION" header - let has_hdr = if let Some(conn) = resp.headers().get(header::CONNECTION) { - if let Ok(s) = conn.to_str() { - s.to_lowercase().contains("upgrade") - } else { false } - } else { false }; - if !has_hdr { - return Err(WsClientError::InvalidConnectionHeader) - } + let inner = WsInner { + tx: self.tx.take().unwrap(), + rx: PayloadHelper::new(resp), + closed: false, + }; - let match_key = if let Some(key) = resp.headers().get( - HeaderName::try_from("SEC-WEBSOCKET-ACCEPT").unwrap()) - { - // field is constructed by concatenating /key/ - // with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455) - const WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - let mut sha1 = Sha1::new(); - sha1.update(self.key.as_ref()); - sha1.update(WS_GUID); - key.as_bytes() == base64::encode(&sha1.digest().bytes()).as_bytes() - } else { - false - }; - if !match_key { - return Err(WsClientError::InvalidChallengeResponse) - } - - let inner = Rc::new(UnsafeCell::new(inner)); - Ok(Async::Ready( - (WsClientReader{inner: Rc::clone(&inner)}, - WsClientWriter{inner: inner}))) - }, - Ok(Async::NotReady) => { - self.inner = Some(inner); - Ok(Async::NotReady) - }, - Err(err) => Err(err.into()) - } + let inner = Rc::new(UnsafeCell::new(inner)); + Ok(Async::Ready( + (WsClientReader{inner: Rc::clone(&inner)}, WsClientWriter{inner: inner}))) } } @@ -389,24 +368,13 @@ impl Stream for WsClientReader { fn poll(&mut self) -> Poll, Self::Error> { let inner = self.as_mut(); - let mut done = false; - - match utils::read_from_io(&mut inner.conn, &mut inner.parser_buf) { - Ok(Async::Ready(0)) => { - done = true; - inner.closed = true; - }, - Ok(Async::Ready(_)) | Ok(Async::NotReady) => (), - Err(err) => - return Err(err.into()) + if inner.closed { + return Ok(Async::Ready(None)) } - // write - let _ = inner.writer.poll_completed(&mut inner.conn, false); - // read - match Frame::parse(&mut inner.parser_buf, false) { - Ok(Some(frame)) => { + match Frame::parse(&mut inner.rx, false) { + Ok(Async::Ready(Some(frame))) => { // trace!("WsFrame {}", frame); let (_finished, opcode, payload) = frame.unpack(); @@ -416,8 +384,9 @@ impl Stream for WsClientReader { Ok(Async::Ready(Some(Message::Error))), OpCode::Close => { inner.closed = true; - inner.error_sent = true; - Ok(Async::Ready(Some(Message::Closed))) + let code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16; + Ok(Async::Ready( + Some(Message::Close(CloseCode::from(code))))) }, OpCode::Ping => Ok(Async::Ready(Some( @@ -440,23 +409,10 @@ impl Stream for WsClientReader { } } } - Ok(None) => { - if done { - Ok(Async::Ready(None)) - } else if inner.closed { - if !inner.error_sent { - inner.error_sent = true; - Ok(Async::Ready(Some(Message::Closed))) - } else { - Ok(Async::Ready(None)) - } - } else { - Ok(Async::NotReady) - } - }, + Ok(Async::Ready(None)) => Ok(Async::Ready(None)), + Ok(Async::NotReady) => Ok(Async::NotReady), Err(err) => { inner.closed = true; - inner.error_sent = true; Err(err.into()) } } @@ -478,9 +434,9 @@ impl WsClientWriter { /// Write payload #[inline] - fn write(&mut self, data: Binary) { + fn write(&mut self, mut data: Binary) { if !self.as_mut().closed { - let _ = self.as_mut().writer.write(data); + let _ = self.as_mut().tx.unbounded_send(data.take()); } else { warn!("Trying to write to disconnected response"); } diff --git a/src/ws/frame.rs b/src/ws/frame.rs index 8771435fa..22067cb60 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -1,11 +1,13 @@ use std::{fmt, mem}; -use std::io::{Error, ErrorKind}; use std::iter::FromIterator; -use bytes::{BytesMut, BufMut}; +use bytes::{Bytes, BytesMut, BufMut}; use byteorder::{ByteOrder, BigEndian, NetworkEndian}; +use futures::{Async, Poll, Stream}; use rand; use body::Binary; +use error::{WsError, PayloadError}; +use payload::PayloadHelper; use ws::proto::{OpCode, CloseCode}; use ws::mask::apply_mask; @@ -48,14 +50,15 @@ impl Frame { } /// Parse the input stream into a frame. - pub fn parse(buf: &mut BytesMut, server: bool) -> Result, Error> { + pub fn parse(pl: &mut PayloadHelper, server: bool) -> Poll, WsError> + where S: Stream + { let mut idx = 2; - let mut size = buf.len(); - - if size < 2 { - return Ok(None) - } - size -= 2; + let buf = match pl.copy(2)? { + Async::Ready(Some(buf)) => buf, + Async::Ready(None) => return Ok(Async::Ready(None)), + Async::NotReady => return Ok(Async::NotReady), + }; let first = buf[0]; let second = buf[1]; let finished = first & 0x80 != 0; @@ -63,11 +66,9 @@ impl Frame { // check masking let masked = second & 0x80 != 0; if !masked && server { - return Err(Error::new( - ErrorKind::Other, "Received an unmasked frame from client")) + return Err(WsError::UnmaskedFrame) } else if masked && !server { - return Err(Error::new( - ErrorKind::Other, "Received a masked frame from server")) + return Err(WsError::MaskedFrame) } let rsv1 = first & 0x40 != 0; @@ -77,19 +78,21 @@ impl Frame { let len = second & 0x7F; let length = if len == 126 { - if size < 2 { - return Ok(None) - } + let buf = match pl.copy(4)? { + Async::Ready(Some(buf)) => buf, + Async::Ready(None) => return Ok(Async::Ready(None)), + Async::NotReady => return Ok(Async::NotReady), + }; let len = NetworkEndian::read_uint(&buf[idx..], 2) as usize; - size -= 2; idx += 2; len } else if len == 127 { - if size < 8 { - return Ok(None) - } + let buf = match pl.copy(10)? { + Async::Ready(Some(buf)) => buf, + Async::Ready(None) => return Ok(Async::Ready(None)), + Async::NotReady => return Ok(Async::NotReady), + }; let len = NetworkEndian::read_uint(&buf[idx..], 8) as usize; - size -= 8; idx += 8; len } else { @@ -97,50 +100,42 @@ impl Frame { }; let mask = if server { - if size < 4 { - return Ok(None) - } else { - let mut mask_bytes = [0u8; 4]; - size -= 4; - mask_bytes.copy_from_slice(&buf[idx..idx+4]); - idx += 4; - Some(mask_bytes) - } + let buf = match pl.copy(idx + 4)? { + Async::Ready(Some(buf)) => buf, + Async::Ready(None) => return Ok(Async::Ready(None)), + Async::NotReady => return Ok(Async::NotReady), + }; + + let mut mask_bytes = [0u8; 4]; + mask_bytes.copy_from_slice(&buf[idx..idx+4]); + idx += 4; + Some(mask_bytes) } else { None }; - if size < length { - return Ok(None) - } + let mut data = match pl.readexactly(idx + length)? { + Async::Ready(Some(buf)) => buf, + Async::Ready(None) => return Ok(Async::Ready(None)), + Async::NotReady => return Ok(Async::NotReady), + }; // get body - buf.split_to(idx); - let mut data = if length > 0 { - buf.split_to(length) - } else { - BytesMut::new() - }; + data.split_to(idx); // Disallow bad opcode if let OpCode::Bad = opcode { - return Err( - Error::new( - ErrorKind::Other, - format!("Encountered invalid opcode: {}", first & 0x0F))) + return Err(WsError::InvalidOpcode(first & 0x0F)) } // control frames must have length <= 125 match opcode { OpCode::Ping | OpCode::Pong if length > 125 => { - return Err( - Error::new( - ErrorKind::Other, - format!("Rejected WebSocket handshake.Received control frame with length: {}.", length))) + return Err(WsError::InvalidLength(length)) } OpCode::Close if length > 125 => { debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame."); - return Ok(Some(Frame::default())) + return Ok(Async::Ready(Some(Frame::default()))) } _ => () } @@ -150,14 +145,14 @@ impl Frame { apply_mask(&mut data, mask); } - Ok(Some(Frame { + Ok(Async::Ready(Some(Frame { finished: finished, rsv1: rsv1, rsv2: rsv2, rsv3: rsv3, opcode: opcode, payload: data.into(), - })) + }))) } /// Generate binary representation @@ -258,13 +253,33 @@ impl fmt::Display for Frame { #[cfg(test)] mod tests { use super::*; + use futures::stream::once; + + fn is_none(frm: Poll, WsError>) -> bool { + match frm { + Ok(Async::Ready(None)) => true, + _ => false, + } + } + + fn extract(frm: Poll, WsError>) -> Frame { + match frm { + Ok(Async::Ready(Some(frame))) => frame, + _ => panic!("error"), + } + } #[test] fn test_parse() { + let mut buf = PayloadHelper::new( + once(Ok(BytesMut::from(&[0b00000001u8, 0b00000001u8][..]).freeze()))); + assert!(is_none(Frame::parse(&mut buf, false))); + let mut buf = BytesMut::from(&[0b00000001u8, 0b00000001u8][..]); - assert!(Frame::parse(&mut buf, false).unwrap().is_none()); buf.extend(b"1"); - let frame = Frame::parse(&mut buf, false).unwrap().unwrap(); + let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); + + let frame = extract(Frame::parse(&mut buf, false)); println!("FRAME: {}", frame); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); @@ -273,8 +288,10 @@ mod tests { #[test] fn test_parse_length0() { - let mut buf = BytesMut::from(&[0b00000001u8, 0b00000000u8][..]); - let frame = Frame::parse(&mut buf, false).unwrap().unwrap(); + let buf = BytesMut::from(&[0b00000001u8, 0b00000000u8][..]); + let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); + + let frame = extract(Frame::parse(&mut buf, false)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); assert!(frame.payload.is_empty()); @@ -282,12 +299,16 @@ mod tests { #[test] fn test_parse_length2() { + let buf = BytesMut::from(&[0b00000001u8, 126u8][..]); + let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); + assert!(is_none(Frame::parse(&mut buf, false))); + let mut buf = BytesMut::from(&[0b00000001u8, 126u8][..]); - assert!(Frame::parse(&mut buf, false).unwrap().is_none()); buf.extend(&[0u8, 4u8][..]); buf.extend(b"1234"); + let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); - let frame = Frame::parse(&mut buf, false).unwrap().unwrap(); + let frame = extract(Frame::parse(&mut buf, false)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); assert_eq!(frame.payload.as_ref(), &b"1234"[..]); @@ -295,12 +316,16 @@ mod tests { #[test] fn test_parse_length4() { + let buf = BytesMut::from(&[0b00000001u8, 127u8][..]); + let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); + assert!(is_none(Frame::parse(&mut buf, false))); + let mut buf = BytesMut::from(&[0b00000001u8, 127u8][..]); - assert!(Frame::parse(&mut buf, false).unwrap().is_none()); buf.extend(&[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8][..]); buf.extend(b"1234"); + let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); - let frame = Frame::parse(&mut buf, false).unwrap().unwrap(); + let frame = extract(Frame::parse(&mut buf, false)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); assert_eq!(frame.payload.as_ref(), &b"1234"[..]); @@ -311,10 +336,11 @@ mod tests { let mut buf = BytesMut::from(&[0b00000001u8, 0b10000001u8][..]); buf.extend(b"0001"); buf.extend(b"1"); + let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); assert!(Frame::parse(&mut buf, false).is_err()); - let frame = Frame::parse(&mut buf, true).unwrap().unwrap(); + let frame = extract(Frame::parse(&mut buf, true)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); assert_eq!(frame.payload, vec![1u8].into()); @@ -324,10 +350,11 @@ mod tests { fn test_parse_frame_no_mask() { let mut buf = BytesMut::from(&[0b00000001u8, 0b00000001u8][..]); buf.extend(&[1u8]); + let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); assert!(Frame::parse(&mut buf, true).is_err()); - let frame = Frame::parse(&mut buf, false).unwrap().unwrap(); + let frame = extract(Frame::parse(&mut buf, false)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); assert_eq!(frame.payload, vec![1u8].into()); diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 93d0b61aa..465400ac9 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -43,15 +43,16 @@ //! # .finish(); //! # } //! ``` -use bytes::BytesMut; +use bytes::Bytes; use http::{Method, StatusCode, header}; use futures::{Async, Poll, Stream}; +use byteorder::{ByteOrder, NetworkEndian}; use actix::{Actor, AsyncContext, Handler}; use body::Binary; -use payload::Payload; -use error::{Error, WsHandshakeError}; +use payload::PayloadHelper; +use error::{Error, WsHandshakeError, PayloadError}; use httprequest::HttpRequest; use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder}; @@ -80,8 +81,7 @@ pub enum Message { Binary(Binary), Ping(String), Pong(String), - Close, - Closed, + Close(CloseCode), Error } @@ -165,104 +165,67 @@ pub fn handshake(req: &HttpRequest) -> Result { + rx: PayloadHelper, closed: bool, - error_sent: bool, } -impl WsStream { - pub fn new(payload: Payload) -> WsStream { - WsStream { rx: payload, - buf: BytesMut::new(), - closed: false, - error_sent: false } +impl WsStream where S: Stream { + pub fn new(stream: S) -> WsStream { + WsStream { rx: PayloadHelper::new(stream), + closed: false } } } -impl Stream for WsStream { +impl Stream for WsStream where S: Stream { type Item = Message; type Error = (); fn poll(&mut self) -> Poll, Self::Error> { - let mut done = false; + if self.closed { + return Ok(Async::Ready(None)) + } - if !self.closed { - loop { - match self.rx.poll() { - Ok(Async::Ready(Some(chunk))) => { - self.buf.extend_from_slice(&chunk) - } - Ok(Async::Ready(None)) => { - done = true; + match Frame::parse(&mut self.rx, true) { + Ok(Async::Ready(Some(frame))) => { + // trace!("WsFrame {}", frame); + let (_finished, opcode, payload) = frame.unpack(); + + match opcode { + OpCode::Continue => unimplemented!(), + OpCode::Bad => + Ok(Async::Ready(Some(Message::Error))), + OpCode::Close => { self.closed = true; - break; - } - Ok(Async::NotReady) => break, - Err(_) => { - self.closed = true; - break; + let code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16; + Ok(Async::Ready( + Some(Message::Close(CloseCode::from(code))))) + }, + OpCode::Ping => + Ok(Async::Ready(Some( + Message::Ping( + String::from_utf8_lossy(payload.as_ref()).into())))), + OpCode::Pong => + Ok(Async::Ready(Some( + Message::Pong(String::from_utf8_lossy(payload.as_ref()).into())))), + OpCode::Binary => + Ok(Async::Ready(Some(Message::Binary(payload)))), + OpCode::Text => { + let tmp = Vec::from(payload.as_ref()); + match String::from_utf8(tmp) { + Ok(s) => + Ok(Async::Ready(Some(Message::Text(s)))), + Err(_) => + Ok(Async::Ready(Some(Message::Error))), + } } } } - } - - loop { - match Frame::parse(&mut self.buf, true) { - Ok(Some(frame)) => { - // trace!("WsFrame {}", frame); - let (_finished, opcode, payload) = frame.unpack(); - - match opcode { - OpCode::Continue => continue, - OpCode::Bad => - return Ok(Async::Ready(Some(Message::Error))), - OpCode::Close => { - self.closed = true; - self.error_sent = true; - return Ok(Async::Ready(Some(Message::Closed))) - }, - OpCode::Ping => - return Ok(Async::Ready(Some( - Message::Ping( - String::from_utf8_lossy(payload.as_ref()).into())))), - OpCode::Pong => - return Ok(Async::Ready(Some( - Message::Pong( - String::from_utf8_lossy(payload.as_ref()).into())))), - OpCode::Binary => - return Ok(Async::Ready(Some(Message::Binary(payload)))), - OpCode::Text => { - let tmp = Vec::from(payload.as_ref()); - match String::from_utf8(tmp) { - Ok(s) => - return Ok(Async::Ready(Some(Message::Text(s)))), - Err(_) => - return Ok(Async::Ready(Some(Message::Error))), - } - } - } - } - Ok(None) => { - if done { - return Ok(Async::Ready(None)) - } else if self.closed { - if !self.error_sent { - self.error_sent = true; - return Ok(Async::Ready(Some(Message::Closed))) - } else { - return Ok(Async::Ready(None)) - } - } else { - return Ok(Async::NotReady) - } - }, - Err(_) => { - self.closed = true; - self.error_sent = true; - return Ok(Async::Ready(Some(Message::Error))); - } + Ok(Async::Ready(None)) => Ok(Async::Ready(None)), + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(_) => { + self.closed = true; + Ok(Async::Ready(Some(Message::Error))) } } } diff --git a/tests/test_ws.rs b/tests/test_ws.rs index ac7119914..8b7d0111f 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -24,6 +24,7 @@ impl Handler for Ws { ws::Message::Ping(msg) => ctx.pong(&msg), ws::Message::Text(text) => ctx.text(text), ws::Message::Binary(bin) => ctx.binary(bin), + ws::Message::Close(reason) => ctx.close(reason, ""), _ => (), } } @@ -49,5 +50,5 @@ fn test_simple() { writer.close(ws::CloseCode::Normal, ""); let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert!(item.is_none()); + assert_eq!(item, Some(ws::Message::Close(ws::CloseCode::Normal))); } From 72aa2d9eaece38efb91efcb4aa50ce7ac9748565 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 26 Feb 2018 14:33:56 -0800 Subject: [PATCH 14/84] clippy warnings --- examples/state/src/main.rs | 2 +- examples/websocket-chat/src/main.rs | 3 +-- examples/websocket/src/main.rs | 2 +- src/application.rs | 6 +++--- src/client/parser.rs | 4 ++-- src/client/pipeline.rs | 18 ++++++------------ src/client/writer.rs | 10 ++++++---- src/context.rs | 5 +---- src/error.rs | 8 ++++---- src/fs.rs | 5 +---- src/handler.rs | 12 ++++++------ src/httprequest.rs | 24 ++++++++++++------------ src/httpresponse.rs | 4 ++-- src/info.rs | 4 ++-- src/lib.rs | 2 +- src/middleware/defaultheaders.rs | 2 +- src/middleware/session.rs | 2 +- src/multipart.rs | 15 +++++---------- src/payload.rs | 2 +- src/pipeline.rs | 13 ++++++------- src/route.rs | 12 ++++-------- src/router.rs | 14 +++++--------- src/server/channel.rs | 2 +- src/server/encoding.rs | 7 ++----- src/server/h1.rs | 20 ++++++++++++-------- src/server/h1writer.rs | 4 +++- src/server/h2.rs | 12 ++++++++---- src/server/h2writer.rs | 4 +++- src/server/settings.rs | 6 +----- src/server/worker.rs | 2 +- src/test.rs | 18 +++++++++--------- src/ws/client.rs | 8 ++++---- src/ws/frame.rs | 8 +------- 33 files changed, 117 insertions(+), 143 deletions(-) diff --git a/examples/state/src/main.rs b/examples/state/src/main.rs index 21eb50483..a981c7fbb 100644 --- a/examples/state/src/main.rs +++ b/examples/state/src/main.rs @@ -46,7 +46,7 @@ impl Handler for MyWebSocket { ws::Message::Ping(msg) => ctx.pong(&msg), ws::Message::Text(text) => ctx.text(text), ws::Message::Binary(bin) => ctx.binary(bin), - ws::Message::Closed | ws::Message::Error => { + ws::Message::Close(_) | ws::Message::Error => { ctx.stop(); } _ => (), diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs index 821fcfa57..50e6bff5e 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -161,10 +161,9 @@ impl Handler for WsChatSession { }, ws::Message::Binary(bin) => println!("Unexpected binary"), - ws::Message::Closed | ws::Message::Error => { + ws::Message::Close(_) | ws::Message::Error => { ctx.stop(); } - _ => (), } } } diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index 9149ead71..620e56d40 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -35,7 +35,7 @@ impl Handler for MyWebSocket { ws::Message::Ping(msg) => ctx.pong(&msg), ws::Message::Text(text) => ctx.text(text), ws::Message::Binary(bin) => ctx.binary(bin), - ws::Message::Closed | ws::Message::Error => { + ws::Message::Close(_) | ws::Message::Error => { ctx.stop(); } _ => (), diff --git a/src/application.rs b/src/application.rs index c7c1bcacb..f8ac85c9f 100644 --- a/src/application.rs +++ b/src/application.rs @@ -149,7 +149,7 @@ impl Application where S: 'static { pub fn with_state(state: S) -> Application { Application { parts: Some(ApplicationParts { - state: state, + state, prefix: "/".to_owned(), settings: ServerSettings::default(), default: Resource::default_not_found(), @@ -361,17 +361,17 @@ impl Application where S: 'static { default: parts.default, encoding: parts.encoding, router: router.clone(), - resources: resources, handlers: parts.handlers, + resources, } )); HttpApplication { state: Rc::new(parts.state), prefix: prefix.to_owned(), - inner: inner, router: router.clone(), middlewares: Rc::new(parts.middlewares), + inner, } } diff --git a/src/client/parser.rs b/src/client/parser.rs index 6a0ee1080..31c6601aa 100644 --- a/src/client/parser.rs +++ b/src/client/parser.rs @@ -176,12 +176,12 @@ impl HttpResponseParser { if let Some(decoder) = decoder { Ok(Async::Ready( (ClientResponse::new( - ClientMessage{status: status, version: version, + ClientMessage{status, version, headers: hdrs, cookies: None}), Some(decoder)))) } else { Ok(Async::Ready( (ClientResponse::new( - ClientMessage{status: status, version: version, + ClientMessage{status, version, headers: hdrs, cookies: None}), None))) } } diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 46705134d..dfb01bd63 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -63,16 +63,13 @@ impl SendRequest { pub(crate) fn with_connector(req: ClientRequest, conn: Addr) -> SendRequest { - SendRequest{ - req: req, - state: State::New, - conn: conn} + SendRequest{req, conn, state: State::New} } pub(crate) fn with_connection(req: ClientRequest, conn: Connection) -> SendRequest { SendRequest{ - req: req, + req, state: State::Connection(conn), conn: ClientConnector::from_registry()} } @@ -103,7 +100,7 @@ impl Future for SendRequest { Err(_) => return Err(SendRequestError::Connector( ClientConnectorError::Disconnected)) }, - State::Connection(stream) => { + State::Connection(conn) => { let mut writer = HttpClientWriter::new(SharedBytes::default()); writer.start(&mut self.req)?; @@ -114,9 +111,7 @@ impl Future for SendRequest { }; let mut pl = Box::new(Pipeline { - body: body, - conn: stream, - writer: writer, + body, conn, writer, parser: Some(HttpResponseParser::default()), parser_buf: BytesMut::new(), disconnected: false, @@ -221,11 +216,10 @@ impl Pipeline { let mut need_run = false; // need write? - match self.poll_write() + if let Async::NotReady = self.poll_write() .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))? { - Async::NotReady => need_run = true, - _ => (), + need_run = true; } // need read? diff --git a/src/client/writer.rs b/src/client/writer.rs index ad1bb6a13..f072ea7f4 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -1,4 +1,6 @@ +#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] #![allow(dead_code)] + use std::io::{self, Write}; use std::cell::RefCell; use std::fmt::Write as FmtWrite; @@ -48,14 +50,14 @@ pub(crate) struct HttpClientWriter { impl HttpClientWriter { - pub fn new(buf: SharedBytes) -> HttpClientWriter { - let encoder = ContentEncoder::Identity(TransferEncoding::eof(buf.clone())); + pub fn new(buffer: SharedBytes) -> HttpClientWriter { + let encoder = ContentEncoder::Identity(TransferEncoding::eof(buffer.clone())); HttpClientWriter { flags: Flags::empty(), written: 0, headers_size: 0, - buffer: buf, - encoder: encoder, + buffer, + encoder, low: LOW_WATERMARK, high: HIGH_WATERMARK, } diff --git a/src/context.rs b/src/context.rs index a3e168f6d..02eda8d5c 100644 --- a/src/context.rs +++ b/src/context.rs @@ -230,10 +230,7 @@ pub struct Drain { impl Drain { pub fn new(fut: oneshot::Receiver<()>) -> Self { - Drain { - fut: fut, - _a: PhantomData - } + Drain { fut, _a: PhantomData } } } diff --git a/src/error.rs b/src/error.rs index f94362031..4cc674860 100644 --- a/src/error.rs +++ b/src/error.rs @@ -90,7 +90,7 @@ impl From for Error { } else { None }; - Error { cause: Box::new(err), backtrace: backtrace } + Error { cause: Box::new(err), backtrace } } } @@ -566,10 +566,10 @@ unsafe impl Sync for InternalError {} unsafe impl Send for InternalError {} impl InternalError { - pub fn new(err: T, status: StatusCode) -> Self { + pub fn new(cause: T, status: StatusCode) -> Self { InternalError { - cause: err, - status: status, + cause, + status, backtrace: Backtrace::new(), } } diff --git a/src/fs.rs b/src/fs.rs index 28260e36a..b7588dc51 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -105,10 +105,7 @@ pub struct Directory{ impl Directory { pub fn new(base: PathBuf, path: PathBuf) -> Directory { - Directory { - base: base, - path: path - } + Directory { base, path } } fn can_list(&self, entry: &io::Result) -> bool { diff --git a/src/handler.rs b/src/handler.rs index 253024e61..b8e074725 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -215,7 +215,7 @@ impl WrapHandler S: 'static, { pub fn new(h: H) -> Self { - WrapHandler{h: h, s: PhantomData} + WrapHandler{h, s: PhantomData} } } @@ -225,7 +225,7 @@ impl RouteHandler for WrapHandler S: 'static, { fn handle(&mut self, req: HttpRequest) -> Reply { - let req2 = req.clone_without_state(); + let req2 = req.without_state(); match self.h.handle(req).respond_to(req2) { Ok(reply) => reply.into(), Err(err) => Reply::response(err.into()), @@ -266,7 +266,7 @@ impl RouteHandler for AsyncHandler S: 'static, { fn handle(&mut self, req: HttpRequest) -> Reply { - let req2 = req.clone_without_state(); + let req2 = req.without_state(); let fut = (self.h)(req) .map_err(|e| e.into()) .then(move |r| { @@ -345,10 +345,10 @@ impl NormalizePath { /// Create new `NormalizePath` instance pub fn new(append: bool, merge: bool, redirect: StatusCode) -> NormalizePath { NormalizePath { - append: append, - merge: merge, + append, + merge, + redirect, re_merge: Regex::new("//+").unwrap(), - redirect: redirect, not_found: StatusCode::NOT_FOUND, } } diff --git a/src/httprequest.rs b/src/httprequest.rs index 3ca4fdf96..3e6000b54 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -106,16 +106,16 @@ impl HttpRequest<()> { { HttpRequest( SharedHttpMessage::from_message(HttpMessage { - method: method, - uri: uri, - version: version, - headers: headers, + method, + uri, + version, + headers, + payload, params: Params::new(), query: Params::new(), query_loaded: false, cookies: None, addr: None, - payload: payload, extensions: Extensions::new(), info: None, }), @@ -153,7 +153,7 @@ impl HttpRequest { #[inline] /// Construct new http request without state. - pub(crate) fn clone_without_state(&self) -> HttpRequest { + pub(crate) fn without_state(&self) -> HttpRequest { HttpRequest(self.0.clone(), None, None) } @@ -483,7 +483,7 @@ impl HttpRequest { /// # fn main() {} /// ``` pub fn body(self) -> RequestBody { - RequestBody::from(self) + RequestBody::new(self.without_state()) } /// Return stream to http payload processes as multipart. @@ -553,7 +553,7 @@ impl HttpRequest { /// # fn main() {} /// ``` pub fn urlencoded(self) -> UrlEncoded { - UrlEncoded::from(self) + UrlEncoded::new(self.without_state()) } /// Parse `application/json` encoded body. @@ -677,9 +677,9 @@ pub struct UrlEncoded { } impl UrlEncoded { - pub fn from(req: HttpRequest) -> UrlEncoded { + pub fn new(req: HttpRequest) -> UrlEncoded { UrlEncoded { - req: Some(req.clone_without_state()), + req: Some(req), limit: 262_144, fut: None, } @@ -762,10 +762,10 @@ pub struct RequestBody { impl RequestBody { /// Create `RequestBody` for request. - pub fn from(req: HttpRequest) -> RequestBody { + pub fn new(req: HttpRequest) -> RequestBody { RequestBody { limit: 262_144, - req: Some(req.clone_without_state()), + req: Some(req), fut: None, } } diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 77b63f125..cf702c67d 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -659,11 +659,11 @@ impl InnerHttpResponse { #[inline] fn new(status: StatusCode, body: Body) -> InnerHttpResponse { InnerHttpResponse { + status, + body, version: None, headers: HeaderMap::with_capacity(16), - status: status, reason: None, - body: body, chunked: None, encoding: None, connection_type: None, diff --git a/src/info.rs b/src/info.rs index 92ffa4d6c..45bd4fe6a 100644 --- a/src/info.rs +++ b/src/info.rs @@ -110,8 +110,8 @@ impl<'a> ConnectionInfo<'a> { ConnectionInfo { scheme: scheme.unwrap_or("http"), host: host.unwrap_or("localhost"), - remote: remote, - peer: peer, + remote, + peer, } } diff --git a/src/lib.rs b/src/lib.rs index 7ec675657..eb0da3c3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,7 @@ //! * Supported *HTTP/1.x* and *HTTP/2.0* protocols //! * Streaming and pipelining //! * Keep-alive and slow requests handling -//! * *WebSockets* server/client +//! * `WebSockets` server/client //! * Transparent content compression/decompression (br, gzip, deflate) //! * Configurable request routing //! * Multipart streams diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index 4e4686ca8..344c69a6a 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -95,7 +95,7 @@ impl DefaultHeadersBuilder { /// Finishes building and returns the built `DefaultHeaders` middleware. pub fn finish(&mut self) -> DefaultHeaders { let headers = self.headers.take().expect("cannot reuse middleware builder"); - DefaultHeaders{ ct: self.ct, headers: headers } + DefaultHeaders{ ct: self.ct, headers } } } diff --git a/src/middleware/session.rs b/src/middleware/session.rs index ad865669f..b46cd49ec 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -377,8 +377,8 @@ impl SessionBackend for CookieSessionBackend { FutOk( CookieSession { changed: false, - state: state, inner: Rc::clone(&self.0), + state, }) } } diff --git a/src/multipart.rs b/src/multipart.rs index 457fe4beb..6211f6116 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -95,8 +95,8 @@ impl Multipart where S: Stream { safety: Safety::new(), inner: Some(Rc::new(RefCell::new( InnerMultipart { + boundary, payload: PayloadRef::new(PayloadHelper::new(stream)), - boundary: boundary, state: InnerState::FirstBoundary, item: InnerMultipartItem::None, }))) @@ -369,12 +369,7 @@ impl Field where S: Stream { fn new(safety: Safety, headers: HeaderMap, ct: mime::Mime, inner: Rc>>) -> Self { - Field { - ct: ct, - headers: headers, - inner: inner, - safety: safety, - } + Field {ct, headers, inner, safety} } pub fn headers(&self) -> &HeaderMap { @@ -443,8 +438,8 @@ impl InnerField where S: Stream { }; Ok(InnerField { + boundary, payload: Some(payload), - boundary: boundary, eof: false, length: len }) } @@ -596,7 +591,7 @@ impl Safety { Safety { task: None, level: Rc::strong_count(&payload), - payload: payload, + payload, } } @@ -612,7 +607,7 @@ impl Clone for Safety { Safety { task: Some(current_task()), level: Rc::strong_count(&payload), - payload: payload, + payload, } } } diff --git a/src/payload.rs b/src/payload.rs index e4ba819d1..664004d7b 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -232,8 +232,8 @@ impl Inner { fn new(eof: bool) -> Self { Inner { + eof, len: 0, - eof: eof, err: None, task: None, items: VecDeque::new(), diff --git a/src/pipeline.rs b/src/pipeline.rs index 2408bf93b..2fd21ec20 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -72,7 +72,7 @@ struct PipelineInfo { impl PipelineInfo { fn new(req: HttpRequest) -> PipelineInfo { PipelineInfo { - req: req, + req, count: 0, mws: Rc::new(Vec::new()), error: None, @@ -108,9 +108,8 @@ impl> Pipeline { handler: Rc>) -> Pipeline { let mut info = PipelineInfo { - req: req, + req, mws, count: 0, - mws: mws, error: None, context: None, disconnected: None, @@ -307,7 +306,7 @@ impl WaitingResponse { RunMiddlewares::init(info, resp), ReplyItem::Future(fut) => PipelineState::Handler( - WaitingResponse { fut: fut, _s: PhantomData, _h: PhantomData }), + WaitingResponse { fut, _s: PhantomData, _h: PhantomData }), } } @@ -355,7 +354,7 @@ impl RunMiddlewares { }, Ok(Response::Future(fut)) => { return PipelineState::RunMiddlewares( - RunMiddlewares { curr: curr, fut: Some(fut), + RunMiddlewares { curr, fut: Some(fut), _s: PhantomData, _h: PhantomData }) }, }; @@ -444,7 +443,7 @@ impl ProcessResponse { #[inline] fn init(resp: HttpResponse) -> PipelineState { PipelineState::Response( - ProcessResponse{ resp: resp, + ProcessResponse{ resp, iostate: IOState::Response, running: RunningState::Running, drain: None, _s: PhantomData, _h: PhantomData}) @@ -644,7 +643,7 @@ impl FinishingMiddlewares { if info.count == 0 { Completed::init(info) } else { - let mut state = FinishingMiddlewares{resp: resp, fut: None, + let mut state = FinishingMiddlewares{resp, fut: None, _s: PhantomData, _h: PhantomData}; if let Some(st) = state.poll(info) { st diff --git a/src/route.rs b/src/route.rs index bd721b1c6..b4d1d2680 100644 --- a/src/route.rs +++ b/src/route.rs @@ -179,14 +179,10 @@ impl Compose { mws: Rc>>>, handler: InnerHandler) -> Self { - let mut info = ComposeInfo { - count: 0, - req: req, - mws: mws, - handler: handler }; + let mut info = ComposeInfo { count: 0, req, mws, handler }; let state = StartMiddlewares::init(&mut info); - Compose {state: state, info: info} + Compose {state, info} } } @@ -308,7 +304,7 @@ impl WaitingResponse { RunMiddlewares::init(info, resp), ReplyItem::Future(fut) => ComposeState::Handler( - WaitingResponse { fut: fut, _s: PhantomData }), + WaitingResponse { fut, _s: PhantomData }), } } @@ -353,7 +349,7 @@ impl RunMiddlewares { }, Ok(MiddlewareResponse::Future(fut)) => { return ComposeState::RunMiddlewares( - RunMiddlewares { curr: curr, fut: Some(fut), _s: PhantomData }) + RunMiddlewares { curr, fut: Some(fut), _s: PhantomData }) }, }; } diff --git a/src/router.rs b/src/router.rs index 9f1d93b05..63cc9045c 100644 --- a/src/router.rs +++ b/src/router.rs @@ -46,13 +46,9 @@ impl Router { } } - let len = prefix.len(); + let prefix_len = prefix.len(); (Router(Rc::new( - Inner{ prefix: prefix, - prefix_len: len, - named: named, - patterns: patterns, - srv: settings })), resources) + Inner{ prefix, prefix_len, named, patterns, srv: settings })), resources) } /// Router prefix @@ -168,10 +164,10 @@ impl Pattern { }; Pattern { - tp: tp, + tp, + pattern, + elements, name: name.into(), - pattern: pattern, - elements: elements, } } diff --git a/src/server/channel.rs b/src/server/channel.rs index 85c3ac4ef..8cf23ed30 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -237,7 +237,7 @@ pub(crate) struct WrapperStream where T: AsyncRead + AsyncWrite + 'static { impl WrapperStream where T: AsyncRead + AsyncWrite + 'static { pub fn new(io: T) -> Self { - WrapperStream{io: io} + WrapperStream{ io } } } diff --git a/src/server/encoding.rs b/src/server/encoding.rs index 9137bb420..d2b2db939 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -309,7 +309,7 @@ pub(crate) struct EncodedPayload { impl EncodedPayload { pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload { - EncodedPayload{ inner: inner, error: false, payload: PayloadStream::new(enc) } + EncodedPayload{ inner, error: false, payload: PayloadStream::new(enc) } } } @@ -821,10 +821,7 @@ impl AcceptEncoding { Err(_) => 0.0, } }; - Some(AcceptEncoding { - encoding: encoding, - quality: quality, - }) + Some(AcceptEncoding{ encoding, quality }) } /// Parse a raw Accept-Encoding header value into an ordered list. diff --git a/src/server/h1.rs b/src/server/h1.rs index 4ce403cb5..527eec671 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -1,3 +1,5 @@ +#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] + use std::{self, io}; use std::rc::Rc; use std::net::SocketAddr; @@ -62,18 +64,20 @@ struct Entry { impl Http1 where T: IoStream, H: HttpHandler + 'static { - pub fn new(h: Rc>, stream: T, addr: Option, buf: BytesMut) - -> Self + pub fn new(settings: Rc>, + stream: T, + addr: Option, read_buf: BytesMut) -> Self { - let bytes = h.get_shared_bytes(); + let bytes = settings.get_shared_bytes(); Http1{ flags: Flags::KEEPALIVE, - settings: h, - addr: addr, stream: H1Writer::new(stream, bytes), reader: Reader::new(), - read_buf: buf, tasks: VecDeque::new(), - keepalive_timer: None } + keepalive_timer: None, + addr, + read_buf, + settings, + } } pub fn settings(&self) -> &WorkerSettings { @@ -540,7 +544,7 @@ impl Reader { let (psender, payload) = Payload::new(false); let info = PayloadInfo { tx: PayloadType::new(&msg.get_mut().headers, psender), - decoder: decoder, + decoder, }; msg.get_mut().payload = Some(payload); Ok(Async::Ready((HttpRequest::from_message(msg), Some(info)))) diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index b2b79c5f9..da60e220c 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -1,3 +1,5 @@ +#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] + use std::{io, mem}; use bytes::BufMut; use futures::{Async, Poll}; @@ -39,11 +41,11 @@ impl H1Writer { pub fn new(stream: T, buf: SharedBytes) -> H1Writer { H1Writer { flags: Flags::empty(), - stream: stream, encoder: ContentEncoder::empty(buf.clone()), written: 0, headers_size: 0, buffer: buf, + stream, } } diff --git a/src/server/h2.rs b/src/server/h2.rs index c843fee89..0a3875250 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -1,3 +1,5 @@ +#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] + use std::{io, cmp, mem}; use std::rc::Rc; use std::io::{Read, Write}; @@ -53,15 +55,17 @@ impl Http2 where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static { - pub fn new(h: Rc>, io: T, addr: Option, buf: Bytes) -> Self + pub fn new(settings: Rc>, + io: T, + addr: Option, buf: Bytes) -> Self { Http2{ flags: Flags::empty(), - settings: h, - addr: addr, tasks: VecDeque::new(), state: State::Handshake( server::handshake(IoWrapper{unread: Some(buf), inner: io})), keepalive_timer: None, + addr, + settings, } } @@ -286,10 +290,10 @@ impl Entry { Entry {task: task.unwrap_or_else(|| Pipeline::error(HTTPNotFound)), payload: psender, - recv: recv, stream: H2Writer::new(resp, settings.get_shared_bytes()), flags: EntryFlags::empty(), capacity: 0, + recv, } } diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index 466f6520b..29b534671 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -1,3 +1,5 @@ +#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] + use std::{io, cmp}; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll}; @@ -38,7 +40,7 @@ impl H2Writer { pub fn new(respond: SendResponse, buf: SharedBytes) -> H2Writer { H2Writer { - respond: respond, + respond, stream: None, encoder: ContentEncoder::empty(buf.clone()), flags: Flags::empty(), diff --git a/src/server/settings.rs b/src/server/settings.rs index 0ca4b4371..33e6fa8d1 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -36,11 +36,7 @@ impl ServerSettings { } else { "localhost".to_owned() }; - ServerSettings { - addr: addr, - secure: secure, - host: host, - } + ServerSettings { addr, secure, host } } /// Returns the socket address of the local half of this TCP connection diff --git a/src/server/worker.rs b/src/server/worker.rs index 23e8a6c61..5257d8615 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -62,7 +62,7 @@ impl Worker { Worker { settings: Rc::new(WorkerSettings::new(h, keep_alive)), hnd: Arbiter::handle().clone(), - handler: handler, + handler, } } diff --git a/src/test.rs b/src/test.rs index fe3422b54..b1d467b69 100644 --- a/src/test.rs +++ b/src/test.rs @@ -94,12 +94,12 @@ impl TestServer { let _ = sys.run(); }); - let (sys, addr) = rx.recv().unwrap(); + let (server_sys, addr) = rx.recv().unwrap(); TestServer { - addr: addr, + addr, thread: Some(join), system: System::new("actix-test"), - server_sys: sys, + server_sys, } } @@ -131,12 +131,12 @@ impl TestServer { let _ = sys.run(); }); - let (sys, addr) = rx.recv().unwrap(); + let (server_sys, addr) = rx.recv().unwrap(); TestServer { - addr: addr, + addr, + server_sys, thread: Some(join), system: System::new("actix-test"), - server_sys: sys, } } @@ -346,7 +346,7 @@ impl TestRequest { /// Start HttpRequest build process with application state pub fn with_state(state: S) -> TestRequest { TestRequest { - state: state, + state, method: Method::GET, uri: Uri::from_str("/").unwrap(), version: Version::HTTP_11, @@ -434,7 +434,7 @@ impl TestRequest { let req = self.finish(); let resp = h.handle(req.clone()); - match resp.respond_to(req.clone_without_state()) { + match resp.respond_to(req.without_state()) { Ok(resp) => { match resp.into().into() { ReplyItem::Message(resp) => Ok(resp), @@ -461,7 +461,7 @@ impl TestRequest { let mut core = Core::new().unwrap(); match core.run(fut) { Ok(r) => { - match r.respond_to(req.clone_without_state()) { + match r.respond_to(req.without_state()) { Ok(reply) => match reply.into().into() { ReplyItem::Message(resp) => Ok(resp), _ => panic!("Nested async replies are not supported"), diff --git a/src/ws/client.rs b/src/ws/client.rs index 369b56315..4b2a3b444 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -130,7 +130,7 @@ impl WsClient { http_err: None, origin: None, protocols: None, - conn: conn, + conn, }; cl.request.uri(uri.as_ref()); cl @@ -253,7 +253,7 @@ impl WsHandshake { io::ErrorKind::Other, "disconnected").into())))); WsHandshake { - key: key, + key, inner: None, request: Some(request.with_connector(conn.clone())), tx: Some(tx), @@ -261,7 +261,7 @@ impl WsHandshake { } } else { WsHandshake { - key: key, + key, inner: None, request: None, tx: None, @@ -340,7 +340,7 @@ impl Future for WsHandshake { let inner = Rc::new(UnsafeCell::new(inner)); Ok(Async::Ready( - (WsClientReader{inner: Rc::clone(&inner)}, WsClientWriter{inner: inner}))) + (WsClientReader{inner: Rc::clone(&inner)}, WsClientWriter{inner}))) } } diff --git a/src/ws/frame.rs b/src/ws/frame.rs index 22067cb60..7c573b712 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -146,13 +146,7 @@ impl Frame { } Ok(Async::Ready(Some(Frame { - finished: finished, - rsv1: rsv1, - rsv2: rsv2, - rsv3: rsv3, - opcode: opcode, - payload: data.into(), - }))) + finished, rsv1, rsv2, rsv3, opcode, payload: data.into() }))) } /// Generate binary representation From d6fd4a3524bf2d57685c7cb50db4749d6fb23ca6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 26 Feb 2018 15:26:27 -0800 Subject: [PATCH 15/84] use buffer capacity; remove unused imports --- guide/src/qs_7.md | 15 +------ src/client/connector.rs | 11 ++--- src/client/request.rs | 16 +++++++ src/client/writer.rs | 10 +++-- src/httprequest.rs | 89 +++++++++++++++++++++------------------ src/lib.rs | 6 +-- src/middleware/session.rs | 16 +++---- src/payload.rs | 9 +--- src/ws/client.rs | 16 ++----- src/ws/mod.rs | 2 +- 10 files changed, 87 insertions(+), 103 deletions(-) diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 2b063f57c..66ffcb633 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -259,20 +259,7 @@ fn index(mut req: HttpRequest) -> Box> { ## Streaming request *HttpRequest* is a stream of `Bytes` objects. It could be used to read request -body payload. At the same time actix uses -[*Payload*](../actix_web/payload/struct.Payload.html) object. -*HttpRequest* provides several methods, which can be used for -payload access.At the same time *Payload* implements *Stream* trait, so it -could be used with various stream combinators. Also *Payload* provides -several convenience methods that return future object that resolve to Bytes object. - -* *readexactly()* method returns *Future* that resolves when specified number of bytes - get received. - -* *readline()* method returns *Future* that resolves when `\n` get received. - -* *readuntil()* method returns *Future* that resolves when specified bytes string - matches in input bytes stream +body payload. In this example handle reads request payload chunk by chunk and prints every chunk. diff --git a/src/client/connector.rs b/src/client/connector.rs index 7acd4ed28..5f27b8265 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -1,19 +1,14 @@ -#![allow(unused_imports, dead_code)] use std::{io, time}; -use std::net::{SocketAddr, Shutdown}; -use std::collections::VecDeque; -use std::time::Duration; +use std::net::Shutdown; -use actix::{fut, Actor, ActorFuture, Arbiter, Context, +use actix::{fut, Actor, ActorFuture, Context, Handler, Message, ActorResponse, Supervised}; use actix::registry::ArbiterService; use actix::fut::WrapFuture; use actix::actors::{Connector, ConnectorError, Connect as ResolveConnect}; use http::{Uri, HttpTryFrom, Error as HttpError}; -use futures::{Async, Future, Poll}; -use tokio_core::reactor::Timeout; -use tokio_core::net::{TcpStream, TcpStreamNew}; +use futures::Poll; use tokio_io::{AsyncRead, AsyncWrite}; #[cfg(feature="alpn")] diff --git a/src/client/request.rs b/src/client/request.rs index fd1d40c5f..392ef6b9d 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -26,6 +26,7 @@ pub struct ClientRequest { upgrade: bool, encoding: ContentEncoding, response_decompress: bool, + buffer_capacity: Option<(usize, usize)>, } impl Default for ClientRequest { @@ -41,6 +42,7 @@ impl Default for ClientRequest { upgrade: false, encoding: ContentEncoding::Auto, response_decompress: true, + buffer_capacity: None, } } } @@ -167,6 +169,10 @@ impl ClientRequest { self.response_decompress } + pub fn buffer_capacity(&self) -> Option<(usize, usize)> { + self.buffer_capacity + } + /// Get body os this response #[inline] pub fn body(&self) -> &Body { @@ -434,6 +440,16 @@ impl ClientRequestBuilder { self } + /// Set write buffer capacity + pub fn buffer_capacity(&mut self, + low_watermark: usize, + high_watermark: usize) -> &mut Self + { + if let Some(parts) = parts(&mut self.request, &self.err) { + parts.buffer_capacity = Some((low_watermark, high_watermark)); + } + self + } /// This method calls provided closure with builder reference if value is true. pub fn if_true(&mut self, value: bool, f: F) -> &mut Self diff --git a/src/client/writer.rs b/src/client/writer.rs index f072ea7f4..f67bd7261 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -1,5 +1,4 @@ #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] -#![allow(dead_code)] use std::io::{self, Write}; use std::cell::RefCell; @@ -67,9 +66,9 @@ impl HttpClientWriter { self.buffer.take(); } - pub fn keepalive(&self) -> bool { - self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE) - } + // pub fn keepalive(&self) -> bool { + // self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE) + // } /// Set write buffer capacity pub fn set_buffer_capacity(&mut self, low_watermark: usize, high_watermark: usize) { @@ -107,6 +106,9 @@ impl HttpClientWriter { // prepare task self.flags.insert(Flags::STARTED); self.encoder = content_encoder(self.buffer.clone(), msg); + if let Some(capacity) = msg.buffer_capacity() { + self.set_buffer_capacity(capacity.0, capacity.1); + } // render message { diff --git a/src/httprequest.rs b/src/httprequest.rs index 3e6000b54..aa8df4f59 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -436,26 +436,6 @@ impl HttpRequest { } } - /// Returns reference to the associated http payload. - #[inline] - pub fn payload(&self) -> &Payload { - let msg = self.as_mut(); - if msg.payload.is_none() { - msg.payload = Some(Payload::empty()); - } - msg.payload.as_ref().unwrap() - } - - /// Returns mutable reference to the associated http payload. - #[inline] - pub fn payload_mut(&mut self) -> &mut Payload { - let msg = self.as_mut(); - if msg.payload.is_none() { - msg.payload = Some(Payload::empty()); - } - msg.payload.as_mut().unwrap() - } - /// Load request body. /// /// By default only 256Kb payload reads to a memory, then `BAD REQUEST` @@ -589,6 +569,24 @@ impl HttpRequest { pub fn json(self) -> JsonBody { JsonBody::from_request(self) } + + #[cfg(test)] + pub(crate) fn payload(&self) -> &Payload { + let msg = self.as_mut(); + if msg.payload.is_none() { + msg.payload = Some(Payload::empty()); + } + msg.payload.as_ref().unwrap() + } + + #[cfg(test)] + pub(crate) fn payload_mut(&mut self) -> &mut Payload { + let msg = self.as_mut(); + if msg.payload.is_none() { + msg.payload = Some(Payload::empty()); + } + msg.payload.as_mut().unwrap() + } } impl Default for HttpRequest<()> { @@ -610,36 +608,45 @@ impl Stream for HttpRequest { type Error = PayloadError; fn poll(&mut self) -> Poll, PayloadError> { - self.payload_mut().poll() + let msg = self.as_mut(); + if msg.payload.is_none() { + Ok(Async::Ready(None)) + } else { + msg.payload.as_mut().unwrap().poll() + } } } impl io::Read for HttpRequest { fn read(&mut self, buf: &mut [u8]) -> io::Result { - match self.payload_mut().poll() { - Ok(Async::Ready(Some(mut b))) => { - let i = cmp::min(b.len(), buf.len()); - buf.copy_from_slice(&b.split_to(i)[..i]); + if self.as_mut().payload.is_some() { + match self.as_mut().payload.as_mut().unwrap().poll() { + Ok(Async::Ready(Some(mut b))) => { + let i = cmp::min(b.len(), buf.len()); + buf.copy_from_slice(&b.split_to(i)[..i]); - if !b.is_empty() { - self.payload_mut().unread_data(b); - } - - if i < buf.len() { - match self.read(&mut buf[i..]) { - Ok(n) => Ok(i + n), - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Ok(i), - Err(e) => Err(e), + if !b.is_empty() { + self.as_mut().payload.as_mut().unwrap().unread_data(b); + } + + if i < buf.len() { + match self.read(&mut buf[i..]) { + Ok(n) => Ok(i + n), + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Ok(i), + Err(e) => Err(e), + } + } else { + Ok(i) } - } else { - Ok(i) } + Ok(Async::Ready(None)) => Ok(0), + Ok(Async::NotReady) => + Err(io::Error::new(io::ErrorKind::WouldBlock, "Not ready")), + Err(e) => + Err(io::Error::new(io::ErrorKind::Other, failure::Error::from(e).compat())), } - Ok(Async::Ready(None)) => Ok(0), - Ok(Async::NotReady) => - Err(io::Error::new(io::ErrorKind::WouldBlock, "Not ready")), - Err(e) => - Err(io::Error::new(io::ErrorKind::Other, failure::Error::from(e).compat())), + } else { + Ok(0) } } } diff --git a/src/lib.rs b/src/lib.rs index eb0da3c3c..6221afb90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,6 +100,7 @@ extern crate tokio_openssl; mod application; mod body; mod context; +mod handler; mod helpers; mod httprequest; mod httpresponse; @@ -107,9 +108,9 @@ mod info; mod json; mod route; mod router; -mod param; mod resource; -mod handler; +mod param; +mod payload; mod pipeline; pub mod client; @@ -121,7 +122,6 @@ pub mod multipart; pub mod middleware; pub mod pred; pub mod test; -pub mod payload; pub mod server; pub use error::{Error, Result, ResponseError}; pub use body::{Body, Binary}; diff --git a/src/middleware/session.rs b/src/middleware/session.rs index b46cd49ec..7cdb7e093 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -1,6 +1,3 @@ -#![allow(dead_code, unused_imports, unused_variables)] - -use std::any::Any; use std::rc::Rc; use std::sync::Arc; use std::marker::PhantomData; @@ -49,8 +46,7 @@ impl RequestSession for HttpRequest { return Session(s.0.as_mut()) } } - //Session(&mut DUMMY) - unreachable!() + Session(unsafe{&mut DUMMY}) } } @@ -195,15 +191,13 @@ pub trait SessionBackend: Sized + 'static { /// Dummy session impl, does not do anything struct DummySessionImpl; -static DUMMY: DummySessionImpl = DummySessionImpl; +static mut DUMMY: DummySessionImpl = DummySessionImpl; impl SessionImpl for DummySessionImpl { - fn get(&self, key: &str) -> Option<&str> { - None - } - fn set(&mut self, key: &str, value: String) {} - fn remove(&mut self, key: &str) {} + fn get(&self, _: &str) -> Option<&str> { None } + fn set(&mut self, _: &str, _: String) {} + fn remove(&mut self, _: &str) {} fn clear(&mut self) {} fn write(&self, resp: HttpResponse) -> Result { Ok(Response::Done(resp)) diff --git a/src/payload.rs b/src/payload.rs index 664004d7b..b193cf646 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -441,14 +441,6 @@ impl PayloadHelper where S: Stream { }) } - pub fn len(&self) -> usize { - self.len - } - - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - pub fn readany(&mut self) -> Poll, PayloadError> { if let Some(data) = self.items.pop_front() { self.len -= data.len(); @@ -569,6 +561,7 @@ impl PayloadHelper where S: Stream { self.items.push_front(data); } + #[allow(dead_code)] pub fn remaining(&mut self) -> Bytes { self.items.iter_mut() .fold(BytesMut::new(), |mut b, c| { diff --git a/src/ws/client.rs b/src/ws/client.rs index 4b2a3b444..19e2543ca 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -1,21 +1,17 @@ //! Http client request -#![allow(unused_imports, dead_code)] use std::{fmt, io, str}; use std::rc::Rc; -use std::time::Duration; use std::cell::UnsafeCell; use base64; use rand; +use bytes::Bytes; use cookie::Cookie; -use bytes::{Bytes, BytesMut}; use http::{HttpTryFrom, StatusCode, Error as HttpError}; use http::header::{self, HeaderName, HeaderValue}; use sha1::Sha1; use futures::{Async, Future, Poll, Stream}; -use futures::future::{Either, err as FutErr}; use futures::unsync::mpsc::{unbounded, UnboundedSender}; -use tokio_core::net::TcpStream; use byteorder::{ByteOrder, NetworkEndian}; use actix::prelude::*; @@ -23,13 +19,10 @@ use actix::prelude::*; use body::{Body, Binary}; use error::{WsError, UrlParseError}; use payload::PayloadHelper; -use server::shared::SharedBytes; -use server::{utils, IoStream}; use client::{ClientRequest, ClientRequestBuilder, ClientResponse, - HttpResponseParser, HttpResponseParserError, HttpClientWriter}; -use client::{Connect, Connection, ClientConnector, ClientConnectorError, - SendRequest, SendRequestError}; + ClientConnector, SendRequest, SendRequestError, + HttpResponseParserError}; use super::Message; use super::frame::Frame; @@ -224,7 +217,6 @@ struct WsInner { } pub struct WsHandshake { - inner: Option, request: Option, tx: Option>, key: String, @@ -254,7 +246,6 @@ impl WsHandshake { WsHandshake { key, - inner: None, request: Some(request.with_connector(conn.clone())), tx: Some(tx), error: err, @@ -262,7 +253,6 @@ impl WsHandshake { } else { WsHandshake { key, - inner: None, request: None, tx: None, error: err, diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 465400ac9..ef9eaf32e 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -91,7 +91,7 @@ pub fn start(req: HttpRequest, actor: A) -> Result S: 'static { let mut resp = handshake(&req)?; - let stream = WsStream::new(req.payload().clone()); + let stream = WsStream::new(req.clone()); let mut ctx = WebsocketContext::new(req, actor); ctx.add_message_stream(stream); From abae65a49efd20ac90824d3ea0ec64aca1d68683 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 26 Feb 2018 16:11:00 -0800 Subject: [PATCH 16/84] remove unused code --- src/client/connector.rs | 12 +- src/payload.rs | 280 +++++++++------------------------------- 2 files changed, 68 insertions(+), 224 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 5f27b8265..4e8ac214b 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -12,9 +12,11 @@ use futures::Poll; use tokio_io::{AsyncRead, AsyncWrite}; #[cfg(feature="alpn")] -use openssl::ssl::{SslMethod, SslConnector, SslVerifyMode, Error as OpensslError}; +use openssl::ssl::{SslMethod, SslConnector, Error as OpensslError}; #[cfg(feature="alpn")] use tokio_openssl::SslConnectorExt; +#[cfg(feature="alpn")] +use futures::Future; use HAS_OPENSSL; use server::IoStream; @@ -92,7 +94,7 @@ impl Default for ClientConnector { fn default() -> ClientConnector { #[cfg(feature="alpn")] { - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + let builder = SslConnector::builder(SslMethod::tls()).unwrap(); ClientConnector { connector: builder.build() } @@ -149,9 +151,7 @@ impl ClientConnector { /// } /// ``` pub fn with_connector(connector: SslConnector) -> ClientConnector { - ClientConnector { - connector: connector - } + ClientConnector { connector } } } @@ -196,7 +196,7 @@ impl Handler for ClientConnector { if proto.is_secure() { fut::Either::A( _act.connector.connect_async(&host, stream) - .map_err(|e| ClientConnectorError::SslError(e)) + .map_err(ClientConnectorError::SslError) .map(|stream| Connection{stream: Box::new(stream)}) .into_actor(_act)) } else { diff --git a/src/payload.rs b/src/payload.rs index b193cf646..401832b89 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -4,7 +4,7 @@ use std::rc::{Rc, Weak}; use std::cell::RefCell; use std::collections::VecDeque; use bytes::{Bytes, BytesMut}; -use futures::{Future, Async, Poll, Stream}; +use futures::{Async, Poll, Stream}; use futures::task::{Task, current as current_task}; use error::PayloadError; @@ -62,30 +62,6 @@ impl Payload { self.inner.borrow().len() == 0 } - /// Get exact number of bytes - #[inline] - pub fn readexactly(&self, size: usize) -> ReadExactly { - ReadExactly(Rc::clone(&self.inner), size) - } - - /// Read until `\n` - #[inline] - pub fn readline(&self) -> ReadLine { - ReadLine(Rc::clone(&self.inner)) - } - - /// Read until match line - #[inline] - pub fn readuntil(&self, line: &[u8]) -> ReadUntil { - ReadUntil(Rc::clone(&self.inner), line.to_vec()) - } - - #[doc(hidden)] - #[inline] - pub fn readall(&self) -> Option { - self.inner.borrow_mut().readall() - } - /// Put unused data back to payload #[inline] pub fn unread_data(&mut self, data: Bytes) { @@ -103,6 +79,11 @@ impl Payload { pub fn set_buffer_size(&self, size: usize) { self.inner.borrow_mut().set_buffer_size(size) } + + #[cfg(test)] + pub(crate) fn readall(&self) -> Option { + self.inner.borrow_mut().readall() + } } impl Stream for Payload { @@ -121,51 +102,6 @@ impl Clone for Payload { } } -/// Get exact number of bytes -pub struct ReadExactly(Rc>, usize); - -impl Future for ReadExactly { - type Item = Bytes; - type Error = PayloadError; - - fn poll(&mut self) -> Poll { - match self.0.borrow_mut().readexactly(self.1, false)? { - Async::Ready(chunk) => Ok(Async::Ready(chunk)), - Async::NotReady => Ok(Async::NotReady), - } - } -} - -/// Read until `\n` -pub struct ReadLine(Rc>); - -impl Future for ReadLine { - type Item = Bytes; - type Error = PayloadError; - - fn poll(&mut self) -> Poll { - match self.0.borrow_mut().readline(false)? { - Async::Ready(chunk) => Ok(Async::Ready(chunk)), - Async::NotReady => Ok(Async::NotReady), - } - } -} - -/// Read until match line -pub struct ReadUntil(Rc>, Vec); - -impl Future for ReadUntil { - type Item = Bytes; - type Error = PayloadError; - - fn poll(&mut self) -> Poll { - match self.0.borrow_mut().readuntil(&self.1, false)? { - Async::Ready(chunk) => Ok(Async::Ready(chunk)), - Async::NotReady => Ok(Async::NotReady), - } - } -} - /// Payload writer interface. pub trait PayloadWriter { @@ -271,6 +207,22 @@ impl Inner { self.len } + #[cfg(test)] + pub(crate) fn readall(&mut self) -> Option { + let len = self.items.iter().map(|b| b.len()).sum(); + if len > 0 { + let mut buf = BytesMut::with_capacity(len); + for item in &self.items { + buf.extend_from_slice(item); + } + self.items = VecDeque::new(); + self.len = 0; + Some(buf.take().freeze()) + } else { + None + } + } + fn readany(&mut self, notify: bool) -> Poll, PayloadError> { if let Some(data) = self.items.pop_front() { self.len -= data.len(); @@ -287,107 +239,6 @@ impl Inner { } } - fn readexactly(&mut self, size: usize, notify: bool) -> Result, PayloadError> { - if size <= self.len { - let mut buf = BytesMut::with_capacity(size); - while buf.len() < size { - let mut chunk = self.items.pop_front().unwrap(); - let rem = cmp::min(size - buf.len(), chunk.len()); - self.len -= rem; - buf.extend_from_slice(&chunk.split_to(rem)); - if !chunk.is_empty() { - self.items.push_front(chunk); - } - } - return Ok(Async::Ready(buf.freeze())) - } - - if let Some(err) = self.err.take() { - Err(err) - } else { - if notify { - self.task = Some(current_task()); - } - Ok(Async::NotReady) - } - } - - fn readuntil(&mut self, line: &[u8], notify: bool) -> Result, PayloadError> { - let mut idx = 0; - let mut num = 0; - let mut offset = 0; - let mut found = false; - let mut length = 0; - - for no in 0..self.items.len() { - { - let chunk = &self.items[no]; - for (pos, ch) in chunk.iter().enumerate() { - if *ch == line[idx] { - idx += 1; - if idx == line.len() { - num = no; - offset = pos+1; - length += pos+1; - found = true; - break; - } - } else { - idx = 0 - } - } - if !found { - length += chunk.len() - } - } - - if found { - let mut buf = BytesMut::with_capacity(length); - if num > 0 { - for _ in 0..num { - buf.extend_from_slice(&self.items.pop_front().unwrap()); - } - } - if offset > 0 { - let mut chunk = self.items.pop_front().unwrap(); - buf.extend_from_slice(&chunk.split_to(offset)); - if !chunk.is_empty() { - self.items.push_front(chunk) - } - } - self.len -= length; - return Ok(Async::Ready(buf.freeze())) - } - } - if let Some(err) = self.err.take() { - Err(err) - } else { - if notify { - self.task = Some(current_task()); - } - Ok(Async::NotReady) - } - } - - fn readline(&mut self, notify: bool) -> Result, PayloadError> { - self.readuntil(b"\n", notify) - } - - pub fn readall(&mut self) -> Option { - let len = self.items.iter().map(|b| b.len()).sum(); - if len > 0 { - let mut buf = BytesMut::with_capacity(len); - for item in &self.items { - buf.extend_from_slice(item); - } - self.items = VecDeque::new(); - self.len = 0; - Some(buf.take().freeze()) - } else { - None - } - } - fn unread_data(&mut self, data: Bytes) { self.len += data.len(); self.items.push_front(data); @@ -592,12 +443,11 @@ mod tests { #[test] fn test_basic() { Core::new().unwrap().run(lazy(|| { - let (_, mut payload) = Payload::new(false); + let (_, payload) = Payload::new(false); + let mut payload = PayloadHelper::new(payload); - assert!(!payload.eof()); - assert!(payload.is_empty()); - assert_eq!(payload.len(), 0); - assert_eq!(Async::NotReady, payload.poll().ok().unwrap()); + assert_eq!(payload.len, 0); + assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); let res: Result<(), ()> = Ok(()); result(res) @@ -607,23 +457,18 @@ mod tests { #[test] fn test_eof() { Core::new().unwrap().run(lazy(|| { - let (mut sender, mut payload) = Payload::new(false); - - assert_eq!(Async::NotReady, payload.poll().ok().unwrap()); - assert!(!payload.eof()); + let (mut sender, payload) = Payload::new(false); + let mut payload = PayloadHelper::new(payload); + assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); sender.feed_data(Bytes::from("data")); sender.feed_eof(); - assert!(!payload.eof()); - assert_eq!(Async::Ready(Some(Bytes::from("data"))), - payload.poll().ok().unwrap()); - assert!(payload.is_empty()); - assert!(payload.eof()); - assert_eq!(payload.len(), 0); + payload.readany().ok().unwrap()); + assert_eq!(payload.len, 0); + assert_eq!(Async::Ready(None), payload.readany().ok().unwrap()); - assert_eq!(Async::Ready(None), payload.poll().ok().unwrap()); let res: Result<(), ()> = Ok(()); result(res) })).unwrap(); @@ -632,12 +477,13 @@ mod tests { #[test] fn test_err() { Core::new().unwrap().run(lazy(|| { - let (mut sender, mut payload) = Payload::new(false); + let (mut sender, payload) = Payload::new(false); + let mut payload = PayloadHelper::new(payload); - assert_eq!(Async::NotReady, payload.poll().ok().unwrap()); + assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); sender.set_error(PayloadError::Incomplete); - payload.poll().err().unwrap(); + payload.readany().err().unwrap(); let res: Result<(), ()> = Ok(()); result(res) })).unwrap(); @@ -646,21 +492,19 @@ mod tests { #[test] fn test_readany() { Core::new().unwrap().run(lazy(|| { - let (mut sender, mut payload) = Payload::new(false); + let (mut sender, payload) = Payload::new(false); + let mut payload = PayloadHelper::new(payload); sender.feed_data(Bytes::from("line1")); - - assert!(!payload.is_empty()); - assert_eq!(payload.len(), 5); - sender.feed_data(Bytes::from("line2")); - assert!(!payload.is_empty()); - assert_eq!(payload.len(), 10); assert_eq!(Async::Ready(Some(Bytes::from("line1"))), - payload.poll().ok().unwrap()); - assert!(!payload.is_empty()); - assert_eq!(payload.len(), 5); + payload.readany().ok().unwrap()); + assert_eq!(payload.len, 0); + + assert_eq!(Async::Ready(Some(Bytes::from("line2"))), + payload.readany().ok().unwrap()); + assert_eq!(payload.len, 0); let res: Result<(), ()> = Ok(()); result(res) @@ -671,23 +515,23 @@ mod tests { fn test_readexactly() { Core::new().unwrap().run(lazy(|| { let (mut sender, payload) = Payload::new(false); + let mut payload = PayloadHelper::new(payload); - assert_eq!(Async::NotReady, payload.readexactly(2).poll().ok().unwrap()); + assert_eq!(Async::NotReady, payload.readexactly(2).ok().unwrap()); sender.feed_data(Bytes::from("line1")); sender.feed_data(Bytes::from("line2")); - assert_eq!(payload.len(), 10); - assert_eq!(Async::Ready(Bytes::from("li")), - payload.readexactly(2).poll().ok().unwrap()); - assert_eq!(payload.len(), 8); + assert_eq!(Async::Ready(Some(BytesMut::from("li"))), + payload.readexactly(2).ok().unwrap()); + assert_eq!(payload.len, 3); - assert_eq!(Async::Ready(Bytes::from("ne1l")), - payload.readexactly(4).poll().ok().unwrap()); - assert_eq!(payload.len(), 4); + assert_eq!(Async::Ready(Some(BytesMut::from("ne1l"))), + payload.readexactly(4).ok().unwrap()); + assert_eq!(payload.len, 4); sender.set_error(PayloadError::Incomplete); - payload.readexactly(10).poll().err().unwrap(); + payload.readexactly(10).err().unwrap(); let res: Result<(), ()> = Ok(()); result(res) @@ -698,23 +542,23 @@ mod tests { fn test_readuntil() { Core::new().unwrap().run(lazy(|| { let (mut sender, payload) = Payload::new(false); + let mut payload = PayloadHelper::new(payload); - assert_eq!(Async::NotReady, payload.readuntil(b"ne").poll().ok().unwrap()); + assert_eq!(Async::NotReady, payload.readuntil(b"ne").ok().unwrap()); sender.feed_data(Bytes::from("line1")); sender.feed_data(Bytes::from("line2")); - assert_eq!(payload.len(), 10); - assert_eq!(Async::Ready(Bytes::from("line")), - payload.readuntil(b"ne").poll().ok().unwrap()); - assert_eq!(payload.len(), 6); + assert_eq!(Async::Ready(Some(Bytes::from("line"))), + payload.readuntil(b"ne").ok().unwrap()); + assert_eq!(payload.len, 1); - assert_eq!(Async::Ready(Bytes::from("1line2")), - payload.readuntil(b"2").poll().ok().unwrap()); - assert_eq!(payload.len(), 0); + assert_eq!(Async::Ready(Some(Bytes::from("1line2"))), + payload.readuntil(b"2").ok().unwrap()); + assert_eq!(payload.len, 0); sender.set_error(PayloadError::Incomplete); - payload.readuntil(b"b").poll().err().unwrap(); + payload.readuntil(b"b").err().unwrap(); let res: Result<(), ()> = Ok(()); result(res) From 0ab8bc11f3c8113440e1e7af4580e031e4fb93b1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 26 Feb 2018 16:41:57 -0800 Subject: [PATCH 17/84] fix guide example --- guide/src/qs_7.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 66ffcb633..448d28eaf 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -169,15 +169,18 @@ get enabled automatically. Enabling chunked encoding for *HTTP/2.0* responses is forbidden. ```rust +# extern crate bytes; # extern crate actix_web; # extern crate futures; # use futures::Stream; use actix_web::*; +use bytes::Bytes; +use futures::stream::once; fn index(req: HttpRequest) -> HttpResponse { HttpResponse::Ok() .chunked() - .body(Body::Streaming(Box::new(payload::Payload::empty().from_err()))).unwrap() + .body(Body::Streaming(Box::new(once(Ok(Bytes::from_static(b"data")))))).unwrap() } # fn main() {} ``` From a344c3a02e035f4d0f086d076437b9e73d8112d1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 26 Feb 2018 20:07:22 -0800 Subject: [PATCH 18/84] remove read buffer management api --- src/payload.rs | 70 +++++------------------ src/server/encoding.rs | 11 ++-- src/server/h1.rs | 125 +++++++++++++++++++++++++++-------------- src/server/h2.rs | 55 ++++++++++-------- 4 files changed, 134 insertions(+), 127 deletions(-) diff --git a/src/payload.rs b/src/payload.rs index 401832b89..4fb80b0bc 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -5,12 +5,9 @@ use std::cell::RefCell; use std::collections::VecDeque; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll, Stream}; -use futures::task::{Task, current as current_task}; use error::PayloadError; -pub(crate) const DEFAULT_BUFFER_SIZE: usize = 65_536; // max buffer size 64k - /// Buffered stream of bytes chunks /// /// Payload stores chunks in a vector. First chunk can be received with `.readany()` method. @@ -68,18 +65,6 @@ impl Payload { self.inner.borrow_mut().unread_data(data); } - /// Get size of payload buffer - #[inline] - pub fn buffer_size(&self) -> usize { - self.inner.borrow().buffer_size() - } - - /// Set size of payload buffer - #[inline] - pub fn set_buffer_size(&self, size: usize) { - self.inner.borrow_mut().set_buffer_size(size) - } - #[cfg(test)] pub(crate) fn readall(&self) -> Option { self.inner.borrow_mut().readall() @@ -92,7 +77,7 @@ impl Stream for Payload { #[inline] fn poll(&mut self) -> Poll, PayloadError> { - self.inner.borrow_mut().readany(false) + self.inner.borrow_mut().readany() } } @@ -103,7 +88,7 @@ impl Clone for Payload { } /// Payload writer interface. -pub trait PayloadWriter { +pub(crate) trait PayloadWriter { /// Set stream error. fn set_error(&mut self, err: PayloadError); @@ -114,8 +99,8 @@ pub trait PayloadWriter { /// Feed bytes into a payload stream fn feed_data(&mut self, data: Bytes); - /// Get estimated available capacity - fn capacity(&self) -> usize; + /// Need read data + fn need_read(&self) -> bool; } /// Sender part of the payload stream @@ -144,24 +129,22 @@ impl PayloadWriter for PayloadSender { } #[inline] - fn capacity(&self) -> usize { + fn need_read(&self) -> bool { if let Some(shared) = self.inner.upgrade() { - shared.borrow().capacity() + shared.borrow().need_read } else { - 0 + false } } } - #[derive(Debug)] struct Inner { len: usize, eof: bool, err: Option, - task: Option, + need_read: bool, items: VecDeque, - buf_size: usize, } impl Inner { @@ -171,32 +154,23 @@ impl Inner { eof, len: 0, err: None, - task: None, items: VecDeque::new(), - buf_size: DEFAULT_BUFFER_SIZE, + need_read: false, } } fn set_error(&mut self, err: PayloadError) { self.err = Some(err); - if let Some(task) = self.task.take() { - task.notify() - } } fn feed_eof(&mut self) { self.eof = true; - if let Some(task) = self.task.take() { - task.notify() - } } fn feed_data(&mut self, data: Bytes) { self.len += data.len(); + self.need_read = false; self.items.push_back(data); - if let Some(task) = self.task.take() { - task.notify() - } } fn eof(&self) -> bool { @@ -219,11 +193,12 @@ impl Inner { self.len = 0; Some(buf.take().freeze()) } else { + self.need_read = true; None } } - fn readany(&mut self, notify: bool) -> Poll, PayloadError> { + fn readany(&mut self) -> Poll, PayloadError> { if let Some(data) = self.items.pop_front() { self.len -= data.len(); Ok(Async::Ready(Some(data))) @@ -232,9 +207,7 @@ impl Inner { } else if self.eof { Ok(Async::Ready(None)) } else { - if notify { - self.task = Some(current_task()); - } + self.need_read = true; Ok(Async::NotReady) } } @@ -243,23 +216,6 @@ impl Inner { self.len += data.len(); self.items.push_front(data); } - - #[inline] - fn capacity(&self) -> usize { - if self.len > self.buf_size { - 0 - } else { - self.buf_size - self.len - } - } - - fn buffer_size(&self) -> usize { - self.buf_size - } - - fn set_buffer_size(&mut self, size: usize) { - self.buf_size = size - } } pub struct PayloadHelper { diff --git a/src/server/encoding.rs b/src/server/encoding.rs index d2b2db939..c666b7232 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -120,10 +120,10 @@ impl PayloadWriter for PayloadType { } #[inline] - fn capacity(&self) -> usize { + fn need_read(&self) -> bool { match *self { - PayloadType::Sender(ref sender) => sender.capacity(), - PayloadType::Encoding(ref enc) => enc.capacity(), + PayloadType::Sender(ref sender) => sender.need_read(), + PayloadType::Encoding(ref enc) => enc.need_read(), } } } @@ -351,8 +351,9 @@ impl PayloadWriter for EncodedPayload { } } - fn capacity(&self) -> usize { - self.inner.capacity() + #[inline] + fn need_read(&self) -> bool { + self.inner.need_read() } } diff --git a/src/server/h1.rs b/src/server/h1.rs index 527eec671..cb24e6d0f 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -88,18 +88,6 @@ impl Http1 self.stream.get_mut() } - fn poll_completed(&mut self, shutdown: bool) -> Result { - // check stream state - match self.stream.poll_completed(shutdown) { - Ok(Async::Ready(_)) => Ok(true), - Ok(Async::NotReady) => Ok(false), - Err(err) => { - debug!("Error sending data: {}", err); - Err(()) - } - } - } - pub fn poll(&mut self) -> Poll<(), ()> { // keep-alive timer if let Some(ref mut timer) = self.keepalive_timer { @@ -113,11 +101,29 @@ impl Http1 } } - self.poll_io() + loop { + match self.poll_io()? { + Async::Ready(true) => (), + Async::Ready(false) => return Ok(Async::Ready(())), + Async::NotReady => return Ok(Async::NotReady), + } + } + } + + fn poll_completed(&mut self, shutdown: bool) -> Result { + // check stream state + match self.stream.poll_completed(shutdown) { + Ok(Async::Ready(_)) => Ok(true), + Ok(Async::NotReady) => Ok(false), + Err(err) => { + debug!("Error sending data: {}", err); + Err(()) + } + } } // TODO: refactor - pub fn poll_io(&mut self) -> Poll<(), ()> { + pub fn poll_io(&mut self) -> Poll { // read incoming data let need_read = if !self.flags.contains(Flags::ERROR) && self.tasks.len() < MAX_PIPELINED_MESSAGES @@ -135,9 +141,9 @@ impl Http1 // start request processing for h in self.settings.handlers().iter_mut() { req = match h.handle(req) { - Ok(t) => { + Ok(pipe) => { self.tasks.push_back( - Entry {pipe: t, flags: EntryFlags::empty()}); + Entry {pipe, flags: EntryFlags::empty()}); continue 'outer }, Err(req) => req, @@ -150,13 +156,6 @@ impl Http1 continue }, Ok(Async::NotReady) => (), - Err(ReaderError::Disconnect) => { - self.flags.insert(Flags::ERROR); - self.stream.disconnected(); - for entry in &mut self.tasks { - entry.pipe.disconnected() - } - }, Err(err) => { // notify all tasks self.stream.disconnected(); @@ -171,12 +170,16 @@ impl Http1 // on parse error, stop reading stream but tasks need to be completed self.flags.insert(Flags::ERROR); - if self.tasks.is_empty() { - if let ReaderError::Error(err) = err { - self.tasks.push_back( - Entry {pipe: Pipeline::error(err.error_response()), - flags: EntryFlags::empty()}); - } + match err { + ReaderError::Disconnect => (), + _ => + if self.tasks.is_empty() { + if let ReaderError::Error(err) = err { + self.tasks.push_back( + Entry {pipe: Pipeline::error(err.error_response()), + flags: EntryFlags::empty()}); + } + } } }, } @@ -187,6 +190,8 @@ impl Http1 true }; + let retry = self.reader.need_read(); + loop { // check in-flight messages let mut io = false; @@ -221,7 +226,12 @@ impl Http1 } }, // no more IO for this iteration - Ok(Async::NotReady) => io = true, + Ok(Async::NotReady) => { + if self.reader.need_read() && !retry { + return Ok(Async::Ready(true)); + } + io = true; + } Err(err) => { // it is not possible to recover from error // during pipe handling, so just drop connection @@ -268,14 +278,14 @@ impl Http1 if !self.poll_completed(true)? { return Ok(Async::NotReady) } - return Ok(Async::Ready(())) + return Ok(Async::Ready(false)) } // start keep-alive timer, this also is slow request timeout if self.tasks.is_empty() { // check stream state if self.flags.contains(Flags::ERROR) { - return Ok(Async::Ready(())) + return Ok(Async::Ready(false)) } if self.settings.keep_alive_enabled() { @@ -295,7 +305,7 @@ impl Http1 return Ok(Async::NotReady) } // keep-alive is disabled, drop connection - return Ok(Async::Ready(())) + return Ok(Async::Ready(false)) } } else if !self.poll_completed(false)? || self.flags.contains(Flags::KEEPALIVE) { @@ -303,7 +313,7 @@ impl Http1 // if keep-alive unset, rely on operating system return Ok(Async::NotReady) } else { - return Ok(Async::Ready(())) + return Ok(Async::Ready(false)) } } else { self.poll_completed(false)?; @@ -341,14 +351,27 @@ impl Reader { } } + #[inline] + fn need_read(&self) -> bool { + if let Some(ref info) = self.payload { + info.tx.need_read() + } else { + true + } + } + #[inline] fn decode(&mut self, buf: &mut BytesMut, payload: &mut PayloadInfo) -> Result { - loop { + while !buf.is_empty() { match payload.decoder.decode(buf) { Ok(Async::Ready(Some(bytes))) => { - payload.tx.feed_data(bytes) + payload.tx.feed_data(bytes); + if payload.decoder.is_eof() { + payload.tx.feed_eof(); + return Ok(Decoding::Ready) + } }, Ok(Async::Ready(None)) => { payload.tx.feed_eof(); @@ -361,6 +384,7 @@ impl Reader { } } } + Ok(Decoding::NotReady) } pub fn parse(&mut self, io: &mut T, @@ -368,12 +392,13 @@ impl Reader { settings: &WorkerSettings) -> Poll where T: IoStream { + if !self.need_read() { + return Ok(Async::NotReady) + } + // read payload let done = { if let Some(ref mut payload) = self.payload { - if payload.tx.capacity() == 0 { - return Ok(Async::NotReady) - } match utils::read_from_io(io, buf) { Ok(Async::Ready(0)) => { payload.tx.set_error(PayloadError::Incomplete); @@ -392,7 +417,11 @@ impl Reader { loop { match payload.decoder.decode(buf) { Ok(Async::Ready(Some(bytes))) => { - payload.tx.feed_data(bytes) + payload.tx.feed_data(bytes); + if payload.decoder.is_eof() { + payload.tx.feed_eof(); + break true + } }, Ok(Async::Ready(None)) => { payload.tx.feed_eof(); @@ -628,6 +657,13 @@ enum ChunkedState { } impl Decoder { + pub fn is_eof(&self) -> bool { + match self.kind { + Kind::Length(0) | Kind::Chunked(ChunkedState::End, _) | Kind::Eof(true) => true, + _ => false, + } + } + pub fn decode(&mut self, body: &mut BytesMut) -> Poll, io::Error> { match self.kind { Kind::Length(ref mut remaining) => { @@ -819,7 +855,7 @@ mod tests { use std::{io, cmp, time}; use std::net::Shutdown; use bytes::{Bytes, BytesMut, Buf}; - use futures::Async; + use futures::{Async, Stream}; use tokio_io::{AsyncRead, AsyncWrite}; use http::{Version, Method}; @@ -1324,6 +1360,7 @@ mod tests { assert!(!req.payload().eof()); buf.feed_data("4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); + let _ = req.payload_mut().poll(); not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert!(!req.payload().eof()); assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"dataline"); @@ -1348,6 +1385,7 @@ mod tests { "4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\ POST /test2 HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n"); + let _ = req.payload_mut().poll(); let req2 = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert_eq!(*req2.method(), Method::POST); @@ -1391,10 +1429,14 @@ mod tests { //buf.feed_data("test: test\r\n"); //not_ready!(reader.parse(&mut buf, &mut readbuf)); + let _ = req.payload_mut().poll(); + not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); + assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"dataline"); assert!(!req.payload().eof()); buf.feed_data("\r\n"); + let _ = req.payload_mut().poll(); not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert!(req.payload().eof()); } @@ -1413,6 +1455,7 @@ mod tests { assert!(!req.payload().eof()); buf.feed_data("4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") + let _ = req.payload_mut().poll(); not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert!(!req.payload().eof()); assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"dataline"); diff --git a/src/server/h2.rs b/src/server/h2.rs index 0a3875250..5dfcb57ad 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -34,7 +34,8 @@ bitflags! { } /// HTTP/2 Transport -pub(crate) struct Http2 +pub(crate) +struct Http2 where T: AsyncRead + AsyncWrite + 'static, H: 'static { flags: Flags, @@ -103,21 +104,29 @@ impl Http2 item.poll_payload(); if !item.flags.contains(EntryFlags::EOF) { - match item.task.poll_io(&mut item.stream) { - Ok(Async::Ready(ready)) => { - item.flags.insert(EntryFlags::EOF); - if ready { - item.flags.insert(EntryFlags::FINISHED); + let retry = item.payload.need_read(); + loop { + match item.task.poll_io(&mut item.stream) { + Ok(Async::Ready(ready)) => { + item.flags.insert(EntryFlags::EOF); + if ready { + item.flags.insert(EntryFlags::FINISHED); + } + not_ready = false; + }, + Ok(Async::NotReady) => { + if item.payload.need_read() && !retry { + continue + } + }, + Err(err) => { + error!("Unhandled error: {}", err); + item.flags.insert(EntryFlags::EOF); + item.flags.insert(EntryFlags::ERROR); + item.stream.reset(Reason::INTERNAL_ERROR); } - not_ready = false; - }, - Ok(Async::NotReady) => (), - Err(err) => { - error!("Unhandled error: {}", err); - item.flags.insert(EntryFlags::EOF); - item.flags.insert(EntryFlags::ERROR); - item.stream.reset(Reason::INTERNAL_ERROR); } + break } } else if !item.flags.contains(EntryFlags::FINISHED) { match item.task.poll() { @@ -248,7 +257,6 @@ struct Entry { payload: PayloadType, recv: RecvStream, stream: H2Writer, - capacity: usize, flags: EntryFlags, } @@ -292,13 +300,20 @@ impl Entry { payload: psender, stream: H2Writer::new(resp, settings.get_shared_bytes()), flags: EntryFlags::empty(), - capacity: 0, recv, } } fn poll_payload(&mut self) { if !self.flags.contains(EntryFlags::REOF) { + if self.payload.need_read() { + if let Err(err) = self.recv.release_capacity().release_capacity(32_768) { + self.payload.set_error(PayloadError::Http2(err)) + } + } else if let Err(err) = self.recv.release_capacity().release_capacity(0) { + self.payload.set_error(PayloadError::Http2(err)) + } + match self.recv.poll() { Ok(Async::Ready(Some(chunk))) => { self.payload.feed_data(chunk); @@ -311,14 +326,6 @@ impl Entry { self.payload.set_error(PayloadError::Http2(err)) } } - - let capacity = self.payload.capacity(); - if self.capacity != capacity { - self.capacity = capacity; - if let Err(err) = self.recv.release_capacity().release_capacity(capacity) { - self.payload.set_error(PayloadError::Http2(err)) - } - } } } } From 5dcb558f5000077a51aa50ffabae83a1ced21113 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 27 Feb 2018 10:09:24 -0800 Subject: [PATCH 19/84] refactor websockets handling --- examples/websocket-chat/src/main.rs | 5 +- examples/websocket/src/client.rs | 4 +- examples/websocket/src/main.rs | 5 +- guide/src/qs_9.md | 5 +- src/error.rs | 94 +--------------- src/ws/client.rs | 126 ++++++++++++--------- src/ws/context.rs | 2 +- src/ws/frame.rs | 48 +++++--- src/ws/mod.rs | 165 ++++++++++++++++++++++++---- tests/test_ws.rs | 3 +- 10 files changed, 265 insertions(+), 192 deletions(-) diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs index 50e6bff5e..b6783e83e 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -92,8 +92,7 @@ impl Handler for WsChatSession { } /// WebSocket message handler -impl Handler for WsChatSession { - type Result = (); +impl StreamHandler for WsChatSession { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { println!("WEBSOCKET MESSAGE: {:?}", msg); @@ -161,7 +160,7 @@ impl Handler for WsChatSession { }, ws::Message::Binary(bin) => println!("Unexpected binary"), - ws::Message::Close(_) | ws::Message::Error => { + ws::Message::Close(_) => { ctx.stop(); } } diff --git a/examples/websocket/src/client.rs b/examples/websocket/src/client.rs index 8dc410a78..dddc53b7b 100644 --- a/examples/websocket/src/client.rs +++ b/examples/websocket/src/client.rs @@ -12,7 +12,7 @@ use std::time::Duration; use actix::*; use futures::Future; -use actix_web::ws::{Message, WsClientError, WsClient, WsClientWriter}; +use actix_web::ws::{Message, WsError, WsClient, WsClientWriter}; fn main() { @@ -93,7 +93,7 @@ impl Handler for ChatClient { } /// Handle server websocket messages -impl StreamHandler for ChatClient { +impl StreamHandler for ChatClient { fn handle(&mut self, msg: Message, ctx: &mut Context) { match msg { diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index 620e56d40..7e0824546 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -25,8 +25,7 @@ impl Actor for MyWebSocket { } /// Handler for `ws::Message` -impl Handler for MyWebSocket { - type Result = (); +impl StreamHandler for MyWebSocket { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { // process websocket messages @@ -35,7 +34,7 @@ impl Handler for MyWebSocket { ws::Message::Ping(msg) => ctx.pong(&msg), ws::Message::Text(text) => ctx.text(text), ws::Message::Binary(bin) => ctx.binary(bin), - ws::Message::Close(_) | ws::Message::Error => { + ws::Message::Close(_) => { ctx.stop(); } _ => (), diff --git a/guide/src/qs_9.md b/guide/src/qs_9.md index 70d1e018f..8200435e0 100644 --- a/guide/src/qs_9.md +++ b/guide/src/qs_9.md @@ -21,9 +21,8 @@ impl Actor for Ws { type Context = ws::WebsocketContext; } -/// Define Handler for ws::Message message -impl Handler for Ws { - type Result=(); +/// Handler for ws::Message message +impl StreamHandler for Ws { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { match msg { diff --git a/src/error.rs b/src/error.rs index 4cc674860..6c50db251 100644 --- a/src/error.rs +++ b/src/error.rs @@ -24,7 +24,7 @@ use body::Body; use handler::Responder; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use httpcodes::{self, HTTPBadRequest, HTTPMethodNotAllowed, HTTPExpectationFailed}; +use httpcodes::{self, HTTPExpectationFailed}; /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) /// for actix web operations @@ -341,82 +341,6 @@ impl ResponseError for ExpectError { } } -/// Websocket handshake errors -#[derive(Fail, PartialEq, Debug)] -pub enum WsHandshakeError { - /// Only get method is allowed - #[fail(display="Method not allowed")] - GetMethodRequired, - /// Upgrade header if not set to websocket - #[fail(display="Websocket upgrade is expected")] - NoWebsocketUpgrade, - /// Connection header is not set to upgrade - #[fail(display="Connection upgrade is expected")] - NoConnectionUpgrade, - /// Websocket version header is not set - #[fail(display="Websocket version header is required")] - NoVersionHeader, - /// Unsupported websocket version - #[fail(display="Unsupported version")] - UnsupportedVersion, - /// Websocket key is not set or wrong - #[fail(display="Unknown websocket key")] - BadWebsocketKey, -} - -impl ResponseError for WsHandshakeError { - - fn error_response(&self) -> HttpResponse { - match *self { - WsHandshakeError::GetMethodRequired => { - HTTPMethodNotAllowed - .build() - .header(header::ALLOW, "GET") - .finish() - .unwrap() - } - WsHandshakeError::NoWebsocketUpgrade => - HTTPBadRequest.with_reason("No WebSocket UPGRADE header found"), - WsHandshakeError::NoConnectionUpgrade => - HTTPBadRequest.with_reason("No CONNECTION upgrade"), - WsHandshakeError::NoVersionHeader => - HTTPBadRequest.with_reason("Websocket version header is required"), - WsHandshakeError::UnsupportedVersion => - HTTPBadRequest.with_reason("Unsupported version"), - WsHandshakeError::BadWebsocketKey => - HTTPBadRequest.with_reason("Handshake error"), - } - } -} - -/// Websocket errors -#[derive(Fail, Debug)] -pub enum WsError { - /// Received an unmasked frame from client - #[fail(display="Received an unmasked frame from client")] - UnmaskedFrame, - /// Received a masked frame from server - #[fail(display="Received a masked frame from server")] - MaskedFrame, - /// Encountered invalid opcode - #[fail(display="Invalid opcode: {}", _0)] - InvalidOpcode(u8), - /// Invalid control frame length - #[fail(display="Invalid control frame length: {}", _0)] - InvalidLength(usize), - /// Payload error - #[fail(display="Payload error: {}", _0)] - Payload(#[cause] PayloadError), -} - -impl ResponseError for WsError {} - -impl From for WsError { - fn from(err: PayloadError) -> WsError { - WsError::Payload(err) - } -} - /// A set of errors that can occur during parsing urlencoded payloads #[derive(Fail, Debug)] pub enum UrlencodedError { @@ -769,22 +693,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::EXPECTATION_FAILED); } - #[test] - fn test_wserror_http_response() { - let resp: HttpResponse = WsHandshakeError::GetMethodRequired.error_response(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let resp: HttpResponse = WsHandshakeError::NoWebsocketUpgrade.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = WsHandshakeError::NoConnectionUpgrade.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = WsHandshakeError::NoVersionHeader.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = WsHandshakeError::UnsupportedVersion.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = WsHandshakeError::BadWebsocketKey.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - } - macro_rules! from { ($from:expr => $error:pat) => { match ParseError::from($from) { diff --git a/src/ws/client.rs b/src/ws/client.rs index 19e2543ca..7b7a07419 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -17,14 +17,14 @@ use byteorder::{ByteOrder, NetworkEndian}; use actix::prelude::*; use body::{Body, Binary}; -use error::{WsError, UrlParseError}; +use error::UrlParseError; use payload::PayloadHelper; use client::{ClientRequest, ClientRequestBuilder, ClientResponse, ClientConnector, SendRequest, SendRequestError, HttpResponseParserError}; -use super::Message; +use super::{Message, WsError}; use super::frame::Frame; use super::proto::{CloseCode, OpCode}; @@ -106,6 +106,7 @@ pub struct WsClient { origin: Option, protocols: Option, conn: Addr, + max_size: usize, } impl WsClient { @@ -123,6 +124,7 @@ impl WsClient { http_err: None, origin: None, protocols: None, + max_size: 65_536, conn, }; cl.request.uri(uri.as_ref()); @@ -158,6 +160,14 @@ impl WsClient { self } + /// Set max frame size + /// + /// By default max size is set to 64kb + pub fn max_frame_size(mut self, size: usize) -> Self { + self.max_size = size; + self + } + /// Set request header pub fn header(mut self, key: K, value: V) -> Self where HeaderName: HttpTryFrom, HeaderValue: HttpTryFrom @@ -167,12 +177,12 @@ impl WsClient { } /// Connect to websocket server and do ws handshake - pub fn connect(&mut self) -> WsHandshake { + pub fn connect(&mut self) -> WsClientHandshake { if let Some(e) = self.err.take() { - WsHandshake::new(None, Some(e), &self.conn) + WsClientHandshake::error(e) } else if let Some(e) = self.http_err.take() { - WsHandshake::new(None, Some(e.into()), &self.conn) + WsClientHandshake::error(e.into()) } else { // origin if let Some(origin) = self.origin.take() { @@ -189,23 +199,22 @@ impl WsClient { } let request = match self.request.finish() { Ok(req) => req, - Err(err) => return WsHandshake::new(None, Some(err.into()), &self.conn), + Err(err) => return WsClientHandshake::error(err.into()), }; if request.uri().host().is_none() { - return WsHandshake::new(None, Some(WsClientError::InvalidUrl), &self.conn) + return WsClientHandshake::error(WsClientError::InvalidUrl) } if let Some(scheme) = request.uri().scheme_part() { if scheme != "http" && scheme != "https" && scheme != "ws" && scheme != "wss" { - return WsHandshake::new( - None, Some(WsClientError::InvalidUrl), &self.conn) + return WsClientHandshake::error(WsClientError::InvalidUrl) } } else { - return WsHandshake::new(None, Some(WsClientError::InvalidUrl), &self.conn) + return WsClientHandshake::error(WsClientError::InvalidUrl) } // start handshake - WsHandshake::new(Some(request), None, &self.conn) + WsClientHandshake::new(request, &self.conn, self.max_size) } } } @@ -216,17 +225,17 @@ struct WsInner { closed: bool, } -pub struct WsHandshake { +pub struct WsClientHandshake { request: Option, tx: Option>, key: String, error: Option, + max_size: usize, } -impl WsHandshake { - fn new(request: Option, - err: Option, - conn: &Addr) -> WsHandshake +impl WsClientHandshake { + fn new(mut request: ClientRequest, + conn: &Addr, max_size: usize) -> WsClientHandshake { // Generate a random key for the `Sec-WebSocket-Key` header. // a base64-encoded (see Section 4 of [RFC4648]) value that, @@ -234,34 +243,36 @@ impl WsHandshake { let sec_key: [u8; 16] = rand::random(); let key = base64::encode(&sec_key); - if let Some(mut request) = request { - request.headers_mut().insert( - HeaderName::try_from("SEC-WEBSOCKET-KEY").unwrap(), - HeaderValue::try_from(key.as_str()).unwrap()); + request.headers_mut().insert( + HeaderName::try_from("SEC-WEBSOCKET-KEY").unwrap(), + HeaderValue::try_from(key.as_str()).unwrap()); - let (tx, rx) = unbounded(); - request.set_body(Body::Streaming( - Box::new(rx.map_err(|_| io::Error::new( - io::ErrorKind::Other, "disconnected").into())))); + let (tx, rx) = unbounded(); + request.set_body(Body::Streaming( + Box::new(rx.map_err(|_| io::Error::new( + io::ErrorKind::Other, "disconnected").into())))); - WsHandshake { - key, - request: Some(request.with_connector(conn.clone())), - tx: Some(tx), - error: err, - } - } else { - WsHandshake { - key, - request: None, - tx: None, - error: err, - } + WsClientHandshake { + key, + max_size, + request: Some(request.with_connector(conn.clone())), + tx: Some(tx), + error: None, + } + } + + fn error(err: WsClientError) -> WsClientHandshake { + WsClientHandshake { + key: String::new(), + request: None, + tx: None, + error: Some(err), + max_size: 0 } } } -impl Future for WsHandshake { +impl Future for WsClientHandshake { type Item = (WsClientReader, WsClientWriter); type Error = WsClientError; @@ -330,13 +341,15 @@ impl Future for WsHandshake { let inner = Rc::new(UnsafeCell::new(inner)); Ok(Async::Ready( - (WsClientReader{inner: Rc::clone(&inner)}, WsClientWriter{inner}))) + (WsClientReader{inner: Rc::clone(&inner), max_size: self.max_size}, + WsClientWriter{inner}))) } } pub struct WsClientReader { - inner: Rc> + inner: Rc>, + max_size: usize, } impl fmt::Debug for WsClientReader { @@ -354,29 +367,36 @@ impl WsClientReader { impl Stream for WsClientReader { type Item = Message; - type Error = WsClientError; + type Error = WsError; fn poll(&mut self) -> Poll, Self::Error> { + let max_size = self.max_size; let inner = self.as_mut(); if inner.closed { return Ok(Async::Ready(None)) } // read - match Frame::parse(&mut inner.rx, false) { + match Frame::parse(&mut inner.rx, false, max_size) { Ok(Async::Ready(Some(frame))) => { - // trace!("WsFrame {}", frame); - let (_finished, opcode, payload) = frame.unpack(); + let (finished, opcode, payload) = frame.unpack(); + + // continuation is not supported + if !finished { + inner.closed = true; + return Err(WsError::NoContinuation) + } match opcode { OpCode::Continue => unimplemented!(), - OpCode::Bad => - Ok(Async::Ready(Some(Message::Error))), + OpCode::Bad => { + inner.closed = true; + Err(WsError::BadOpCode) + }, OpCode::Close => { inner.closed = true; let code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16; - Ok(Async::Ready( - Some(Message::Close(CloseCode::from(code))))) + Ok(Async::Ready(Some(Message::Close(CloseCode::from(code))))) }, OpCode::Ping => Ok(Async::Ready(Some( @@ -393,17 +413,19 @@ impl Stream for WsClientReader { match String::from_utf8(tmp) { Ok(s) => Ok(Async::Ready(Some(Message::Text(s)))), - Err(_) => - Ok(Async::Ready(Some(Message::Error))), + Err(_) => { + inner.closed = true; + Err(WsError::BadEncoding) + } } } } } Ok(Async::Ready(None)) => Ok(Async::Ready(None)), Ok(Async::NotReady) => Ok(Async::NotReady), - Err(err) => { + Err(e) => { inner.closed = true; - Err(err.into()) + Err(e) } } } diff --git a/src/ws/context.rs b/src/ws/context.rs index b9214b749..8720b461c 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -18,7 +18,7 @@ use ws::frame::Frame; use ws::proto::{OpCode, CloseCode}; -/// Http actor execution context +/// `WebSockets` actor execution context pub struct WebsocketContext where A: Actor>, { inner: ContextImpl, diff --git a/src/ws/frame.rs b/src/ws/frame.rs index 7c573b712..320566585 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -6,8 +6,10 @@ use futures::{Async, Poll, Stream}; use rand; use body::Binary; -use error::{WsError, PayloadError}; +use error::{PayloadError}; use payload::PayloadHelper; + +use ws::WsError; use ws::proto::{OpCode, CloseCode}; use ws::mask::apply_mask; @@ -50,7 +52,8 @@ impl Frame { } /// Parse the input stream into a frame. - pub fn parse(pl: &mut PayloadHelper, server: bool) -> Poll, WsError> + pub fn parse(pl: &mut PayloadHelper, server: bool, max_size: usize) + -> Poll, WsError> where S: Stream { let mut idx = 2; @@ -99,6 +102,11 @@ impl Frame { len as usize }; + // check for max allowed size + if length > max_size { + return Err(WsError::Overflow) + } + let mask = if server { let buf = match pl.copy(idx + 4)? { Async::Ready(Some(buf)) => buf, @@ -267,13 +275,13 @@ mod tests { fn test_parse() { let mut buf = PayloadHelper::new( once(Ok(BytesMut::from(&[0b00000001u8, 0b00000001u8][..]).freeze()))); - assert!(is_none(Frame::parse(&mut buf, false))); + assert!(is_none(Frame::parse(&mut buf, false, 1024))); let mut buf = BytesMut::from(&[0b00000001u8, 0b00000001u8][..]); buf.extend(b"1"); let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); - let frame = extract(Frame::parse(&mut buf, false)); + let frame = extract(Frame::parse(&mut buf, false, 1024)); println!("FRAME: {}", frame); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); @@ -285,7 +293,7 @@ mod tests { let buf = BytesMut::from(&[0b00000001u8, 0b00000000u8][..]); let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); - let frame = extract(Frame::parse(&mut buf, false)); + let frame = extract(Frame::parse(&mut buf, false, 1024)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); assert!(frame.payload.is_empty()); @@ -295,14 +303,14 @@ mod tests { fn test_parse_length2() { let buf = BytesMut::from(&[0b00000001u8, 126u8][..]); let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); - assert!(is_none(Frame::parse(&mut buf, false))); + assert!(is_none(Frame::parse(&mut buf, false, 1024))); let mut buf = BytesMut::from(&[0b00000001u8, 126u8][..]); buf.extend(&[0u8, 4u8][..]); buf.extend(b"1234"); let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); - let frame = extract(Frame::parse(&mut buf, false)); + let frame = extract(Frame::parse(&mut buf, false, 1024)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); assert_eq!(frame.payload.as_ref(), &b"1234"[..]); @@ -312,14 +320,14 @@ mod tests { fn test_parse_length4() { let buf = BytesMut::from(&[0b00000001u8, 127u8][..]); let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); - assert!(is_none(Frame::parse(&mut buf, false))); + assert!(is_none(Frame::parse(&mut buf, false, 1024))); let mut buf = BytesMut::from(&[0b00000001u8, 127u8][..]); buf.extend(&[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8][..]); buf.extend(b"1234"); let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); - let frame = extract(Frame::parse(&mut buf, false)); + let frame = extract(Frame::parse(&mut buf, false, 1024)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); assert_eq!(frame.payload.as_ref(), &b"1234"[..]); @@ -332,9 +340,9 @@ mod tests { buf.extend(b"1"); let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); - assert!(Frame::parse(&mut buf, false).is_err()); + assert!(Frame::parse(&mut buf, false, 1024).is_err()); - let frame = extract(Frame::parse(&mut buf, true)); + let frame = extract(Frame::parse(&mut buf, true, 1024)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); assert_eq!(frame.payload, vec![1u8].into()); @@ -346,14 +354,28 @@ mod tests { buf.extend(&[1u8]); let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); - assert!(Frame::parse(&mut buf, true).is_err()); + assert!(Frame::parse(&mut buf, true, 1024).is_err()); - let frame = extract(Frame::parse(&mut buf, false)); + let frame = extract(Frame::parse(&mut buf, false, 1024)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); assert_eq!(frame.payload, vec![1u8].into()); } + #[test] + fn test_parse_frame_max_size() { + let mut buf = BytesMut::from(&[0b00000001u8, 0b00000010u8][..]); + buf.extend(&[1u8, 1u8]); + let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); + + assert!(Frame::parse(&mut buf, true, 1).is_err()); + + if let Err(WsError::Overflow) = Frame::parse(&mut buf, false, 0) { + } else { + panic!("error"); + } + } + #[test] fn test_ping_frame() { let frame = Frame::message(Vec::from("data"), OpCode::Ping, true, false); diff --git a/src/ws/mod.rs b/src/ws/mod.rs index ef9eaf32e..b2f0da3c1 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -23,9 +23,8 @@ //! type Context = ws::WebsocketContext; //! } //! -//! // Define Handler for ws::Message message -//! impl Handler for Ws { -//! type Result = (); +//! // Handler for ws::Message messages +//! impl StreamHandler for Ws { //! //! fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { //! match msg { @@ -48,13 +47,14 @@ use http::{Method, StatusCode, header}; use futures::{Async, Poll, Stream}; use byteorder::{ByteOrder, NetworkEndian}; -use actix::{Actor, AsyncContext, Handler}; +use actix::{Actor, AsyncContext, StreamHandler}; use body::Binary; use payload::PayloadHelper; -use error::{Error, WsHandshakeError, PayloadError}; +use error::{Error, PayloadError, ResponseError}; use httprequest::HttpRequest; use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder}; +use httpcodes::{HTTPBadRequest, HTTPMethodNotAllowed}; mod frame; mod proto; @@ -66,7 +66,8 @@ use self::frame::Frame; use self::proto::{hash_key, OpCode}; pub use self::proto::CloseCode; pub use self::context::WebsocketContext; -pub use self::client::{WsClient, WsClientError, WsClientReader, WsClientWriter, WsHandshake}; +pub use self::client::{WsClient, WsClientError, + WsClientReader, WsClientWriter, WsClientHandshake}; const SEC_WEBSOCKET_ACCEPT: &str = "SEC-WEBSOCKET-ACCEPT"; const SEC_WEBSOCKET_KEY: &str = "SEC-WEBSOCKET-KEY"; @@ -74,6 +75,94 @@ const SEC_WEBSOCKET_VERSION: &str = "SEC-WEBSOCKET-VERSION"; // const SEC_WEBSOCKET_PROTOCOL: &'static str = "SEC-WEBSOCKET-PROTOCOL"; +/// Websocket errors +#[derive(Fail, Debug)] +pub enum WsError { + /// Received an unmasked frame from client + #[fail(display="Received an unmasked frame from client")] + UnmaskedFrame, + /// Received a masked frame from server + #[fail(display="Received a masked frame from server")] + MaskedFrame, + /// Encountered invalid opcode + #[fail(display="Invalid opcode: {}", _0)] + InvalidOpcode(u8), + /// Invalid control frame length + #[fail(display="Invalid control frame length: {}", _0)] + InvalidLength(usize), + /// Bad web socket op code + #[fail(display="Bad web socket op code")] + BadOpCode, + /// A payload reached size limit. + #[fail(display="A payload reached size limit.")] + Overflow, + /// Continuation is not supproted + #[fail(display="Continuation is not supproted.")] + NoContinuation, + /// Bad utf-8 encoding + #[fail(display="Bad utf-8 encoding.")] + BadEncoding, + /// Payload error + #[fail(display="Payload error: {}", _0)] + Payload(#[cause] PayloadError), +} + +impl ResponseError for WsError {} + +impl From for WsError { + fn from(err: PayloadError) -> WsError { + WsError::Payload(err) + } +} + +/// Websocket handshake errors +#[derive(Fail, PartialEq, Debug)] +pub enum WsHandshakeError { + /// Only get method is allowed + #[fail(display="Method not allowed")] + GetMethodRequired, + /// Upgrade header if not set to websocket + #[fail(display="Websocket upgrade is expected")] + NoWebsocketUpgrade, + /// Connection header is not set to upgrade + #[fail(display="Connection upgrade is expected")] + NoConnectionUpgrade, + /// Websocket version header is not set + #[fail(display="Websocket version header is required")] + NoVersionHeader, + /// Unsupported websocket version + #[fail(display="Unsupported version")] + UnsupportedVersion, + /// Websocket key is not set or wrong + #[fail(display="Unknown websocket key")] + BadWebsocketKey, +} + +impl ResponseError for WsHandshakeError { + + fn error_response(&self) -> HttpResponse { + match *self { + WsHandshakeError::GetMethodRequired => { + HTTPMethodNotAllowed + .build() + .header(header::ALLOW, "GET") + .finish() + .unwrap() + } + WsHandshakeError::NoWebsocketUpgrade => + HTTPBadRequest.with_reason("No WebSocket UPGRADE header found"), + WsHandshakeError::NoConnectionUpgrade => + HTTPBadRequest.with_reason("No CONNECTION upgrade"), + WsHandshakeError::NoVersionHeader => + HTTPBadRequest.with_reason("Websocket version header is required"), + WsHandshakeError::UnsupportedVersion => + HTTPBadRequest.with_reason("Unsupported version"), + WsHandshakeError::BadWebsocketKey => + HTTPBadRequest.with_reason("Handshake error"), + } + } +} + /// `WebSocket` Message #[derive(Debug, PartialEq, Message)] pub enum Message { @@ -82,19 +171,18 @@ pub enum Message { Ping(String), Pong(String), Close(CloseCode), - Error } /// Do websocket handshake and start actor pub fn start(req: HttpRequest, actor: A) -> Result - where A: Actor> + Handler, + where A: Actor> + StreamHandler, S: 'static { let mut resp = handshake(&req)?; let stream = WsStream::new(req.clone()); let mut ctx = WebsocketContext::new(req, actor); - ctx.add_message_stream(stream); + ctx.add_stream(stream); Ok(resp.body(ctx)?) } @@ -168,33 +256,52 @@ pub fn handshake(req: &HttpRequest) -> Result { rx: PayloadHelper, closed: bool, + max_size: usize, } impl WsStream where S: Stream { + /// Create new websocket frames stream pub fn new(stream: S) -> WsStream { WsStream { rx: PayloadHelper::new(stream), - closed: false } + closed: false, + max_size: 65_536, + } + } + + /// Set max frame size + /// + /// By default max size is set to 64kb + pub fn max_size(mut self, size: usize) -> Self { + self.max_size = size; + self } } impl Stream for WsStream where S: Stream { type Item = Message; - type Error = (); + type Error = WsError; fn poll(&mut self) -> Poll, Self::Error> { if self.closed { return Ok(Async::Ready(None)) } - match Frame::parse(&mut self.rx, true) { + match Frame::parse(&mut self.rx, true, self.max_size) { Ok(Async::Ready(Some(frame))) => { - // trace!("WsFrame {}", frame); - let (_finished, opcode, payload) = frame.unpack(); + let (finished, opcode, payload) = frame.unpack(); + + // continuation is not supported + if !finished { + self.closed = true; + return Err(WsError::NoContinuation) + } match opcode { OpCode::Continue => unimplemented!(), - OpCode::Bad => - Ok(Async::Ready(Some(Message::Error))), + OpCode::Bad => { + self.closed = true; + Err(WsError::BadOpCode) + } OpCode::Close => { self.closed = true; let code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16; @@ -215,17 +322,19 @@ impl Stream for WsStream where S: Stream { match String::from_utf8(tmp) { Ok(s) => Ok(Async::Ready(Some(Message::Text(s)))), - Err(_) => - Ok(Async::Ready(Some(Message::Error))), + Err(_) => { + self.closed = true; + Err(WsError::BadEncoding) + } } } } } Ok(Async::Ready(None)) => Ok(Async::Ready(None)), Ok(Async::NotReady) => Ok(Async::NotReady), - Err(_) => { + Err(e) => { self.closed = true; - Ok(Async::Ready(Some(Message::Error))) + Err(e) } } } @@ -306,4 +415,20 @@ mod tests { assert_eq!(StatusCode::SWITCHING_PROTOCOLS, handshake(&req).unwrap().finish().unwrap().status()); } + + #[test] + fn test_wserror_http_response() { + let resp: HttpResponse = WsHandshakeError::GetMethodRequired.error_response(); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + let resp: HttpResponse = WsHandshakeError::NoWebsocketUpgrade.error_response(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + let resp: HttpResponse = WsHandshakeError::NoConnectionUpgrade.error_response(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + let resp: HttpResponse = WsHandshakeError::NoVersionHeader.error_response(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + let resp: HttpResponse = WsHandshakeError::UnsupportedVersion.error_response(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + let resp: HttpResponse = WsHandshakeError::BadWebsocketKey.error_response(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } } diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 8b7d0111f..13aeef486 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -16,8 +16,7 @@ impl Actor for Ws { type Context = ws::WebsocketContext; } -impl Handler for Ws { - type Result = (); +impl StreamHandler for Ws { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { match msg { From 6c480fae90b9515c0615ad009deae09183b88375 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 27 Feb 2018 11:31:54 -0800 Subject: [PATCH 20/84] added HttpRequest::encoding() method; fix urlencoded parsing with charset --- CHANGES.md | 2 + Cargo.toml | 3 +- examples/state/src/main.rs | 5 +- guide/src/qs_8.md | 3 +- src/error.rs | 22 ++++++- src/httprequest.rs | 119 ++++++++++++++++++++++++++++++------- src/lib.rs | 1 + 7 files changed, 127 insertions(+), 28 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a406bdfa0..04e1aed85 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,8 @@ * Simplify HttpServer type definition +* Added HttpRequest::encoding() method + * Added HttpRequest::mime_type() method * Added HttpRequest::uri_mut(), allows to modify request uri diff --git a/Cargo.toml b/Cargo.toml index b7999b743..bdafe0127 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,8 @@ serde_json = "1.0" sha1 = "0.4" smallvec = "0.6" time = "0.1" -url = "1.6" +encoding = "0.2" +url = { version="1.7", features=["query_encoding"] } cookie = { version="0.10", features=["percent-encode", "secure"] } # io diff --git a/examples/state/src/main.rs b/examples/state/src/main.rs index a981c7fbb..f40f779ed 100644 --- a/examples/state/src/main.rs +++ b/examples/state/src/main.rs @@ -36,8 +36,7 @@ impl Actor for MyWebSocket { type Context = ws::WebsocketContext; } -impl Handler for MyWebSocket { - type Result = (); +impl StreamHandler for MyWebSocket { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { self.counter += 1; @@ -46,7 +45,7 @@ impl Handler for MyWebSocket { ws::Message::Ping(msg) => ctx.pong(&msg), ws::Message::Text(text) => ctx.text(text), ws::Message::Binary(bin) => ctx.binary(bin), - ws::Message::Close(_) | ws::Message::Error => { + ws::Message::Close(_) => { ctx.stop(); } _ => (), diff --git a/guide/src/qs_8.md b/guide/src/qs_8.md index 2e2b54201..b19e94a45 100644 --- a/guide/src/qs_8.md +++ b/guide/src/qs_8.md @@ -130,8 +130,7 @@ impl Actor for Ws { type Context = ws::WebsocketContext; } -impl Handler for Ws { - type Result = (); +impl StreamHandler for Ws { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { match msg { diff --git a/src/error.rs b/src/error.rs index 6c50db251..d0497073a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -335,12 +335,29 @@ pub enum ExpectError { } impl ResponseError for ExpectError { - fn error_response(&self) -> HttpResponse { HTTPExpectationFailed.with_body("Unknown Expect") } } +/// A set of error that can occure during parsing content type +#[derive(Fail, PartialEq, Debug)] +pub enum ContentTypeError { + /// Can not parse content type + #[fail(display="Can not parse content type")] + ParseError, + /// Unknown content encoding + #[fail(display="Unknown content encoding")] + UnknownEncoding, +} + +/// Return `BadRequest` for `ContentTypeError` +impl ResponseError for ContentTypeError { + fn error_response(&self) -> HttpResponse { + HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) + } +} + /// A set of errors that can occur during parsing urlencoded payloads #[derive(Fail, Debug)] pub enum UrlencodedError { @@ -356,6 +373,9 @@ pub enum UrlencodedError { /// Content type error #[fail(display="Content type error")] ContentType, + /// Parse error + #[fail(display="Parse error")] + Parse, /// Payload error #[fail(display="Error that occur during reading payload: {}", _0)] Payload(#[cause] PayloadError), diff --git a/src/httprequest.rs b/src/httprequest.rs index aa8df4f59..ca70b6ed0 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -11,6 +11,9 @@ use serde::de::DeserializeOwned; use mime::Mime; use failure; use url::{Url, form_urlencoded}; +use encoding::all::UTF_8; +use encoding::EncodingRef; +use encoding::label::encoding_from_whatwg_label; use http::{header, Uri, Method, Version, HeaderMap, Extensions}; use tokio_io::AsyncRead; @@ -21,7 +24,7 @@ use payload::Payload; use json::JsonBody; use multipart::Multipart; use helpers::SharedHttpMessage; -use error::{ParseError, UrlGenerationError, +use error::{ParseError, ContentTypeError, UrlGenerationError, CookieParseError, HttpRangeError, PayloadError, UrlencodedError}; @@ -389,17 +392,38 @@ impl HttpRequest { "" } + /// Get content type encoding + /// + /// UTF-8 is used by default, If request charset is not set. + pub fn encoding(&self) -> Result { + if let Some(mime_type) = self.mime_type()? { + if let Some(charset) = mime_type.get_param("charset") { + if let Some(enc) = encoding_from_whatwg_label(charset.as_str()) { + Ok(enc) + } else { + Err(ContentTypeError::UnknownEncoding) + } + } else { + Ok(UTF_8) + } + } else { + Ok(UTF_8) + } + } + /// Convert the request content type to a known mime type. - pub fn mime_type(&self) -> Option { + pub fn mime_type(&self) -> Result, ContentTypeError> { if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { if let Ok(content_type) = content_type.to_str() { return match content_type.parse() { - Ok(mt) => Some(mt), - Err(_) => None + Ok(mt) => Ok(Some(mt)), + Err(_) => Err(ContentTypeError::ParseError), }; + } else { + return Err(ContentTypeError::ParseError) } } - None + Ok(None) } /// Check if request requires connection upgrade @@ -722,17 +746,10 @@ impl Future for UrlEncoded { } // check content type - let mut err = true; - if let Some(content_type) = req.headers().get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - if content_type.to_lowercase() == "application/x-www-form-urlencoded" { - err = false; - } - } - } - if err { - return Err(UrlencodedError::ContentType); + if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { + return Err(UrlencodedError::ContentType) } + let encoding = req.encoding().map_err(|_| UrlencodedError::ContentType)?; // future let limit = self.limit; @@ -745,12 +762,14 @@ impl Future for UrlEncoded { Ok(body) } }) - .map(|body| { + .and_then(move |body| { let mut m = HashMap::new(); - for (k, v) in form_urlencoded::parse(&body) { + let parsed = form_urlencoded::parse_with_encoding( + &body, Some(encoding), false).map_err(|_| UrlencodedError::Parse)?; + for (k, v) in parsed { m.insert(k.into(), v.into()); } - m + Ok(m) }); self.fut = Some(Box::new(fut)); } @@ -828,8 +847,11 @@ impl Future for RequestBody { mod tests { use super::*; use mime; + use encoding::Encoding; + use encoding::all::ISO_8859_2; use http::{Uri, HttpTryFrom}; use std::str::FromStr; + use std::iter::FromIterator; use router::Pattern; use resource::Resource; use test::TestRequest; @@ -856,17 +878,49 @@ mod tests { #[test] fn test_mime_type() { let req = TestRequest::with_header("content-type", "application/json").finish(); - assert_eq!(req.mime_type(), Some(mime::APPLICATION_JSON)); + assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON)); let req = HttpRequest::default(); - assert_eq!(req.mime_type(), None); + assert_eq!(req.mime_type().unwrap(), None); let req = TestRequest::with_header( "content-type", "application/json; charset=utf-8").finish(); - let mt = req.mime_type().unwrap(); + let mt = req.mime_type().unwrap().unwrap(); assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8)); assert_eq!(mt.type_(), mime::APPLICATION); assert_eq!(mt.subtype(), mime::JSON); } + #[test] + fn test_mime_type_error() { + let req = TestRequest::with_header( + "content-type", "applicationadfadsfasdflknadsfklnadsfjson").finish(); + assert_eq!(Err(ContentTypeError::ParseError), req.mime_type()); + } + + #[test] + fn test_encoding() { + let req = HttpRequest::default(); + assert_eq!(UTF_8.name(), req.encoding().unwrap().name()); + + let req = TestRequest::with_header( + "content-type", "application/json").finish(); + assert_eq!(UTF_8.name(), req.encoding().unwrap().name()); + + let req = TestRequest::with_header( + "content-type", "application/json; charset=ISO-8859-2").finish(); + assert_eq!(ISO_8859_2.name(), req.encoding().unwrap().name()); + } + + #[test] + fn test_encoding_error() { + let req = TestRequest::with_header( + "content-type", "applicatjson").finish(); + assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err()); + + let req = TestRequest::with_header( + "content-type", "application/json; charset=kkkttktk").finish(); + assert_eq!(Some(ContentTypeError::UnknownEncoding), req.encoding().err()); + } + #[test] fn test_uri_mut() { let mut req = HttpRequest::default(); @@ -1009,6 +1063,29 @@ mod tests { assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::ContentType); } + #[test] + fn test_urlencoded() { + let mut req = TestRequest::with_header( + header::CONTENT_TYPE, "application/x-www-form-urlencoded") + .header(header::CONTENT_LENGTH, "11") + .finish(); + req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); + + let result = req.urlencoded().poll().ok().unwrap(); + assert_eq!(result, Async::Ready( + HashMap::from_iter(vec![("hello".to_owned(), "world".to_owned())]))); + + let mut req = TestRequest::with_header( + header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8") + .header(header::CONTENT_LENGTH, "11") + .finish(); + req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); + + let result = req.urlencoded().poll().ok().unwrap(); + assert_eq!(result, Async::Ready( + HashMap::from_iter(vec![("hello".to_owned(), "world".to_owned())]))); +} + #[test] fn test_request_body() { let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); diff --git a/src/lib.rs b/src/lib.rs index 6221afb90..91ec9e946 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,6 +77,7 @@ extern crate serde; extern crate serde_json; extern crate flate2; extern crate brotli2; +extern crate encoding; extern crate percent_encoding; extern crate smallvec; extern crate num_cpus; From aac9b5a97c63e617d21407893b41d46e619a163b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 27 Feb 2018 12:49:11 -0800 Subject: [PATCH 21/84] update readme --- README.md | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 47d7db733..12426d083 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](http://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -Actix web is a small, fast, pragmatic, open source rust web framework. +Actix web is a small, pragmatic, extremely fast, web framework for Rust. * Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.github.io/actix-web/guide/qs_13.html) protocols * Streaming and pipelining diff --git a/src/lib.rs b/src/lib.rs index 91ec9e946..8177dd759 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -//! Actix web is a small, fast, pragmatic, open source rust web framework. +//! Actix web is a small, pragmatic, extremely fast, web framework for Rust. //! //! ```rust //! use actix_web::*; From a7bf635158105f54758cf085ec27e380211a7e2a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 27 Feb 2018 15:03:28 -0800 Subject: [PATCH 22/84] unify headers and body processing for client response and server request --- src/client/mod.rs | 2 +- src/client/pipeline.rs | 1 + src/client/response.rs | 331 +-------------------- src/helpers.rs | 54 ++-- src/httpmessage.rs | 595 ++++++++++++++++++++++++++++++++++++++ src/httprequest.rs | 600 ++------------------------------------- src/info.rs | 1 + src/json.rs | 28 +- src/lib.rs | 4 +- src/middleware/cors.rs | 1 + src/middleware/logger.rs | 1 + src/pred.rs | 1 + src/server/encoding.rs | 4 +- src/server/h1.rs | 1 + src/server/h1writer.rs | 4 +- src/server/h2.rs | 1 + src/server/h2writer.rs | 4 +- src/server/mod.rs | 4 +- src/server/settings.rs | 4 +- src/ws/client.rs | 1 + src/ws/mod.rs | 1 + 21 files changed, 699 insertions(+), 944 deletions(-) create mode 100644 src/httpmessage.rs diff --git a/src/client/mod.rs b/src/client/mod.rs index f7b735437..f3d8172ba 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -8,7 +8,7 @@ mod writer; pub use self::pipeline::{SendRequest, SendRequestError}; pub use self::request::{ClientRequest, ClientRequestBuilder}; -pub use self::response::{ClientResponse, ResponseBody, JsonResponse, UrlEncoded}; +pub use self::response::ClientResponse; pub use self::connector::{Connect, Connection, ClientConnector, ClientConnectorError}; pub(crate) use self::writer::HttpClientWriter; pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError}; diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index dfb01bd63..bd35d975b 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -10,6 +10,7 @@ use error::Error; use body::{Body, BodyStream}; use context::{Frame, ActorHttpContext}; use headers::ContentEncoding; +use httpmessage::HttpMessage; use error::PayloadError; use server::WriterState; use server::shared::SharedBytes; diff --git a/src/client/response.rs b/src/client/response.rs index 4bb7c2d66..0f997dcda 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,20 +1,15 @@ use std::{fmt, str}; use std::rc::Rc; use std::cell::UnsafeCell; -use std::collections::HashMap; -use bytes::{Bytes, BytesMut}; +use bytes::Bytes; use cookie::Cookie; -use futures::{Async, Future, Poll, Stream}; +use futures::{Async, Poll, Stream}; use http::{HeaderMap, StatusCode, Version}; use http::header::{self, HeaderValue}; -use mime::Mime; -use serde_json; -use serde::de::DeserializeOwned; -use url::form_urlencoded; -// use multipart::Multipart; -use error::{CookieParseError, ParseError, PayloadError, JsonPayloadError, UrlencodedError}; +use httpmessage::HttpMessage; +use error::{CookieParseError, PayloadError}; use super::pipeline::Pipeline; @@ -41,6 +36,14 @@ impl Default for ClientMessage { /// An HTTP Client response pub struct ClientResponse(Rc>, Option>); +impl HttpMessage for ClientResponse { + /// Get the headers from the response. + #[inline] + fn headers(&self) -> &HeaderMap { + &self.as_ref().headers + } +} + impl ClientResponse { pub(crate) fn new(msg: ClientMessage) -> ClientResponse { @@ -68,12 +71,6 @@ impl ClientResponse { self.as_ref().version } - /// Get the headers from the response. - #[inline] - pub fn headers(&self) -> &HeaderMap { - &self.as_ref().headers - } - /// Get a mutable reference to the headers. #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { @@ -120,83 +117,6 @@ impl ClientResponse { } None } - - /// Read the request content type. If request does not contain - /// *Content-Type* header, empty str get returned. - pub fn content_type(&self) -> &str { - if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - return content_type.split(';').next().unwrap().trim() - } - } - "" - } - - /// Convert the request content type to a known mime type. - pub fn mime_type(&self) -> Option { - if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - return match content_type.parse() { - Ok(mt) => Some(mt), - Err(_) => None - }; - } - } - None - } - - /// Check if request has chunked transfer encoding - pub fn chunked(&self) -> Result { - if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) { - if let Ok(s) = encodings.to_str() { - Ok(s.to_lowercase().contains("chunked")) - } else { - Err(ParseError::Header) - } - } else { - Ok(false) - } - } - - /// Load request body. - /// - /// By default only 256Kb payload reads to a memory, then connection get dropped - /// and `PayloadError` get returned. Use `ResponseBody::limit()` - /// method to change upper limit. - pub fn body(self) -> ResponseBody { - ResponseBody::new(self) - } - - // /// Return stream to http payload processes as multipart. - // /// - // /// Content-type: multipart/form-data; - // pub fn multipart(mut self) -> Multipart { - // Multipart::from_response(&mut self) - // } - - /// Parse `application/x-www-form-urlencoded` encoded body. - /// Return `UrlEncoded` future. It resolves to a `HashMap` which - /// contains decoded parameters. - /// - /// Returns error: - /// - /// * content type is not `application/x-www-form-urlencoded` - /// * transfer encoding is `chunked`. - /// * content-length is greater than 256k - pub fn urlencoded(self) -> UrlEncoded { - UrlEncoded::new(self) - } - - /// Parse `application/json` encoded body. - /// Return `JsonResponse` future. It resolves to a `T` value. - /// - /// Returns error: - /// - /// * content type is not `application/json` - /// * content length is greater than 256k - pub fn json(self) -> JsonResponse { - JsonResponse::from_response(self) - } } impl fmt::Debug for ClientResponse { @@ -229,230 +149,3 @@ impl Stream for ClientResponse { } } } - -/// Future that resolves to a complete response body. -#[must_use = "ResponseBody does nothing unless polled"] -pub struct ResponseBody { - limit: usize, - resp: Option, - fut: Option>>, -} - -impl ResponseBody { - - /// Create `ResponseBody` for request. - pub fn new(resp: ClientResponse) -> Self { - ResponseBody { - limit: 262_144, - resp: Some(resp), - fut: None, - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } -} - -impl Future for ResponseBody { - type Item = Bytes; - type Error = PayloadError; - - fn poll(&mut self) -> Poll { - if let Some(resp) = self.resp.take() { - if let Some(len) = resp.headers().get(header::CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - if len > self.limit { - return Err(PayloadError::Overflow); - } - } else { - return Err(PayloadError::Overflow); - } - } - } - let limit = self.limit; - let fut = resp.from_err() - .fold(BytesMut::new(), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(PayloadError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }) - .map(|bytes| bytes.freeze()); - self.fut = Some(Box::new(fut)); - } - - self.fut.as_mut().expect("ResponseBody could not be used second time").poll() - } -} - -/// Client response json parser that resolves to a deserialized `T` value. -/// -/// Returns error: -/// -/// * content type is not `application/json` -/// * content length is greater than 256k -#[must_use = "JsonResponse does nothing unless polled"] -pub struct JsonResponse{ - limit: usize, - ct: &'static str, - resp: Option, - fut: Option>>, -} - -impl JsonResponse { - - /// Create `JsonResponse` for request. - pub fn from_response(resp: ClientResponse) -> Self { - JsonResponse{ - limit: 262_144, - resp: Some(resp), - ct: "application/json", - fut: None, - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - /// Set allowed content type. - /// - /// By default *application/json* content type is used. Set content type - /// to empty string if you want to disable content type check. - pub fn content_type(mut self, ct: &'static str) -> Self { - self.ct = ct; - self - } -} - -impl Future for JsonResponse { - type Item = T; - type Error = JsonPayloadError; - - fn poll(&mut self) -> Poll { - if let Some(resp) = self.resp.take() { - if let Some(len) = resp.headers().get(header::CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - if len > self.limit { - return Err(JsonPayloadError::Overflow); - } - } else { - return Err(JsonPayloadError::Overflow); - } - } - } - // check content-type - if !self.ct.is_empty() && resp.content_type() != self.ct { - return Err(JsonPayloadError::ContentType) - } - - let limit = self.limit; - let fut = resp.from_err() - .fold(BytesMut::new(), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(JsonPayloadError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }) - .and_then(|body| Ok(serde_json::from_slice::(&body)?)); - - self.fut = Some(Box::new(fut)); - } - - self.fut.as_mut().expect("JsonResponse could not be used second time").poll() - } -} - -/// Future that resolves to a parsed urlencoded values. -#[must_use = "UrlEncoded does nothing unless polled"] -pub struct UrlEncoded { - resp: Option, - limit: usize, - fut: Option, Error=UrlencodedError>>>, -} - -impl UrlEncoded { - pub fn new(resp: ClientResponse) -> UrlEncoded { - UrlEncoded{resp: Some(resp), - limit: 262_144, - fut: None} - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } -} - -impl Future for UrlEncoded { - type Item = HashMap; - type Error = UrlencodedError; - - fn poll(&mut self) -> Poll { - if let Some(resp) = self.resp.take() { - if resp.chunked().unwrap_or(false) { - return Err(UrlencodedError::Chunked) - } else if let Some(len) = resp.headers().get(header::CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - if len > 262_144 { - return Err(UrlencodedError::Overflow); - } - } else { - return Err(UrlencodedError::UnknownLength); - } - } else { - return Err(UrlencodedError::UnknownLength); - } - } - - // check content type - let mut encoding = false; - if let Some(content_type) = resp.headers().get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - if content_type.to_lowercase() == "application/x-www-form-urlencoded" { - encoding = true; - } - } - } - if !encoding { - return Err(UrlencodedError::ContentType); - } - - // urlencoded body - let limit = self.limit; - let fut = resp.from_err() - .fold(BytesMut::new(), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(UrlencodedError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }) - .and_then(|body| { - let mut m = HashMap::new(); - for (k, v) in form_urlencoded::parse(&body) { - m.insert(k.into(), v.into()); - } - Ok(m) - }); - - self.fut = Some(Box::new(fut)); - } - - self.fut.as_mut().expect("UrlEncoded could not be used second time").poll() - } -} diff --git a/src/helpers.rs b/src/helpers.rs index 25e22b8fe..5f54f48f9 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -8,7 +8,7 @@ use time; use bytes::{BufMut, BytesMut}; use http::Version; -use httprequest::HttpMessage; +use httprequest::HttpInnerMessage; // "Sun, 06 Nov 1994 08:49:37 GMT".len() pub(crate) const DATE_VALUE_LENGTH: usize = 29; @@ -67,7 +67,7 @@ impl fmt::Write for CachedDate { } /// Internal use only! unsafe -pub(crate) struct SharedMessagePool(RefCell>>); +pub(crate) struct SharedMessagePool(RefCell>>); impl SharedMessagePool { pub fn new() -> SharedMessagePool { @@ -75,16 +75,16 @@ impl SharedMessagePool { } #[inline] - pub fn get(&self) -> Rc { + pub fn get(&self) -> Rc { if let Some(msg) = self.0.borrow_mut().pop_front() { msg } else { - Rc::new(HttpMessage::default()) + Rc::new(HttpInnerMessage::default()) } } #[inline] - pub fn release(&self, mut msg: Rc) { + pub fn release(&self, mut msg: Rc) { let v = &mut self.0.borrow_mut(); if v.len() < 128 { Rc::get_mut(&mut msg).unwrap().reset(); @@ -93,10 +93,10 @@ impl SharedMessagePool { } } -pub(crate) struct SharedHttpMessage( - Option>, Option>); +pub(crate) struct SharedHttpInnerMessage( + Option>, Option>); -impl Drop for SharedHttpMessage { +impl Drop for SharedHttpInnerMessage { fn drop(&mut self) { if let Some(ref pool) = self.1 { if let Some(msg) = self.0.take() { @@ -108,56 +108,56 @@ impl Drop for SharedHttpMessage { } } -impl Deref for SharedHttpMessage { - type Target = HttpMessage; +impl Deref for SharedHttpInnerMessage { + type Target = HttpInnerMessage; - fn deref(&self) -> &HttpMessage { + fn deref(&self) -> &HttpInnerMessage { self.get_ref() } } -impl DerefMut for SharedHttpMessage { +impl DerefMut for SharedHttpInnerMessage { - fn deref_mut(&mut self) -> &mut HttpMessage { + fn deref_mut(&mut self) -> &mut HttpInnerMessage { self.get_mut() } } -impl Clone for SharedHttpMessage { +impl Clone for SharedHttpInnerMessage { - fn clone(&self) -> SharedHttpMessage { - SharedHttpMessage(self.0.clone(), self.1.clone()) + fn clone(&self) -> SharedHttpInnerMessage { + SharedHttpInnerMessage(self.0.clone(), self.1.clone()) } } -impl Default for SharedHttpMessage { +impl Default for SharedHttpInnerMessage { - fn default() -> SharedHttpMessage { - SharedHttpMessage(Some(Rc::new(HttpMessage::default())), None) + fn default() -> SharedHttpInnerMessage { + SharedHttpInnerMessage(Some(Rc::new(HttpInnerMessage::default())), None) } } -impl SharedHttpMessage { +impl SharedHttpInnerMessage { - pub fn from_message(msg: HttpMessage) -> SharedHttpMessage { - SharedHttpMessage(Some(Rc::new(msg)), None) + pub fn from_message(msg: HttpInnerMessage) -> SharedHttpInnerMessage { + SharedHttpInnerMessage(Some(Rc::new(msg)), None) } - pub fn new(msg: Rc, pool: Rc) -> SharedHttpMessage { - SharedHttpMessage(Some(msg), Some(pool)) + pub fn new(msg: Rc, pool: Rc) -> SharedHttpInnerMessage { + SharedHttpInnerMessage(Some(msg), Some(pool)) } #[inline(always)] #[allow(mutable_transmutes)] #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] - pub fn get_mut(&self) -> &mut HttpMessage { - let r: &HttpMessage = self.0.as_ref().unwrap().as_ref(); + pub fn get_mut(&self) -> &mut HttpInnerMessage { + let r: &HttpInnerMessage = self.0.as_ref().unwrap().as_ref(); unsafe{mem::transmute(r)} } #[inline(always)] #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] - pub fn get_ref(&self) -> &HttpMessage { + pub fn get_ref(&self) -> &HttpInnerMessage { self.0.as_ref().unwrap() } } diff --git a/src/httpmessage.rs b/src/httpmessage.rs new file mode 100644 index 000000000..14a551ead --- /dev/null +++ b/src/httpmessage.rs @@ -0,0 +1,595 @@ +use std::str; +use std::collections::HashMap; +use bytes::{Bytes, BytesMut}; +use futures::{Future, Stream, Poll}; +use http_range::HttpRange; +use serde::de::DeserializeOwned; +use mime::Mime; +use url::form_urlencoded; +use encoding::all::UTF_8; +use encoding::EncodingRef; +use encoding::label::encoding_from_whatwg_label; +use http::{header, HeaderMap}; + +use json::JsonBody; +use multipart::Multipart; +use error::{ParseError, ContentTypeError, + HttpRangeError, PayloadError, UrlencodedError}; + + +pub trait HttpMessage { + + /// Read the Message Headers. + fn headers(&self) -> &HeaderMap; + + /// Read the request content type. If request does not contain + /// *Content-Type* header, empty str get returned. + fn content_type(&self) -> &str { + if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { + if let Ok(content_type) = content_type.to_str() { + return content_type.split(';').next().unwrap().trim() + } + } + "" + } + + /// Get content type encoding + /// + /// UTF-8 is used by default, If request charset is not set. + fn encoding(&self) -> Result { + if let Some(mime_type) = self.mime_type()? { + if let Some(charset) = mime_type.get_param("charset") { + if let Some(enc) = encoding_from_whatwg_label(charset.as_str()) { + Ok(enc) + } else { + Err(ContentTypeError::UnknownEncoding) + } + } else { + Ok(UTF_8) + } + } else { + Ok(UTF_8) + } + } + + /// Convert the request content type to a known mime type. + fn mime_type(&self) -> Result, ContentTypeError> { + if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { + if let Ok(content_type) = content_type.to_str() { + return match content_type.parse() { + Ok(mt) => Ok(Some(mt)), + Err(_) => Err(ContentTypeError::ParseError), + }; + } else { + return Err(ContentTypeError::ParseError) + } + } + Ok(None) + } + + /// Check if request has chunked transfer encoding + fn chunked(&self) -> Result { + if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) { + if let Ok(s) = encodings.to_str() { + Ok(s.to_lowercase().contains("chunked")) + } else { + Err(ParseError::Header) + } + } else { + Ok(false) + } + } + + /// Parses Range HTTP header string as per RFC 2616. + /// `size` is full size of response (file). + fn range(&self, size: u64) -> Result, HttpRangeError> { + if let Some(range) = self.headers().get(header::RANGE) { + HttpRange::parse(unsafe{str::from_utf8_unchecked(range.as_bytes())}, size) + .map_err(|e| e.into()) + } else { + Ok(Vec::new()) + } + } + + /// Load http message body. + /// + /// By default only 256Kb payload reads to a memory, then `PayloadError::Overflow` + /// get returned. Use `MessageBody::limit()` method to change upper limit. + /// + /// ## Server example + /// + /// ```rust + /// # extern crate bytes; + /// # extern crate actix_web; + /// # extern crate futures; + /// # #[macro_use] extern crate serde_derive; + /// use actix_web::*; + /// use bytes::Bytes; + /// use futures::future::Future; + /// + /// fn index(mut req: HttpRequest) -> Box> { + /// req.body() // <- get Body future + /// .limit(1024) // <- change max size of the body to a 1kb + /// .from_err() + /// .and_then(|bytes: Bytes| { // <- complete body + /// println!("==== BODY ==== {:?}", bytes); + /// Ok(httpcodes::HTTPOk.into()) + /// }).responder() + /// } + /// # fn main() {} + /// ``` + fn body(self) -> MessageBody + where Self: Stream + Sized + { + MessageBody::new(self) + } + + /// Parse `application/x-www-form-urlencoded` encoded body. + /// Return `UrlEncoded` future. It resolves to a `HashMap` which + /// contains decoded parameters. + /// + /// Returns error: + /// + /// * content type is not `application/x-www-form-urlencoded` + /// * transfer encoding is `chunked`. + /// * content-length is greater than 256k + /// + /// ## Server example + /// + /// ```rust + /// # extern crate actix_web; + /// # extern crate futures; + /// use actix_web::*; + /// use futures::future::{Future, ok}; + /// + /// fn index(mut req: HttpRequest) -> Box> { + /// req.urlencoded() // <- get UrlEncoded future + /// .from_err() + /// .and_then(|params| { // <- url encoded parameters + /// println!("==== BODY ==== {:?}", params); + /// ok(httpcodes::HTTPOk.into()) + /// }) + /// .responder() + /// } + /// # fn main() {} + /// ``` + fn urlencoded(self) -> UrlEncoded + where Self: Stream + Sized + { + UrlEncoded::new(self) + } + + /// Parse `application/json` encoded body. + /// Return `JsonBody` future. It resolves to a `T` value. + /// + /// Returns error: + /// + /// * content type is not `application/json` + /// * content length is greater than 256k + /// + /// ## Server example + /// + /// ```rust + /// # extern crate actix_web; + /// # extern crate futures; + /// # #[macro_use] extern crate serde_derive; + /// use actix_web::*; + /// use futures::future::{Future, ok}; + /// + /// #[derive(Deserialize, Debug)] + /// struct MyObj { + /// name: String, + /// } + /// + /// fn index(mut req: HttpRequest) -> Box> { + /// req.json() // <- get JsonBody future + /// .from_err() + /// .and_then(|val: MyObj| { // <- deserialized value + /// println!("==== BODY ==== {:?}", val); + /// Ok(httpcodes::HTTPOk.into()) + /// }).responder() + /// } + /// # fn main() {} + /// ``` + fn json(self) -> JsonBody + where Self: Stream + Sized + { + JsonBody::new(self) + } + + /// Return stream to http payload processes as multipart. + /// + /// Content-type: multipart/form-data; + /// + /// ## Server example + /// + /// ```rust + /// # extern crate actix; + /// # extern crate actix_web; + /// # extern crate env_logger; + /// # extern crate futures; + /// # use std::str; + /// # use actix::*; + /// # use actix_web::*; + /// # use futures::{Future, Stream}; + /// # use futures::future::{ok, result, Either}; + /// fn index(mut req: HttpRequest) -> Box> { + /// req.multipart().from_err() // <- get multipart stream for current request + /// .and_then(|item| match item { // <- iterate over multipart items + /// multipart::MultipartItem::Field(field) => { + /// // Field in turn is stream of *Bytes* object + /// Either::A(field.from_err() + /// .map(|c| println!("-- CHUNK: \n{:?}", str::from_utf8(&c))) + /// .finish()) + /// }, + /// multipart::MultipartItem::Nested(mp) => { + /// // Or item could be nested Multipart stream + /// Either::B(ok(())) + /// } + /// }) + /// .finish() // <- Stream::finish() combinator from actix + /// .map(|_| httpcodes::HTTPOk.into()) + /// .responder() + /// } + /// # fn main() {} + /// ``` + fn multipart(self) -> Multipart + where Self: Stream + Sized + { + let boundary = Multipart::boundary(self.headers()); + Multipart::new(boundary, self) + } +} + +/// Future that resolves to a complete http message body. +pub struct MessageBody { + limit: usize, + req: Option, + fut: Option>>, +} + +impl MessageBody { + + /// Create `RequestBody` for request. + pub fn new(req: T) -> MessageBody { + MessageBody { + limit: 262_144, + req: Some(req), + fut: None, + } + } + + /// Change max size of payload. By default max size is 256Kb + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } +} + +impl Future for MessageBody + where T: HttpMessage + Stream + 'static +{ + type Item = Bytes; + type Error = PayloadError; + + fn poll(&mut self) -> Poll { + if let Some(req) = self.req.take() { + if let Some(len) = req.headers().get(header::CONTENT_LENGTH) { + if let Ok(s) = len.to_str() { + if let Ok(len) = s.parse::() { + if len > self.limit { + return Err(PayloadError::Overflow); + } + } else { + return Err(PayloadError::UnknownLength); + } + } else { + return Err(PayloadError::UnknownLength); + } + } + + // future + let limit = self.limit; + self.fut = Some(Box::new( + req.from_err() + .fold(BytesMut::new(), move |mut body, chunk| { + if (body.len() + chunk.len()) > limit { + Err(PayloadError::Overflow) + } else { + body.extend_from_slice(&chunk); + Ok(body) + } + }) + .map(|body| body.freeze()) + )); + } + + self.fut.as_mut().expect("UrlEncoded could not be used second time").poll() + } +} + +/// Future that resolves to a parsed urlencoded values. +pub struct UrlEncoded { + req: Option, + limit: usize, + fut: Option, Error=UrlencodedError>>>, +} + +impl UrlEncoded { + pub fn new(req: T) -> UrlEncoded { + UrlEncoded { + req: Some(req), + limit: 262_144, + fut: None, + } + } + + /// Change max size of payload. By default max size is 256Kb + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } +} + +impl Future for UrlEncoded + where T: HttpMessage + Stream + 'static +{ + type Item = HashMap; + type Error = UrlencodedError; + + fn poll(&mut self) -> Poll { + if let Some(req) = self.req.take() { + if req.chunked().unwrap_or(false) { + return Err(UrlencodedError::Chunked) + } else if let Some(len) = req.headers().get(header::CONTENT_LENGTH) { + if let Ok(s) = len.to_str() { + if let Ok(len) = s.parse::() { + if len > 262_144 { + return Err(UrlencodedError::Overflow); + } + } else { + return Err(UrlencodedError::UnknownLength) + } + } else { + return Err(UrlencodedError::UnknownLength) + } + } + + // check content type + if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { + return Err(UrlencodedError::ContentType) + } + let encoding = req.encoding().map_err(|_| UrlencodedError::ContentType)?; + + // future + let limit = self.limit; + let fut = req.from_err() + .fold(BytesMut::new(), move |mut body, chunk| { + if (body.len() + chunk.len()) > limit { + Err(UrlencodedError::Overflow) + } else { + body.extend_from_slice(&chunk); + Ok(body) + } + }) + .and_then(move |body| { + let mut m = HashMap::new(); + let parsed = form_urlencoded::parse_with_encoding( + &body, Some(encoding), false).map_err(|_| UrlencodedError::Parse)?; + for (k, v) in parsed { + m.insert(k.into(), v.into()); + } + Ok(m) + }); + self.fut = Some(Box::new(fut)); + } + + self.fut.as_mut().expect("UrlEncoded could not be used second time").poll() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use mime; + use encoding::Encoding; + use encoding::all::ISO_8859_2; + use futures::Async; + use http::{Method, Version, Uri}; + use httprequest::HttpRequest; + use std::str::FromStr; + use std::iter::FromIterator; + use test::TestRequest; + + #[test] + fn test_content_type() { + let req = TestRequest::with_header("content-type", "text/plain").finish(); + assert_eq!(req.content_type(), "text/plain"); + let req = TestRequest::with_header( + "content-type", "application/json; charset=utf=8").finish(); + assert_eq!(req.content_type(), "application/json"); + let req = HttpRequest::default(); + assert_eq!(req.content_type(), ""); + } + + #[test] + fn test_mime_type() { + let req = TestRequest::with_header("content-type", "application/json").finish(); + assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON)); + let req = HttpRequest::default(); + assert_eq!(req.mime_type().unwrap(), None); + let req = TestRequest::with_header( + "content-type", "application/json; charset=utf-8").finish(); + let mt = req.mime_type().unwrap().unwrap(); + assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8)); + assert_eq!(mt.type_(), mime::APPLICATION); + assert_eq!(mt.subtype(), mime::JSON); + } + + #[test] + fn test_mime_type_error() { + let req = TestRequest::with_header( + "content-type", "applicationadfadsfasdflknadsfklnadsfjson").finish(); + assert_eq!(Err(ContentTypeError::ParseError), req.mime_type()); + } + + #[test] + fn test_encoding() { + let req = HttpRequest::default(); + assert_eq!(UTF_8.name(), req.encoding().unwrap().name()); + + let req = TestRequest::with_header( + "content-type", "application/json").finish(); + assert_eq!(UTF_8.name(), req.encoding().unwrap().name()); + + let req = TestRequest::with_header( + "content-type", "application/json; charset=ISO-8859-2").finish(); + assert_eq!(ISO_8859_2.name(), req.encoding().unwrap().name()); + } + + #[test] + fn test_encoding_error() { + let req = TestRequest::with_header( + "content-type", "applicatjson").finish(); + assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err()); + + let req = TestRequest::with_header( + "content-type", "application/json; charset=kkkttktk").finish(); + assert_eq!(Some(ContentTypeError::UnknownEncoding), req.encoding().err()); + } + + #[test] + fn test_no_request_range_header() { + let req = HttpRequest::default(); + let ranges = req.range(100).unwrap(); + assert!(ranges.is_empty()); + } + + #[test] + fn test_request_range_header() { + let req = TestRequest::with_header(header::RANGE, "bytes=0-4").finish(); + let ranges = req.range(100).unwrap(); + assert_eq!(ranges.len(), 1); + assert_eq!(ranges[0].start, 0); + assert_eq!(ranges[0].length, 5); + } + + #[test] + fn test_chunked() { + let req = HttpRequest::default(); + assert!(!req.chunked().unwrap()); + + let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish(); + assert!(req.chunked().unwrap()); + + let mut headers = HeaderMap::new(); + let s = unsafe{str::from_utf8_unchecked(b"some va\xadscc\xacas0xsdasdlue".as_ref())}; + + headers.insert(header::TRANSFER_ENCODING, + header::HeaderValue::from_str(s).unwrap()); + let req = HttpRequest::new( + Method::GET, Uri::from_str("/").unwrap(), + Version::HTTP_11, headers, None); + assert!(req.chunked().is_err()); + } + + impl PartialEq for UrlencodedError { + fn eq(&self, other: &UrlencodedError) -> bool { + match *self { + UrlencodedError::Chunked => match *other { + UrlencodedError::Chunked => true, + _ => false, + }, + UrlencodedError::Overflow => match *other { + UrlencodedError::Overflow => true, + _ => false, + }, + UrlencodedError::UnknownLength => match *other { + UrlencodedError::UnknownLength => true, + _ => false, + }, + UrlencodedError::ContentType => match *other { + UrlencodedError::ContentType => true, + _ => false, + }, + _ => false, + } + } + } + + #[test] + fn test_urlencoded_error() { + let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish(); + assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Chunked); + + let req = TestRequest::with_header( + header::CONTENT_TYPE, "application/x-www-form-urlencoded") + .header(header::CONTENT_LENGTH, "xxxx") + .finish(); + assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::UnknownLength); + + let req = TestRequest::with_header( + header::CONTENT_TYPE, "application/x-www-form-urlencoded") + .header(header::CONTENT_LENGTH, "1000000") + .finish(); + assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Overflow); + + let req = TestRequest::with_header( + header::CONTENT_TYPE, "text/plain") + .header(header::CONTENT_LENGTH, "10") + .finish(); + assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::ContentType); + } + + #[test] + fn test_urlencoded() { + let mut req = TestRequest::with_header( + header::CONTENT_TYPE, "application/x-www-form-urlencoded") + .header(header::CONTENT_LENGTH, "11") + .finish(); + req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); + + let result = req.urlencoded().poll().ok().unwrap(); + assert_eq!(result, Async::Ready( + HashMap::from_iter(vec![("hello".to_owned(), "world".to_owned())]))); + + let mut req = TestRequest::with_header( + header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8") + .header(header::CONTENT_LENGTH, "11") + .finish(); + req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); + + let result = req.urlencoded().poll().ok().unwrap(); + assert_eq!(result, Async::Ready( + HashMap::from_iter(vec![("hello".to_owned(), "world".to_owned())]))); + } + + #[test] + fn test_message_body() { + let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); + match req.body().poll().err().unwrap() { + PayloadError::UnknownLength => (), + _ => panic!("error"), + } + + let req = TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish(); + match req.body().poll().err().unwrap() { + PayloadError::Overflow => (), + _ => panic!("error"), + } + + let mut req = HttpRequest::default(); + req.payload_mut().unread_data(Bytes::from_static(b"test")); + match req.body().poll().ok().unwrap() { + Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")), + _ => panic!("error"), + } + + let mut req = HttpRequest::default(); + req.payload_mut().unread_data(Bytes::from_static(b"11111111111111")); + match req.body().limit(5).poll().err().unwrap() { + PayloadError::Overflow => (), + _ => panic!("error"), + } + } +} diff --git a/src/httprequest.rs b/src/httprequest.rs index ca70b6ed0..b6a5fdcba 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -2,18 +2,11 @@ use std::{io, cmp, str, fmt, mem}; use std::rc::Rc; use std::net::SocketAddr; -use std::collections::HashMap; -use bytes::{Bytes, BytesMut}; +use bytes::Bytes; use cookie::Cookie; -use futures::{Async, Future, Stream, Poll}; -use http_range::HttpRange; -use serde::de::DeserializeOwned; -use mime::Mime; +use futures::{Async, Stream, Poll}; use failure; use url::{Url, form_urlencoded}; -use encoding::all::UTF_8; -use encoding::EncodingRef; -use encoding::label::encoding_from_whatwg_label; use http::{header, Uri, Method, Version, HeaderMap, Extensions}; use tokio_io::AsyncRead; @@ -21,14 +14,12 @@ use info::ConnectionInfo; use param::Params; use router::Router; use payload::Payload; -use json::JsonBody; -use multipart::Multipart; -use helpers::SharedHttpMessage; -use error::{ParseError, ContentTypeError, UrlGenerationError, - CookieParseError, HttpRangeError, PayloadError, UrlencodedError}; +use httpmessage::HttpMessage; +use helpers::SharedHttpInnerMessage; +use error::{UrlGenerationError, CookieParseError, PayloadError}; -pub struct HttpMessage { +pub struct HttpInnerMessage { pub version: Version, pub method: Method, pub uri: Uri, @@ -43,10 +34,10 @@ pub struct HttpMessage { pub info: Option>, } -impl Default for HttpMessage { +impl Default for HttpInnerMessage { - fn default() -> HttpMessage { - HttpMessage { + fn default() -> HttpInnerMessage { + HttpInnerMessage { method: Method::GET, uri: Uri::default(), version: Version::HTTP_11, @@ -63,7 +54,7 @@ impl Default for HttpMessage { } } -impl HttpMessage { +impl HttpInnerMessage { /// Checks if a connection should be kept alive. #[inline] @@ -99,7 +90,7 @@ impl HttpMessage { } /// An HTTP Request -pub struct HttpRequest(SharedHttpMessage, Option>, Option); +pub struct HttpRequest(SharedHttpInnerMessage, Option>, Option); impl HttpRequest<()> { /// Construct a new Request. @@ -108,7 +99,7 @@ impl HttpRequest<()> { version: Version, headers: HeaderMap, payload: Option) -> HttpRequest { HttpRequest( - SharedHttpMessage::from_message(HttpMessage { + SharedHttpInnerMessage::from_message(HttpInnerMessage { method, uri, version, @@ -129,7 +120,7 @@ impl HttpRequest<()> { #[inline(always)] #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] - pub(crate) fn from_message(msg: SharedHttpMessage) -> HttpRequest { + pub(crate) fn from_message(msg: SharedHttpInnerMessage) -> HttpRequest { HttpRequest(msg, None, None) } @@ -146,6 +137,14 @@ impl HttpRequest<()> { } } + +impl HttpMessage for HttpRequest { + #[inline] + fn headers(&self) -> &HeaderMap { + &self.as_ref().headers + } +} + impl HttpRequest { #[inline] @@ -164,18 +163,18 @@ impl HttpRequest { /// mutable reference should not be returned as result for request's method #[inline(always)] #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] - pub(crate) fn as_mut(&self) -> &mut HttpMessage { + pub(crate) fn as_mut(&self) -> &mut HttpInnerMessage { self.0.get_mut() } #[inline(always)] #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] - fn as_ref(&self) -> &HttpMessage { + fn as_ref(&self) -> &HttpInnerMessage { self.0.get_ref() } #[inline] - pub(crate) fn get_inner(&mut self) -> &mut HttpMessage { + pub(crate) fn get_inner(&mut self) -> &mut HttpInnerMessage { self.as_mut() } @@ -219,12 +218,6 @@ impl HttpRequest { self.as_ref().version } - /// Read the Request Headers. - #[inline] - pub fn headers(&self) -> &HeaderMap { - &self.as_ref().headers - } - #[doc(hidden)] #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { @@ -381,51 +374,6 @@ impl HttpRequest { self.as_ref().keep_alive() } - /// Read the request content type. If request does not contain - /// *Content-Type* header, empty str get returned. - pub fn content_type(&self) -> &str { - if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - return content_type.split(';').next().unwrap().trim() - } - } - "" - } - - /// Get content type encoding - /// - /// UTF-8 is used by default, If request charset is not set. - pub fn encoding(&self) -> Result { - if let Some(mime_type) = self.mime_type()? { - if let Some(charset) = mime_type.get_param("charset") { - if let Some(enc) = encoding_from_whatwg_label(charset.as_str()) { - Ok(enc) - } else { - Err(ContentTypeError::UnknownEncoding) - } - } else { - Ok(UTF_8) - } - } else { - Ok(UTF_8) - } - } - - /// Convert the request content type to a known mime type. - pub fn mime_type(&self) -> Result, ContentTypeError> { - if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - return match content_type.parse() { - Ok(mt) => Ok(Some(mt)), - Err(_) => Err(ContentTypeError::ParseError), - }; - } else { - return Err(ContentTypeError::ParseError) - } - } - Ok(None) - } - /// Check if request requires connection upgrade pub(crate) fn upgrade(&self) -> bool { if let Some(conn) = self.as_ref().headers.get(header::CONNECTION) { @@ -436,164 +384,6 @@ impl HttpRequest { self.as_ref().method == Method::CONNECT } - /// Check if request has chunked transfer encoding - pub fn chunked(&self) -> Result { - if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) { - if let Ok(s) = encodings.to_str() { - Ok(s.to_lowercase().contains("chunked")) - } else { - Err(ParseError::Header) - } - } else { - Ok(false) - } - } - - /// Parses Range HTTP header string as per RFC 2616. - /// `size` is full size of response (file). - pub fn range(&self, size: u64) -> Result, HttpRangeError> { - if let Some(range) = self.headers().get(header::RANGE) { - HttpRange::parse(unsafe{str::from_utf8_unchecked(range.as_bytes())}, size) - .map_err(|e| e.into()) - } else { - Ok(Vec::new()) - } - } - - /// Load request body. - /// - /// By default only 256Kb payload reads to a memory, then `BAD REQUEST` - /// http response get returns to a peer. Use `RequestBody::limit()` - /// method to change upper limit. - /// - /// ```rust - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// # #[macro_use] extern crate serde_derive; - /// use actix_web::*; - /// use bytes::Bytes; - /// use futures::future::Future; - /// - /// fn index(mut req: HttpRequest) -> Box> { - /// req.body() // <- get Body future - /// .limit(1024) // <- change max size of the body to a 1kb - /// .from_err() - /// .and_then(|bytes: Bytes| { // <- complete body - /// println!("==== BODY ==== {:?}", bytes); - /// Ok(httpcodes::HTTPOk.into()) - /// }).responder() - /// } - /// # fn main() {} - /// ``` - pub fn body(self) -> RequestBody { - RequestBody::new(self.without_state()) - } - - /// Return stream to http payload processes as multipart. - /// - /// Content-type: multipart/form-data; - /// - /// ```rust - /// # extern crate actix; - /// # extern crate actix_web; - /// # extern crate env_logger; - /// # extern crate futures; - /// # use std::str; - /// # use actix::*; - /// # use actix_web::*; - /// # use futures::{Future, Stream}; - /// # use futures::future::{ok, result, Either}; - /// fn index(mut req: HttpRequest) -> Box> { - /// req.multipart().from_err() // <- get multipart stream for current request - /// .and_then(|item| match item { // <- iterate over multipart items - /// multipart::MultipartItem::Field(field) => { - /// // Field in turn is stream of *Bytes* object - /// Either::A(field.from_err() - /// .map(|c| println!("-- CHUNK: \n{:?}", str::from_utf8(&c))) - /// .finish()) - /// }, - /// multipart::MultipartItem::Nested(mp) => { - /// // Or item could be nested Multipart stream - /// Either::B(ok(())) - /// } - /// }) - /// .finish() // <- Stream::finish() combinator from actix - /// .map(|_| httpcodes::HTTPOk.into()) - /// .responder() - /// } - /// # fn main() {} - /// ``` - pub fn multipart(self) -> Multipart> { - let boundary = Multipart::boundary(self.headers()); - Multipart::new(boundary, self) - } - - /// Parse `application/x-www-form-urlencoded` encoded body. - /// Return `UrlEncoded` future. It resolves to a `HashMap` which - /// contains decoded parameters. - /// - /// Returns error: - /// - /// * content type is not `application/x-www-form-urlencoded` - /// * transfer encoding is `chunked`. - /// * content-length is greater than 256k - /// - /// ```rust - /// # extern crate actix_web; - /// # extern crate futures; - /// use actix_web::*; - /// use futures::future::{Future, ok}; - /// - /// fn index(mut req: HttpRequest) -> Box> { - /// req.urlencoded() // <- get UrlEncoded future - /// .from_err() - /// .and_then(|params| { // <- url encoded parameters - /// println!("==== BODY ==== {:?}", params); - /// ok(httpcodes::HTTPOk.into()) - /// }) - /// .responder() - /// } - /// # fn main() {} - /// ``` - pub fn urlencoded(self) -> UrlEncoded { - UrlEncoded::new(self.without_state()) - } - - /// Parse `application/json` encoded body. - /// Return `JsonBody` future. It resolves to a `T` value. - /// - /// Returns error: - /// - /// * content type is not `application/json` - /// * content length is greater than 256k - /// - /// ```rust - /// # extern crate actix_web; - /// # extern crate futures; - /// # #[macro_use] extern crate serde_derive; - /// use actix_web::*; - /// use futures::future::{Future, ok}; - /// - /// #[derive(Deserialize, Debug)] - /// struct MyObj { - /// name: String, - /// } - /// - /// fn index(mut req: HttpRequest) -> Box> { - /// req.json() // <- get JsonBody future - /// .from_err() - /// .and_then(|val: MyObj| { // <- deserialized value - /// println!("==== BODY ==== {:?}", val); - /// Ok(httpcodes::HTTPOk.into()) - /// }).responder() - /// } - /// # fn main() {} - /// ``` - pub fn json(self) -> JsonBody { - JsonBody::from_request(self) - } - #[cfg(test)] pub(crate) fn payload(&self) -> &Payload { let msg = self.as_mut(); @@ -617,7 +407,7 @@ impl Default for HttpRequest<()> { /// Construct default request fn default() -> HttpRequest { - HttpRequest(SharedHttpMessage::default(), None, None) + HttpRequest(SharedHttpInnerMessage::default(), None, None) } } @@ -700,158 +490,10 @@ impl fmt::Debug for HttpRequest { } } -/// Future that resolves to a parsed urlencoded values. -pub struct UrlEncoded { - req: Option>, - limit: usize, - fut: Option, Error=UrlencodedError>>>, -} - -impl UrlEncoded { - pub fn new(req: HttpRequest) -> UrlEncoded { - UrlEncoded { - req: Some(req), - limit: 262_144, - fut: None, - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } -} - -impl Future for UrlEncoded { - type Item = HashMap; - type Error = UrlencodedError; - - fn poll(&mut self) -> Poll { - if let Some(req) = self.req.take() { - if req.chunked().unwrap_or(false) { - return Err(UrlencodedError::Chunked) - } else if let Some(len) = req.headers().get(header::CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - if len > 262_144 { - return Err(UrlencodedError::Overflow); - } - } else { - return Err(UrlencodedError::UnknownLength) - } - } else { - return Err(UrlencodedError::UnknownLength) - } - } - - // check content type - if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { - return Err(UrlencodedError::ContentType) - } - let encoding = req.encoding().map_err(|_| UrlencodedError::ContentType)?; - - // future - let limit = self.limit; - let fut = req.from_err() - .fold(BytesMut::new(), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(UrlencodedError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }) - .and_then(move |body| { - let mut m = HashMap::new(); - let parsed = form_urlencoded::parse_with_encoding( - &body, Some(encoding), false).map_err(|_| UrlencodedError::Parse)?; - for (k, v) in parsed { - m.insert(k.into(), v.into()); - } - Ok(m) - }); - self.fut = Some(Box::new(fut)); - } - - self.fut.as_mut().expect("UrlEncoded could not be used second time").poll() - } -} - -/// Future that resolves to a complete request body. -pub struct RequestBody { - limit: usize, - req: Option>, - fut: Option>>, -} - -impl RequestBody { - - /// Create `RequestBody` for request. - pub fn new(req: HttpRequest) -> RequestBody { - RequestBody { - limit: 262_144, - req: Some(req), - fut: None, - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } -} - -impl Future for RequestBody { - type Item = Bytes; - type Error = PayloadError; - - fn poll(&mut self) -> Poll { - if let Some(req) = self.req.take() { - if let Some(len) = req.headers().get(header::CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - if len > self.limit { - return Err(PayloadError::Overflow); - } - } else { - return Err(PayloadError::UnknownLength); - } - } else { - return Err(PayloadError::UnknownLength); - } - } - - // future - let limit = self.limit; - self.fut = Some(Box::new( - req.from_err() - .fold(BytesMut::new(), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(PayloadError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }) - .map(|body| body.freeze()) - )); - } - - self.fut.as_mut().expect("UrlEncoded could not be used second time").poll() - } -} - #[cfg(test)] mod tests { use super::*; - use mime; - use encoding::Encoding; - use encoding::all::ISO_8859_2; use http::{Uri, HttpTryFrom}; - use std::str::FromStr; - use std::iter::FromIterator; use router::Pattern; use resource::Resource; use test::TestRequest; @@ -864,63 +506,6 @@ mod tests { assert!(dbg.contains("HttpRequest")); } - #[test] - fn test_content_type() { - let req = TestRequest::with_header("content-type", "text/plain").finish(); - assert_eq!(req.content_type(), "text/plain"); - let req = TestRequest::with_header( - "content-type", "application/json; charset=utf=8").finish(); - assert_eq!(req.content_type(), "application/json"); - let req = HttpRequest::default(); - assert_eq!(req.content_type(), ""); - } - - #[test] - fn test_mime_type() { - let req = TestRequest::with_header("content-type", "application/json").finish(); - assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON)); - let req = HttpRequest::default(); - assert_eq!(req.mime_type().unwrap(), None); - let req = TestRequest::with_header( - "content-type", "application/json; charset=utf-8").finish(); - let mt = req.mime_type().unwrap().unwrap(); - assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8)); - assert_eq!(mt.type_(), mime::APPLICATION); - assert_eq!(mt.subtype(), mime::JSON); - } - - #[test] - fn test_mime_type_error() { - let req = TestRequest::with_header( - "content-type", "applicationadfadsfasdflknadsfklnadsfjson").finish(); - assert_eq!(Err(ContentTypeError::ParseError), req.mime_type()); - } - - #[test] - fn test_encoding() { - let req = HttpRequest::default(); - assert_eq!(UTF_8.name(), req.encoding().unwrap().name()); - - let req = TestRequest::with_header( - "content-type", "application/json").finish(); - assert_eq!(UTF_8.name(), req.encoding().unwrap().name()); - - let req = TestRequest::with_header( - "content-type", "application/json; charset=ISO-8859-2").finish(); - assert_eq!(ISO_8859_2.name(), req.encoding().unwrap().name()); - } - - #[test] - fn test_encoding_error() { - let req = TestRequest::with_header( - "content-type", "applicatjson").finish(); - assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err()); - - let req = TestRequest::with_header( - "content-type", "application/json; charset=kkkttktk").finish(); - assert_eq!(Some(ContentTypeError::UnknownEncoding), req.encoding().err()); - } - #[test] fn test_uri_mut() { let mut req = HttpRequest::default(); @@ -958,22 +543,6 @@ mod tests { assert!(cookie.is_none()); } - #[test] - fn test_no_request_range_header() { - let req = HttpRequest::default(); - let ranges = req.range(100).unwrap(); - assert!(ranges.is_empty()); - } - - #[test] - fn test_request_range_header() { - let req = TestRequest::with_header(header::RANGE, "bytes=0-4").finish(); - let ranges = req.range(100).unwrap(); - assert_eq!(ranges.len(), 1); - assert_eq!(ranges[0].start, 0); - assert_eq!(ranges[0].length, 5); - } - #[test] fn test_request_query() { let req = TestRequest::with_uri("/?id=test").finish(); @@ -996,125 +565,6 @@ mod tests { assert_eq!(req.match_info().get("key"), Some("value")); } - #[test] - fn test_chunked() { - let req = HttpRequest::default(); - assert!(!req.chunked().unwrap()); - - let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish(); - assert!(req.chunked().unwrap()); - - let mut headers = HeaderMap::new(); - let s = unsafe{str::from_utf8_unchecked(b"some va\xadscc\xacas0xsdasdlue".as_ref())}; - - headers.insert(header::TRANSFER_ENCODING, - header::HeaderValue::from_str(s).unwrap()); - let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, None); - assert!(req.chunked().is_err()); - } - - impl PartialEq for UrlencodedError { - fn eq(&self, other: &UrlencodedError) -> bool { - match *self { - UrlencodedError::Chunked => match *other { - UrlencodedError::Chunked => true, - _ => false, - }, - UrlencodedError::Overflow => match *other { - UrlencodedError::Overflow => true, - _ => false, - }, - UrlencodedError::UnknownLength => match *other { - UrlencodedError::UnknownLength => true, - _ => false, - }, - UrlencodedError::ContentType => match *other { - UrlencodedError::ContentType => true, - _ => false, - }, - _ => false, - } - } - } - - #[test] - fn test_urlencoded_error() { - let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish(); - assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Chunked); - - let req = TestRequest::with_header( - header::CONTENT_TYPE, "application/x-www-form-urlencoded") - .header(header::CONTENT_LENGTH, "xxxx") - .finish(); - assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::UnknownLength); - - let req = TestRequest::with_header( - header::CONTENT_TYPE, "application/x-www-form-urlencoded") - .header(header::CONTENT_LENGTH, "1000000") - .finish(); - assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Overflow); - - let req = TestRequest::with_header( - header::CONTENT_TYPE, "text/plain") - .header(header::CONTENT_LENGTH, "10") - .finish(); - assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::ContentType); - } - - #[test] - fn test_urlencoded() { - let mut req = TestRequest::with_header( - header::CONTENT_TYPE, "application/x-www-form-urlencoded") - .header(header::CONTENT_LENGTH, "11") - .finish(); - req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); - - let result = req.urlencoded().poll().ok().unwrap(); - assert_eq!(result, Async::Ready( - HashMap::from_iter(vec![("hello".to_owned(), "world".to_owned())]))); - - let mut req = TestRequest::with_header( - header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8") - .header(header::CONTENT_LENGTH, "11") - .finish(); - req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); - - let result = req.urlencoded().poll().ok().unwrap(); - assert_eq!(result, Async::Ready( - HashMap::from_iter(vec![("hello".to_owned(), "world".to_owned())]))); -} - - #[test] - fn test_request_body() { - let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); - match req.body().poll().err().unwrap() { - PayloadError::UnknownLength => (), - _ => panic!("error"), - } - - let req = TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish(); - match req.body().poll().err().unwrap() { - PayloadError::Overflow => (), - _ => panic!("error"), - } - - let mut req = HttpRequest::default(); - req.payload_mut().unread_data(Bytes::from_static(b"test")); - match req.body().poll().ok().unwrap() { - Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")), - _ => panic!("error"), - } - - let mut req = HttpRequest::default(); - req.payload_mut().unread_data(Bytes::from_static(b"11111111111111")); - match req.body().limit(5).poll().err().unwrap() { - PayloadError::Overflow => (), - _ => panic!("error"), - } - } - #[test] fn test_url_for() { let req = TestRequest::with_header(header::HOST, "www.rust-lang.org") diff --git a/src/info.rs b/src/info.rs index 45bd4fe6a..6177cd021 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,5 +1,6 @@ use std::str::FromStr; use http::header::{self, HeaderName}; +use httpmessage::HttpMessage; use httprequest::HttpRequest; const X_FORWARDED_FOR: &str = "X-FORWARDED-FOR"; diff --git a/src/json.rs b/src/json.rs index 341bc32dd..56b2a46aa 100644 --- a/src/json.rs +++ b/src/json.rs @@ -1,4 +1,4 @@ -use bytes::BytesMut; +use bytes::{Bytes, BytesMut}; use futures::{Poll, Future, Stream}; use http::header::CONTENT_LENGTH; @@ -6,8 +6,9 @@ use serde_json; use serde::Serialize; use serde::de::DeserializeOwned; -use error::{Error, JsonPayloadError}; +use error::{Error, JsonPayloadError, PayloadError}; use handler::Responder; +use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -54,6 +55,9 @@ impl Responder for Json { /// * content type is not `application/json` /// * content length is greater than 256k /// +/// +/// # Server example +/// /// ```rust /// # extern crate actix_web; /// # extern crate futures; @@ -76,17 +80,17 @@ impl Responder for Json { /// } /// # fn main() {} /// ``` -pub struct JsonBody{ +pub struct JsonBody{ limit: usize, ct: &'static str, - req: Option>, - fut: Option>>, + req: Option, + fut: Option>>, } -impl JsonBody { +impl JsonBody { /// Create `JsonBody` for request. - pub fn from_request(req: HttpRequest) -> Self { + pub fn new(req: T) -> Self { JsonBody{ limit: 262_144, req: Some(req), @@ -111,11 +115,13 @@ impl JsonBody { } } -impl Future for JsonBody { - type Item = T; +impl Future for JsonBody + where T: HttpMessage + Stream + 'static +{ + type Item = U; type Error = JsonPayloadError; - fn poll(&mut self) -> Poll { + fn poll(&mut self) -> Poll { if let Some(req) = self.req.take() { if let Some(len) = req.headers().get(CONTENT_LENGTH) { if let Ok(s) = len.to_str() { @@ -143,7 +149,7 @@ impl Future for JsonBody { Ok(body) } }) - .and_then(|body| Ok(serde_json::from_slice::(&body)?)); + .and_then(|body| Ok(serde_json::from_slice::(&body)?)); self.fut = Some(Box::new(fut)); } diff --git a/src/lib.rs b/src/lib.rs index 8177dd759..05127e1f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -103,6 +103,7 @@ mod body; mod context; mod handler; mod helpers; +mod httpmessage; mod httprequest; mod httpresponse; mod info; @@ -128,6 +129,7 @@ pub use error::{Error, Result, ResponseError}; pub use body::{Body, Binary}; pub use json::Json; pub use application::Application; +pub use httpmessage::HttpMessage; pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; pub use handler::{Reply, Responder, NormalizePath, AsyncResponder}; @@ -191,6 +193,6 @@ pub mod dev { pub use json::JsonBody; pub use router::{Router, Pattern}; pub use param::{FromParam, Params}; - pub use httprequest::{UrlEncoded, RequestBody}; + pub use httpmessage::{UrlEncoded, MessageBody}; pub use httpresponse::HttpResponseBuilder; } diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 748ab1bba..c949bcc49 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -55,6 +55,7 @@ use http::header::{self, HeaderName, HeaderValue}; use error::{Result, ResponseError}; use resource::Resource; +use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; use httpcodes::{HTTPOk, HTTPBadRequest}; diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 4907b214c..f5f2e270b 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -8,6 +8,7 @@ use time; use regex::Regex; use error::Result; +use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Middleware, Started, Finished}; diff --git a/src/pred.rs b/src/pred.rs index 47d906fb0..c84325eef 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -3,6 +3,7 @@ use std::marker::PhantomData; use http; use http::{header, HttpTryFrom}; +use httpmessage::HttpMessage; use httprequest::HttpRequest; /// Trait defines resource route predicate. diff --git a/src/server/encoding.rs b/src/server/encoding.rs index c666b7232..694d63a1d 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -16,7 +16,7 @@ use bytes::{Bytes, BytesMut, BufMut, Writer}; use headers::ContentEncoding; use body::{Body, Binary}; use error::PayloadError; -use httprequest::HttpMessage; +use httprequest::HttpInnerMessage; use httpresponse::HttpResponse; use payload::{PayloadSender, PayloadWriter}; @@ -371,7 +371,7 @@ impl ContentEncoder { } pub fn for_server(buf: SharedBytes, - req: &HttpMessage, + req: &HttpInnerMessage, resp: &mut HttpResponse, response_encoding: ContentEncoding) -> ContentEncoder { diff --git a/src/server/h1.rs b/src/server/h1.rs index cb24e6d0f..8fb3a9e97 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -860,6 +860,7 @@ mod tests { use http::{Version, Method}; use super::*; + use httpmessage::HttpMessage; use application::HttpApplication; use server::settings::WorkerSettings; use server::IoStream; diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index da60e220c..80d02f292 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -10,7 +10,7 @@ use http::header::{HeaderValue, CONNECTION, DATE}; use helpers; use body::{Body, Binary}; use headers::ContentEncoding; -use httprequest::HttpMessage; +use httprequest::HttpInnerMessage; use httpresponse::HttpResponse; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; use super::shared::SharedBytes; @@ -98,7 +98,7 @@ impl Writer for H1Writer { } fn start(&mut self, - req: &mut HttpMessage, + req: &mut HttpInnerMessage, msg: &mut HttpResponse, encoding: ContentEncoding) -> io::Result { diff --git a/src/server/h2.rs b/src/server/h2.rs index 5dfcb57ad..97974c88e 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -19,6 +19,7 @@ use tokio_core::reactor::Timeout; use pipeline::Pipeline; use error::PayloadError; use httpcodes::HTTPNotFound; +use httpmessage::HttpMessage; use httprequest::HttpRequest; use payload::{Payload, PayloadWriter}; diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index 29b534671..095cd78f2 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -11,7 +11,7 @@ use http::header::{HeaderValue, CONNECTION, TRANSFER_ENCODING, DATE, CONTENT_LEN use helpers; use body::{Body, Binary}; use headers::ContentEncoding; -use httprequest::HttpMessage; +use httprequest::HttpInnerMessage; use httpresponse::HttpResponse; use super::encoding::ContentEncoder; use super::shared::SharedBytes; @@ -111,7 +111,7 @@ impl Writer for H2Writer { self.written } - fn start(&mut self, req: &mut HttpMessage, msg: &mut HttpResponse, encoding: ContentEncoding) + fn start(&mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse, encoding: ContentEncoding) -> io::Result { // prepare response self.flags.insert(Flags::STARTED); diff --git a/src/server/mod.rs b/src/server/mod.rs index 3769e588e..9f644a1e9 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -25,7 +25,7 @@ pub use self::settings::ServerSettings; use body::Binary; use error::Error; use headers::ContentEncoding; -use httprequest::{HttpMessage, HttpRequest}; +use httprequest::{HttpInnerMessage, HttpRequest}; use httpresponse::HttpResponse; /// max buffer size 64k @@ -103,7 +103,7 @@ pub enum WriterState { pub trait Writer { fn written(&self) -> u64; - fn start(&mut self, req: &mut HttpMessage, resp: &mut HttpResponse, encoding: ContentEncoding) + fn start(&mut self, req: &mut HttpInnerMessage, resp: &mut HttpResponse, encoding: ContentEncoding) -> io::Result; fn write(&mut self, payload: Binary) -> io::Result; diff --git a/src/server/settings.rs b/src/server/settings.rs index 33e6fa8d1..50be1f7e7 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -103,8 +103,8 @@ impl WorkerSettings { SharedBytes::new(self.bytes.get_bytes(), Rc::clone(&self.bytes)) } - pub fn get_http_message(&self) -> helpers::SharedHttpMessage { - helpers::SharedHttpMessage::new(self.messages.get(), Rc::clone(&self.messages)) + pub fn get_http_message(&self) -> helpers::SharedHttpInnerMessage { + helpers::SharedHttpInnerMessage::new(self.messages.get(), Rc::clone(&self.messages)) } pub fn add_channel(&self) { diff --git a/src/ws/client.rs b/src/ws/client.rs index 7b7a07419..152391465 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -19,6 +19,7 @@ use actix::prelude::*; use body::{Body, Binary}; use error::UrlParseError; use payload::PayloadHelper; +use httpmessage::HttpMessage; use client::{ClientRequest, ClientRequestBuilder, ClientResponse, ClientConnector, SendRequest, SendRequestError, diff --git a/src/ws/mod.rs b/src/ws/mod.rs index b2f0da3c1..90b2558b7 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -52,6 +52,7 @@ use actix::{Actor, AsyncContext, StreamHandler}; use body::Binary; use payload::PayloadHelper; use error::{Error, PayloadError, ResponseError}; +use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder}; use httpcodes::{HTTPBadRequest, HTTPMethodNotAllowed}; From 33c935dccc5b2093b074209602616410a9ea364d Mon Sep 17 00:00:00 2001 From: Mustafa Paltun Date: Wed, 28 Feb 2018 01:13:59 +0200 Subject: [PATCH 23/84] Fix typos in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 12426d083..19009e41c 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ fn main() { * [Basics](https://github.com/actix/actix-web/tree/master/examples/basics/) * [Stateful](https://github.com/actix/actix-web/tree/master/examples/state/) -* [Mulitpart streams](https://github.com/actix/actix-web/tree/master/examples/multipart/) +* [Multipart streams](https://github.com/actix/actix-web/tree/master/examples/multipart/) * [Simple websocket session](https://github.com/actix/actix-web/tree/master/examples/websocket/) * [Tera templates](https://github.com/actix/actix-web/tree/master/examples/template_tera/) * [Diesel integration](https://github.com/actix/actix-web/tree/master/examples/diesel/) @@ -61,7 +61,7 @@ fn main() { * [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=plaintext) -* Some basic benchmarks could be found in this [respository](https://github.com/fafhrd91/benchmarks). +* Some basic benchmarks could be found in this [repository](https://github.com/fafhrd91/benchmarks). ## License From 1f063e4136fdb5a06b92a8f751713a92ce5745a9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 27 Feb 2018 15:14:33 -0800 Subject: [PATCH 24/84] move with_connector method to ClientRequestBuilder --- src/client/request.rs | 45 +++++++++++++++++++++++++++++++------------ src/ws/client.rs | 8 ++++---- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/client/request.rs b/src/client/request.rs index 392ef6b9d..92960d7fe 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -27,6 +27,14 @@ pub struct ClientRequest { encoding: ContentEncoding, response_decompress: bool, buffer_capacity: Option<(usize, usize)>, + conn: ConnectionType, + +} + +enum ConnectionType { + Default, + Connector(Addr), + Connection(Connection), } impl Default for ClientRequest { @@ -43,6 +51,7 @@ impl Default for ClientRequest { encoding: ContentEncoding::Auto, response_decompress: true, buffer_capacity: None, + conn: ConnectionType::Default, } } } @@ -190,18 +199,14 @@ impl ClientRequest { } /// Send request - pub fn send(self) -> SendRequest { - SendRequest::new(self) - } - - /// Send request using custom connector - pub fn with_connector(self, conn: Addr) -> SendRequest { - SendRequest::with_connector(self, conn) - } - - /// Send request using existing Connection - pub fn with_connection(self, conn: Connection) -> SendRequest { - SendRequest::with_connection(self, conn) + /// + /// This method returns future that resolves to a ClientResponse + pub fn send(mut self) -> SendRequest { + match mem::replace(&mut self.conn, ConnectionType::Default) { + ConnectionType::Default => SendRequest::new(self), + ConnectionType::Connector(conn) => SendRequest::with_connector(self, conn), + ConnectionType::Connection(conn) => SendRequest::with_connection(self, conn), + } } } @@ -451,6 +456,22 @@ impl ClientRequestBuilder { self } + /// Send request using custom connector + pub fn with_connector(&mut self, conn: Addr) -> &mut Self { + if let Some(parts) = parts(&mut self.request, &self.err) { + parts.conn = ConnectionType::Connector(conn); + } + self + } + + /// Send request using existing Connection + pub fn with_connection(&mut self, conn: Connection) -> &mut Self { + if let Some(parts) = parts(&mut self.request, &self.err) { + parts.conn = ConnectionType::Connection(conn); + } + self + } + /// This method calls provided closure with builder reference if value is true. pub fn if_true(&mut self, value: bool, f: F) -> &mut Self where F: FnOnce(&mut ClientRequestBuilder) diff --git a/src/ws/client.rs b/src/ws/client.rs index 152391465..bd5d8f8b1 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -194,6 +194,7 @@ impl WsClient { self.request.set_header(header::UPGRADE, "websocket"); self.request.set_header(header::CONNECTION, "upgrade"); self.request.set_header("SEC-WEBSOCKET-VERSION", "13"); + self.request.with_connector(self.conn.clone()); if let Some(protocols) = self.protocols.take() { self.request.set_header("SEC-WEBSOCKET-PROTOCOL", protocols.as_str()); @@ -215,7 +216,7 @@ impl WsClient { } // start handshake - WsClientHandshake::new(request, &self.conn, self.max_size) + WsClientHandshake::new(request, self.max_size) } } } @@ -235,8 +236,7 @@ pub struct WsClientHandshake { } impl WsClientHandshake { - fn new(mut request: ClientRequest, - conn: &Addr, max_size: usize) -> WsClientHandshake + fn new(mut request: ClientRequest, max_size: usize) -> WsClientHandshake { // Generate a random key for the `Sec-WebSocket-Key` header. // a base64-encoded (see Section 4 of [RFC4648]) value that, @@ -256,7 +256,7 @@ impl WsClientHandshake { WsClientHandshake { key, max_size, - request: Some(request.with_connector(conn.clone())), + request: Some(request.send()), tx: Some(tx), error: None, } From 4f99cd1580c417db77b02c69f5c4321afd4a777d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 27 Feb 2018 15:38:57 -0800 Subject: [PATCH 25/84] add ws error tracing --- src/ws/client.rs | 47 +++++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/src/ws/client.rs b/src/ws/client.rs index bd5d8f8b1..587d60a65 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -36,13 +36,17 @@ pub enum WsClientError { #[fail(display="Invalid url")] InvalidUrl, #[fail(display="Invalid response status")] - InvalidResponseStatus, + InvalidResponseStatus(StatusCode), #[fail(display="Invalid upgrade header")] InvalidUpgradeHeader, #[fail(display="Invalid connection header")] - InvalidConnectionHeader, + InvalidConnectionHeader(HeaderValue), + #[fail(display="Missing CONNECTION header")] + MissingConnectionHeader, + #[fail(display="Missing SEC-WEBSOCKET-ACCEPT header")] + MissingWebSocketAcceptHeader, #[fail(display="Invalid challenge response")] - InvalidChallengeResponse, + InvalidChallengeResponse(String, HeaderValue), #[fail(display="Http parsing error")] Http(HttpError), #[fail(display="Url parsing error")] @@ -292,7 +296,7 @@ impl Future for WsClientHandshake { // verify response if resp.status() != StatusCode::SWITCHING_PROTOCOLS { - return Err(WsClientError::InvalidResponseStatus) + return Err(WsClientError::InvalidResponseStatus(resp.status())) } // Check for "UPGRADE" to websocket header let has_hdr = if let Some(hdr) = resp.headers().get(header::UPGRADE) { @@ -305,19 +309,26 @@ impl Future for WsClientHandshake { false }; if !has_hdr { + trace!("Invalid upgrade header"); return Err(WsClientError::InvalidUpgradeHeader) } // Check for "CONNECTION" header - let has_hdr = if let Some(conn) = resp.headers().get(header::CONNECTION) { + if let Some(conn) = resp.headers().get(header::CONNECTION) { if let Ok(s) = conn.to_str() { - s.to_lowercase().contains("upgrade") - } else { false } - } else { false }; - if !has_hdr { - return Err(WsClientError::InvalidConnectionHeader) + if !s.to_lowercase().contains("upgrade") { + trace!("Invalid connection header: {}", s); + return Err(WsClientError::InvalidConnectionHeader(conn.clone())) + } + } else { + trace!("Invalid connection header: {:?}", conn); + return Err(WsClientError::InvalidConnectionHeader(conn.clone())) + } + } else { + trace!("Missing connection header"); + return Err(WsClientError::MissingConnectionHeader) } - let match_key = if let Some(key) = resp.headers().get( + if let Some(key) = resp.headers().get( HeaderName::try_from("SEC-WEBSOCKET-ACCEPT").unwrap()) { // field is constructed by concatenating /key/ @@ -326,13 +337,17 @@ impl Future for WsClientHandshake { let mut sha1 = Sha1::new(); sha1.update(self.key.as_ref()); sha1.update(WS_GUID); - key.as_bytes() == base64::encode(&sha1.digest().bytes()).as_bytes() + let encoded = base64::encode(&sha1.digest().bytes()); + if key.as_bytes() != encoded.as_bytes() { + trace!( + "Invalid challenge response: expected: {} received: {:?}", + encoded, key); + return Err(WsClientError::InvalidChallengeResponse(encoded, key.clone())); + } } else { - false + trace!("Missing SEC-WEBSOCKET-ACCEPT header"); + return Err(WsClientError::MissingWebSocketAcceptHeader) }; - if !match_key { - return Err(WsClientError::InvalidChallengeResponse) - } let inner = WsInner { tx: self.tx.take().unwrap(), From e2c8f17c2cb03494e13c0ca1586962e4ad189f2c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 27 Feb 2018 16:08:57 -0800 Subject: [PATCH 26/84] drop connection if handler get dropped without consuming payload --- src/client/response.rs | 12 ------------ src/payload.rs | 21 +++++++++++++++++---- src/server/encoding.rs | 6 +++--- src/server/h1.rs | 17 ++++++++++------- src/server/h2.rs | 9 +++++---- 5 files changed, 35 insertions(+), 30 deletions(-) diff --git a/src/client/response.rs b/src/client/response.rs index 0f997dcda..392c91332 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -71,24 +71,12 @@ impl ClientResponse { self.as_ref().version } - /// Get a mutable reference to the headers. - #[inline] - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.as_mut().headers - } - /// Get the status from the server. #[inline] pub fn status(&self) -> StatusCode { self.as_ref().status } - /// Set the `StatusCode` for this response. - #[inline] - pub fn set_status(&mut self, status: StatusCode) { - self.as_mut().status = status - } - /// Load request cookies. pub fn cookies(&self) -> Result<&Vec>, CookieParseError> { if self.as_ref().cookies.is_none() { diff --git a/src/payload.rs b/src/payload.rs index 4fb80b0bc..3c0f41532 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -8,6 +8,13 @@ use futures::{Async, Poll, Stream}; use error::PayloadError; +#[derive(Debug, PartialEq)] +pub(crate) enum PayloadStatus { + Read, + Pause, + Dropped, +} + /// Buffered stream of bytes chunks /// /// Payload stores chunks in a vector. First chunk can be received with `.readany()` method. @@ -100,7 +107,7 @@ pub(crate) trait PayloadWriter { fn feed_data(&mut self, data: Bytes); /// Need read data - fn need_read(&self) -> bool; + fn need_read(&self) -> PayloadStatus; } /// Sender part of the payload stream @@ -129,11 +136,17 @@ impl PayloadWriter for PayloadSender { } #[inline] - fn need_read(&self) -> bool { + fn need_read(&self) -> PayloadStatus { + // we check need_read only if Payload (other side) is alive, + // otherwise always return true (consume payload) if let Some(shared) = self.inner.upgrade() { - shared.borrow().need_read + if shared.borrow().need_read { + PayloadStatus::Read + } else { + PayloadStatus::Pause + } } else { - false + PayloadStatus::Dropped } } } diff --git a/src/server/encoding.rs b/src/server/encoding.rs index 694d63a1d..5e0ac7b0f 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -18,7 +18,7 @@ use body::{Body, Binary}; use error::PayloadError; use httprequest::HttpInnerMessage; use httpresponse::HttpResponse; -use payload::{PayloadSender, PayloadWriter}; +use payload::{PayloadSender, PayloadWriter, PayloadStatus}; use super::shared::SharedBytes; @@ -120,7 +120,7 @@ impl PayloadWriter for PayloadType { } #[inline] - fn need_read(&self) -> bool { + fn need_read(&self) -> PayloadStatus { match *self { PayloadType::Sender(ref sender) => sender.need_read(), PayloadType::Encoding(ref enc) => enc.need_read(), @@ -352,7 +352,7 @@ impl PayloadWriter for EncodedPayload { } #[inline] - fn need_read(&self) -> bool { + fn need_read(&self) -> PayloadStatus { self.inner.need_read() } } diff --git a/src/server/h1.rs b/src/server/h1.rs index 8fb3a9e97..11c0bb2d4 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -18,7 +18,7 @@ use pipeline::Pipeline; use httpcodes::HTTPNotFound; use httprequest::HttpRequest; use error::{ParseError, PayloadError, ResponseError}; -use payload::{Payload, PayloadWriter}; +use payload::{Payload, PayloadWriter, PayloadStatus}; use super::{utils, Writer}; use super::h1writer::H1Writer; @@ -190,7 +190,7 @@ impl Http1 true }; - let retry = self.reader.need_read(); + let retry = self.reader.need_read() == PayloadStatus::Read; loop { // check in-flight messages @@ -227,7 +227,7 @@ impl Http1 }, // no more IO for this iteration Ok(Async::NotReady) => { - if self.reader.need_read() && !retry { + if self.reader.need_read() == PayloadStatus::Read && !retry { return Ok(Async::Ready(true)); } io = true; @@ -341,6 +341,7 @@ struct PayloadInfo { enum ReaderError { Disconnect, Payload, + PayloadDropped, Error(ParseError), } @@ -352,11 +353,11 @@ impl Reader { } #[inline] - fn need_read(&self) -> bool { + fn need_read(&self) -> PayloadStatus { if let Some(ref info) = self.payload { info.tx.need_read() } else { - true + PayloadStatus::Read } } @@ -392,8 +393,10 @@ impl Reader { settings: &WorkerSettings) -> Poll where T: IoStream { - if !self.need_read() { - return Ok(Async::NotReady) + match self.need_read() { + PayloadStatus::Read => (), + PayloadStatus::Pause => return Ok(Async::NotReady), + PayloadStatus::Dropped => return Err(ReaderError::PayloadDropped), } // read payload diff --git a/src/server/h2.rs b/src/server/h2.rs index 97974c88e..ed75c97f3 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -21,7 +21,7 @@ use error::PayloadError; use httpcodes::HTTPNotFound; use httpmessage::HttpMessage; use httprequest::HttpRequest; -use payload::{Payload, PayloadWriter}; +use payload::{Payload, PayloadWriter, PayloadStatus}; use super::h2writer::H2Writer; use super::encoding::PayloadType; @@ -105,7 +105,7 @@ impl Http2 item.poll_payload(); if !item.flags.contains(EntryFlags::EOF) { - let retry = item.payload.need_read(); + let retry = item.payload.need_read() == PayloadStatus::Read; loop { match item.task.poll_io(&mut item.stream) { Ok(Async::Ready(ready)) => { @@ -116,7 +116,8 @@ impl Http2 not_ready = false; }, Ok(Async::NotReady) => { - if item.payload.need_read() && !retry { + if item.payload.need_read() == PayloadStatus::Read && !retry + { continue } }, @@ -307,7 +308,7 @@ impl Entry { fn poll_payload(&mut self) { if !self.flags.contains(EntryFlags::REOF) { - if self.payload.need_read() { + if self.payload.need_read() == PayloadStatus::Read { if let Err(err) = self.recv.release_capacity().release_capacity(32_768) { self.payload.set_error(PayloadError::Http2(err)) } From 1e2aa4fc9062c608f40aa98c0807f660bb8a8457 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 27 Feb 2018 18:05:06 -0800 Subject: [PATCH 27/84] mark context as modified after writing data --- src/context.rs | 1 + src/ws/context.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/context.rs b/src/context.rs index 02eda8d5c..aa6f4c49a 100644 --- a/src/context.rs +++ b/src/context.rs @@ -164,6 +164,7 @@ impl HttpContext where A: Actor { self.stream = Some(SmallVec::new()); } self.stream.as_mut().map(|s| s.push(frame)); + self.inner.modify(); } /// Handle of the running future diff --git a/src/ws/context.rs b/src/ws/context.rs index 8720b461c..56320c895 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -112,6 +112,7 @@ impl WebsocketContext where A: Actor { } let stream = self.stream.as_mut().unwrap(); stream.push(ContextFrame::Chunk(Some(data))); + self.inner.modify(); } else { warn!("Trying to write to disconnected response"); } @@ -179,6 +180,7 @@ impl WebsocketContext where A: Actor { self.stream = Some(SmallVec::new()); } self.stream.as_mut().map(|s| s.push(frame)); + self.inner.modify(); } /// Handle of the running future From c316a99746b4c535228f736e5a434fa64a790e0c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 27 Feb 2018 20:04:01 -0800 Subject: [PATCH 28/84] stop server test --- tests/test_server.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/test_server.rs b/tests/test_server.rs index ad2028a2b..a7ca7318d 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -93,6 +93,37 @@ fn test_start() { } } +#[test] +fn test_shutdown() { + let _ = test::TestServer::unused_addr(); + let (tx, rx) = mpsc::channel(); + + thread::spawn(move || { + let sys = System::new("test"); + let srv = HttpServer::new( + || vec![Application::new() + .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))]); + + let srv = srv.bind("127.0.0.1:0").unwrap(); + let addr = srv.addrs()[0]; + let srv_addr = srv.shutdown_timeout(1).start(); + let _ = tx.send((addr, srv_addr)); + sys.run(); + }); + let (addr, srv_addr) = rx.recv().unwrap(); + + let mut sys = System::new("test-server"); + + { + let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()).finish().unwrap(); + let response = sys.run_until_complete(req.send()).unwrap(); + srv_addr.do_send(server::StopServer{graceful: true}); + assert!(response.status().is_success()); + } + + assert!(net::TcpStream::connect(addr).is_err()); +} + #[test] fn test_simple() { let mut srv = test::TestServer::new(|app| app.handler(httpcodes::HTTPOk)); From da76de76f04aa9f276ad4b5171347f1d65b60730 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 27 Feb 2018 20:32:51 -0800 Subject: [PATCH 29/84] upgrade sha crate --- Cargo.toml | 2 +- tests/test_server.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bdafe0127..e66eb42d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ rand = "0.4" regex = "0.2" serde = "1.0" serde_json = "1.0" -sha1 = "0.4" +sha1 = "0.6" smallvec = "0.6" time = "0.1" encoding = "0.2" diff --git a/tests/test_server.rs b/tests/test_server.rs index a7ca7318d..0aa3f1bd3 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -121,6 +121,7 @@ fn test_shutdown() { assert!(response.status().is_success()); } + thread::sleep(time::Duration::from_millis(100)); assert!(net::TcpStream::connect(addr).is_err()); } From ccb6ebb2598767c032ae3839111e8fe6ced174a2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 27 Feb 2018 20:49:53 -0800 Subject: [PATCH 30/84] headers test --- tests/test_server.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_server.rs b/tests/test_server.rs index 0aa3f1bd3..4a4a1538c 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -133,6 +133,25 @@ fn test_simple() { assert!(response.status().is_success()); } +#[test] +fn test_headers() { + let mut srv = test::TestServer::new( + |app| app.handler(|_| { + let mut builder = httpcodes::HTTPOk.build(); + for idx in 0..90 { + builder.header(format!("X-TEST-{}", idx).as_str(), "TEST TEST TEST"); + } + builder.body(STR)})); + + let request = srv.get().finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!(Bytes::from(bytes), Bytes::from_static(STR.as_ref())); +} + #[test] fn test_body() { let mut srv = test::TestServer::new( From 9f81eae21522ae750fa4833255f21719d488e805 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 27 Feb 2018 21:04:22 -0800 Subject: [PATCH 31/84] build docs on nightly --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b0c4c6e73..e16fc61c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -73,7 +73,7 @@ script: # Upload docs after_success: - | - if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then + if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; then cargo doc --features "alpn, tls" --no-deps && echo "" > target/doc/index.html && cargo install mdbook && From 1283c005836cafec88169ab3b4cda63f2ba563ec Mon Sep 17 00:00:00 2001 From: pyros2097 Date: Wed, 28 Feb 2018 10:41:24 +0530 Subject: [PATCH 32/84] add juniper example --- Cargo.toml | 1 + examples/juniper/Cargo.toml | 17 +++++ examples/juniper/README.md | 15 +++++ examples/juniper/src/main.rs | 110 +++++++++++++++++++++++++++++++++ examples/juniper/src/schema.rs | 58 +++++++++++++++++ 5 files changed, 201 insertions(+) create mode 100644 examples/juniper/Cargo.toml create mode 100644 examples/juniper/README.md create mode 100644 examples/juniper/src/main.rs create mode 100644 examples/juniper/src/schema.rs diff --git a/Cargo.toml b/Cargo.toml index e66eb42d6..b72c6ecbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,6 +99,7 @@ codegen-units = 1 members = [ "./", "examples/basics", + "examples/juniper", "examples/diesel", "examples/r2d2", "examples/json", diff --git a/examples/juniper/Cargo.toml b/examples/juniper/Cargo.toml new file mode 100644 index 000000000..9e52b0a83 --- /dev/null +++ b/examples/juniper/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "juniper-example" +version = "0.1.0" +authors = ["pyros2097 "] +workspace = "../.." + +[dependencies] +env_logger = "0.5" +actix = "0.5" +actix-web = { path = "../../" } + +futures = "0.1" +serde = "1.0" +serde_json = "1.0" +serde_derive = "1.0" + +juniper = "0.9.2" diff --git a/examples/juniper/README.md b/examples/juniper/README.md new file mode 100644 index 000000000..2ac0eac4e --- /dev/null +++ b/examples/juniper/README.md @@ -0,0 +1,15 @@ +# juniper + +Juniper integration for Actix web + +### server + +```bash +cd actix-web/examples/juniper +cargo run (or ``cargo watch -x run``) +# Started http server: 127.0.0.1:8080 +``` + +### web client + +[http://127.0.0.1:8080/graphiql](http://127.0.0.1:8080/graphiql) diff --git a/examples/juniper/src/main.rs b/examples/juniper/src/main.rs new file mode 100644 index 000000000..643ecb353 --- /dev/null +++ b/examples/juniper/src/main.rs @@ -0,0 +1,110 @@ +//! Actix web juniper example +//! +//! A simple example integrating juniper in actix-web +extern crate serde; +extern crate serde_json; +#[macro_use] +extern crate serde_derive; +#[macro_use] +extern crate juniper; +extern crate futures; +extern crate actix; +extern crate actix_web; +extern crate env_logger; + +use actix::*; +use actix_web::*; +use juniper::http::graphiql::graphiql_source; +use juniper::http::GraphQLRequest; + +use futures::future::Future; + +mod schema; + +use schema::Schema; +use schema::create_schema; + +struct State { + executor: Addr, +} + +#[derive(Serialize, Deserialize)] +pub struct GraphQLData(GraphQLRequest); + +impl Message for GraphQLData { + type Result = Result; +} + +pub struct GraphQLExecutor { + schema: std::sync::Arc +} + +impl GraphQLExecutor { + fn new(schema: std::sync::Arc) -> GraphQLExecutor { + GraphQLExecutor { + schema: schema, + } + } +} + +impl Actor for GraphQLExecutor { + type Context = SyncContext; +} + +impl Handler for GraphQLExecutor { + type Result = Result; + + fn handle(&mut self, msg: GraphQLData, _: &mut Self::Context) -> Self::Result { + let res = msg.0.execute(&self.schema, &()); + let res_text = serde_json::to_string(&res)?; + Ok(res_text) + } +} + +fn graphiql(_req: HttpRequest) -> Result { + let html = graphiql_source("http://localhost:8080/graphql"); + Ok(HttpResponse::build(StatusCode::OK) + .content_type("text/html; charset=utf-8") + .body(html).unwrap()) +} + +fn graphql(req: HttpRequest) -> Box> { + let executor = req.state().executor.clone(); + req.json() + .from_err() + .and_then(move |val: GraphQLData| { + executor.send(val) + .from_err() + .and_then(|res| { + match res { + Ok(user) => Ok(httpcodes::HTTPOk.build().body(user)?), + Err(_) => Ok(httpcodes::HTTPInternalServerError.into()) + } + }) + }) + .responder() +} + +fn main() { + ::std::env::set_var("RUST_LOG", "actix_web=info"); + let _ = env_logger::init(); + let sys = actix::System::new("juniper-example"); + + let schema = std::sync::Arc::new(create_schema()); + let addr = SyncArbiter::start(3, move || { + GraphQLExecutor::new(schema.clone()) + }); + + // Start http server + let _addr = HttpServer::new(move || { + Application::with_state(State{executor: addr.clone()}) + // enable logger + .middleware(middleware::Logger::default()) + .resource("/graphql", |r| r.method(Method::POST).a(graphql)) + .resource("/graphiql", |r| r.method(Method::GET).f(graphiql))}) + .bind("127.0.0.1:8080").unwrap() + .start(); + + println!("Started http server: 127.0.0.1:8080"); + let _ = sys.run(); +} diff --git a/examples/juniper/src/schema.rs b/examples/juniper/src/schema.rs new file mode 100644 index 000000000..2b4cf3042 --- /dev/null +++ b/examples/juniper/src/schema.rs @@ -0,0 +1,58 @@ +use juniper::FieldResult; +use juniper::RootNode; + +#[derive(GraphQLEnum)] +enum Episode { + NewHope, + Empire, + Jedi, +} + +#[derive(GraphQLObject)] +#[graphql(description = "A humanoid creature in the Star Wars universe")] +struct Human { + id: String, + name: String, + appears_in: Vec, + home_planet: String, +} + +#[derive(GraphQLInputObject)] +#[graphql(description = "A humanoid creature in the Star Wars universe")] +struct NewHuman { + name: String, + appears_in: Vec, + home_planet: String, +} + +pub struct QueryRoot; + +graphql_object!(QueryRoot: () |&self| { + field human(&executor, id: String) -> FieldResult { + Ok(Human{ + id: "1234".to_owned(), + name: "Luke".to_owned(), + appears_in: vec![Episode::NewHope], + home_planet: "Mars".to_owned(), + }) + } +}); + +pub struct MutationRoot; + +graphql_object!(MutationRoot: () |&self| { + field createHuman(&executor, new_human: NewHuman) -> FieldResult { + Ok(Human{ + id: "1234".to_owned(), + name: new_human.name, + appears_in: new_human.appears_in, + home_planet: new_human.home_planet, + }) + } +}); + +pub type Schema = RootNode<'static, QueryRoot, MutationRoot>; + +pub fn create_schema() -> Schema { + Schema::new(QueryRoot {}, MutationRoot {}) +} From b1ad4763a2c0f5577adca13bdcb74b932d1e1f20 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 27 Feb 2018 21:23:41 -0800 Subject: [PATCH 33/84] check juniper example --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index e16fc61c3..1d8784f93 100644 --- a/.travis.yml +++ b/.travis.yml @@ -57,6 +57,7 @@ script: cd examples/hello-world && cargo check && cd ../.. cd examples/multipart && cargo check && cd ../.. cd examples/json && cargo check && cd ../.. + cd examples/juniper && cargo check && cd ../.. cd examples/state && cargo check && cd ../.. cd examples/template_tera && cargo check && cd ../.. cd examples/diesel && cargo check && cd ../.. From 4a48b43927b3d1c3b189c5d156418997bbb83027 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 27 Feb 2018 21:49:08 -0800 Subject: [PATCH 34/84] big value --- tests/test_server.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/test_server.rs b/tests/test_server.rs index 4a4a1538c..e5631f398 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -139,7 +139,18 @@ fn test_headers() { |app| app.handler(|_| { let mut builder = httpcodes::HTTPOk.build(); for idx in 0..90 { - builder.header(format!("X-TEST-{}", idx).as_str(), "TEST TEST TEST"); + builder.header( + format!("X-TEST-{}", idx).as_str(), + "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST "); } builder.body(STR)})); From 7591592279129fc38ecf0ac80e4379931f5634d4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 27 Feb 2018 23:04:57 -0800 Subject: [PATCH 35/84] fix handle big data chunkd for parsing --- src/client/parser.rs | 4 ++-- src/server/h1.rs | 4 ++-- tests/test_server.rs | 21 ++++++++++++--------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/client/parser.rs b/src/client/parser.rs index 31c6601aa..1eeb021b1 100644 --- a/src/client/parser.rs +++ b/src/client/parser.rs @@ -2,7 +2,7 @@ use std::mem; use httparse; use http::{Version, HttpTryFrom, HeaderMap, StatusCode}; use http::header::{self, HeaderName, HeaderValue}; -use bytes::{Bytes, BytesMut}; +use bytes::{Bytes, BytesMut, BufMut}; use futures::{Poll, Async}; use error::{ParseError, PayloadError}; @@ -64,7 +64,7 @@ impl HttpResponseParser { if buf.capacity() >= MAX_BUFFER_SIZE { return Err(HttpResponseParserError::Error(ParseError::TooLarge)); } - if read { + if read || buf.remaining_mut() == 0 { match utils::read_from_io(io, buf) { Ok(Async::Ready(0)) => return Err(HttpResponseParserError::Disconnect), Ok(Async::Ready(_)) => (), diff --git a/src/server/h1.rs b/src/server/h1.rs index 11c0bb2d4..605aaed27 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -10,7 +10,7 @@ use actix::Arbiter; use httparse; use http::{Uri, Method, Version, HttpTryFrom, HeaderMap}; use http::header::{self, HeaderName, HeaderValue}; -use bytes::{Bytes, BytesMut}; +use bytes::{Bytes, BytesMut, BufMut}; use futures::{Future, Poll, Async}; use tokio_core::reactor::Timeout; @@ -474,7 +474,7 @@ impl Reader { error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); return Err(ReaderError::Error(ParseError::TooLarge)); } - if read { + if read || buf.remaining_mut() == 0 { match utils::read_from_io(io, buf) { Ok(Async::Ready(0)) => { debug!("Ignored premature client disconnection"); diff --git a/tests/test_server.rs b/tests/test_server.rs index e5631f398..7714ad1fc 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -141,15 +141,18 @@ fn test_headers() { for idx in 0..90 { builder.header( format!("X-TEST-{}", idx).as_str(), - "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST + "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST "); } builder.body(STR)})); From 8994732227c59f549e1562eb17b1bb79abb71809 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 27 Feb 2018 23:30:26 -0800 Subject: [PATCH 36/84] doc strings --- CHANGES.md | 4 ++-- src/httpmessage.rs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 04e1aed85..f2deadfdb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,10 +1,10 @@ # Changes -## 0.4.0 (2018-02-..) +## 0.4.0 (2018-02-28) * Actix 0.5 compatibility -* Fix request json loader +* Fix request json/urlencoded loaders * Simplify HttpServer type definition diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 14a551ead..2d8b1659d 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -17,9 +17,10 @@ use error::{ParseError, ContentTypeError, HttpRangeError, PayloadError, UrlencodedError}; +/// Trait that implements general purpose operations on http messages pub trait HttpMessage { - /// Read the Message Headers. + /// Read the message headers. fn headers(&self) -> &HeaderMap; /// Read the request content type. If request does not contain From b339ea0a3a9fa0690f9e2f23157ba9b6d85a34a9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 27 Feb 2018 23:31:43 -0800 Subject: [PATCH 37/84] update versions in guide --- guide/src/qs_2.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md index ac53d8707..01cb98499 100644 --- a/guide/src/qs_2.md +++ b/guide/src/qs_2.md @@ -20,8 +20,8 @@ contains the following: ```toml [dependencies] -actix = "0.4" -actix-web = "0.3" +actix = "0.5" +actix-web = "0.4" ``` In order to implement a web server, first we need to create a request handler. From 764421fe44b12180d1228aa1073e8f2e18949197 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 27 Feb 2018 23:51:57 -0800 Subject: [PATCH 38/84] update categories --- Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b72c6ecbb..3d12d1388 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,9 @@ homepage = "https://github.com/actix/actix-web" repository = "https://github.com/actix/actix-web.git" documentation = "https://docs.rs/actix-web/" categories = ["network-programming", "asynchronous", - "web-programming::http-server", "web-programming::websocket"] + "web-programming::http-server", + "web-programming::http-client", + "web-programming::websocket"] license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml", "/examples/**"] From 67f33a4760d295d8db059da5945e8f1f4fb66464 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 28 Feb 2018 10:26:40 -0800 Subject: [PATCH 39/84] add redis session example --- Cargo.toml | 1 + examples/redis-session/Cargo.toml | 11 +++++++ examples/redis-session/src/main.rs | 48 ++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 examples/redis-session/Cargo.toml create mode 100644 examples/redis-session/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 3d12d1388..0780053d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,6 +108,7 @@ members = [ "examples/hello-world", "examples/multipart", "examples/state", + "examples/redis-session", "examples/template_tera", "examples/tls", "examples/websocket", diff --git a/examples/redis-session/Cargo.toml b/examples/redis-session/Cargo.toml new file mode 100644 index 000000000..cfa102d11 --- /dev/null +++ b/examples/redis-session/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "redis-session" +version = "0.1.0" +authors = ["Nikolay Kim "] +workspace = "../.." + +[dependencies] +env_logger = "0.5" +actix = "0.5" +actix-web = "0.4" +actix-redis = { version = "0.2", features = ["web"] } diff --git a/examples/redis-session/src/main.rs b/examples/redis-session/src/main.rs new file mode 100644 index 000000000..36df16559 --- /dev/null +++ b/examples/redis-session/src/main.rs @@ -0,0 +1,48 @@ +#![allow(unused_variables)] + +extern crate actix; +extern crate actix_web; +extern crate actix_redis; +extern crate env_logger; + +use actix_web::*; +use actix_web::middleware::RequestSession; +use actix_redis::RedisSessionBackend; + + +/// simple handler +fn index(mut req: HttpRequest) -> Result { + println!("{:?}", req); + + // session + if let Some(count) = req.session().get::("counter")? { + println!("SESSION value: {}", count); + req.session().set("counter", count+1)?; + } else { + req.session().set("counter", 1)?; + } + + Ok("Welcome!".into()) +} + +fn main() { + ::std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info"); + env_logger::init(); + let sys = actix::System::new("basic-example"); + + HttpServer::new( + || Application::new() + // enable logger + .middleware(middleware::Logger::default()) + // cookie session middleware + .middleware(middleware::SessionStorage::new( + RedisSessionBackend::new("127.0.0.1:6379", &[0; 32]) + )) + // register simple route, handle all methods + .resource("/", |r| r.f(index))) + .bind("0.0.0.0:8080").unwrap() + .threads(1) + .start(); + + let _ = sys.run(); +} From 171a23561e8dcdaaec31e4a29f7cfcd9a5dede94 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 28 Feb 2018 11:10:54 -0800 Subject: [PATCH 40/84] export Drain --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 05127e1f3..e85ce7480 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -188,6 +188,7 @@ pub mod dev { //! ``` pub use body::BodyStream; + pub use context::Drain; pub use info::ConnectionInfo; pub use handler::Handler; pub use json::JsonBody; From 313396d9b50a44ef87492cf40e2b7a0c248d8549 Mon Sep 17 00:00:00 2001 From: Alex Whitney Date: Wed, 28 Feb 2018 19:35:26 +0000 Subject: [PATCH 41/84] fix session mut borrow lifetime --- src/middleware/session.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/session.rs b/src/middleware/session.rs index 7cdb7e093..e08ce03f2 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -86,7 +86,7 @@ impl<'a> Session<'a> { } /// Set a `value` from the session. - pub fn set(&'a mut self, key: &str, value: T) -> Result<()> { + pub fn set(&mut self, key: &str, value: T) -> Result<()> { self.0.set(key, serde_json::to_string(&value)?); Ok(()) } From bb68f9dd905e84c262fe849ba61915328cbf09e8 Mon Sep 17 00:00:00 2001 From: Alex Whitney Date: Wed, 28 Feb 2018 19:51:14 +0000 Subject: [PATCH 42/84] add session borrow fix to changes --- CHANGES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index f2deadfdb..f056ce6ed 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## 0.4.1 (2018-03-xx) + +* Fix Session mutable borrow lifetime + + ## 0.4.0 (2018-02-28) * Actix 0.5 compatibility From 1284264511999f8ebcce4baffd7669b090ee26a0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 28 Feb 2018 12:35:16 -0800 Subject: [PATCH 43/84] Update CHANGES.md --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index f056ce6ed..bf56f4bc3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ ## 0.4.1 (2018-03-xx) -* Fix Session mutable borrow lifetime +* Fix Session mutable borrow lifetime #87 ## 0.4.0 (2018-02-28) From d62d6e68e003ef97f051e304c37bb2310e76fd3e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 28 Feb 2018 14:16:55 -0800 Subject: [PATCH 44/84] use new version of http crate --- Cargo.toml | 2 +- src/ws/client.rs | 9 ++++----- src/ws/mod.rs | 23 +++++++++-------------- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0780053d0..085878302 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,7 @@ brotli2 = "^0.3.2" failure = "0.1.1" flate2 = "1.0" h2 = "0.1" -http = "^0.1.2" +http = "^0.1.5" httparse = "1.2" http-range = "0.1" libc = "0.2" diff --git a/src/ws/client.rs b/src/ws/client.rs index 587d60a65..e160016be 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -197,11 +197,11 @@ impl WsClient { self.request.upgrade(); self.request.set_header(header::UPGRADE, "websocket"); self.request.set_header(header::CONNECTION, "upgrade"); - self.request.set_header("SEC-WEBSOCKET-VERSION", "13"); + self.request.set_header(header::SEC_WEBSOCKET_VERSION, "13"); self.request.with_connector(self.conn.clone()); if let Some(protocols) = self.protocols.take() { - self.request.set_header("SEC-WEBSOCKET-PROTOCOL", protocols.as_str()); + self.request.set_header(header::SEC_WEBSOCKET_PROTOCOL, protocols.as_str()); } let request = match self.request.finish() { Ok(req) => req, @@ -249,7 +249,7 @@ impl WsClientHandshake { let key = base64::encode(&sec_key); request.headers_mut().insert( - HeaderName::try_from("SEC-WEBSOCKET-KEY").unwrap(), + header::SEC_WEBSOCKET_KEY, HeaderValue::try_from(key.as_str()).unwrap()); let (tx, rx) = unbounded(); @@ -328,8 +328,7 @@ impl Future for WsClientHandshake { return Err(WsClientError::MissingConnectionHeader) } - if let Some(key) = resp.headers().get( - HeaderName::try_from("SEC-WEBSOCKET-ACCEPT").unwrap()) + if let Some(key) = resp.headers().get(header::SEC_WEBSOCKET_ACCEPT) { // field is constructed by concatenating /key/ // with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455) diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 90b2558b7..8cd4d9207 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -70,11 +70,6 @@ pub use self::context::WebsocketContext; pub use self::client::{WsClient, WsClientError, WsClientReader, WsClientWriter, WsClientHandshake}; -const SEC_WEBSOCKET_ACCEPT: &str = "SEC-WEBSOCKET-ACCEPT"; -const SEC_WEBSOCKET_KEY: &str = "SEC-WEBSOCKET-KEY"; -const SEC_WEBSOCKET_VERSION: &str = "SEC-WEBSOCKET-VERSION"; -// const SEC_WEBSOCKET_PROTOCOL: &'static str = "SEC-WEBSOCKET-PROTOCOL"; - /// Websocket errors #[derive(Fail, Debug)] @@ -222,11 +217,11 @@ pub fn handshake(req: &HttpRequest) -> Result(req: &HttpRequest) -> Result(req: &HttpRequest) -> Result Date: Thu, 1 Mar 2018 01:01:27 +0100 Subject: [PATCH 45/84] be consistent with host - had CORS preflight once --- examples/juniper/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/juniper/src/main.rs b/examples/juniper/src/main.rs index 643ecb353..c0be2754e 100644 --- a/examples/juniper/src/main.rs +++ b/examples/juniper/src/main.rs @@ -62,7 +62,7 @@ impl Handler for GraphQLExecutor { } fn graphiql(_req: HttpRequest) -> Result { - let html = graphiql_source("http://localhost:8080/graphql"); + let html = graphiql_source("http://127.0.0.1:8080/graphql"); Ok(HttpResponse::build(StatusCode::OK) .content_type("text/html; charset=utf-8") .body(html).unwrap()) From 0335fde3f9fd2606d1ffea3e48a5f6554a36b340 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 28 Feb 2018 16:58:05 -0800 Subject: [PATCH 46/84] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 19009e41c..afd70ae73 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Actix web is a small, pragmatic, extremely fast, web framework for Rust. * Configurable [request routing](https://actix.github.io/actix-web/guide/qs_5.html) * Graceful server shutdown * Multipart streams +* SSL support with openssl or native-tls * Middlewares ([Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging), [Session](https://actix.github.io/actix-web/guide/qs_10.html#user-sessions), [DefaultHeaders](https://actix.github.io/actix-web/guide/qs_10.html#default-headers), From 4aaf9f08f877326e1098922cc9a257eb2941ddbc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 28 Feb 2018 22:31:54 -0800 Subject: [PATCH 47/84] update readme --- Cargo.toml | 2 +- README.md | 6 +++++- src/lib.rs | 7 ++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 085878302..cf050ed6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "actix-web" version = "0.4.0" authors = ["Nikolay Kim "] -description = "Actix web framework" +description = "Actix web is a small, pragmatic, extremely fast, web framework for Rust." readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://github.com/actix/actix-web" diff --git a/README.md b/README.md index afd70ae73..7c8ac4e0a 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,10 @@ Actix web is a small, pragmatic, extremely fast, web framework for Rust. * SSL support with openssl or native-tls * Middlewares ([Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging), [Session](https://actix.github.io/actix-web/guide/qs_10.html#user-sessions), + [Redis sessions](https://github.com/actix/actix-redis), [DefaultHeaders](https://actix.github.io/actix-web/guide/qs_10.html#default-headers), [CORS](https://actix.github.io/actix-web/actix_web/middleware/cors/index.html)) -* Built on top of [Actix](https://github.com/actix/actix). +* Built on top of [Actix actor framework](https://github.com/actix/actix). ## Documentation @@ -58,6 +59,9 @@ fn main() { * [SockJS Server](https://github.com/actix/actix-sockjs) * [Json](https://github.com/actix/actix-web/tree/master/examples/json/) +You may consider checking out +[this directory](https://github.com/actix/actix-web/tree/master/examples) for more examples. + ## Benchmarks * [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=plaintext) diff --git a/src/lib.rs b/src/lib.rs index e85ce7480..f3decb149 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,10 +35,11 @@ //! * `WebSockets` server/client //! * Transparent content compression/decompression (br, gzip, deflate) //! * Configurable request routing -//! * Multipart streams -//! * Middlewares (`Logger`, `Session`, `CORS`, `DefaultHeaders`) //! * Graceful server shutdown -//! * Built on top of [Actix](https://github.com/actix/actix). +//! * Multipart streams +//! * SSL support with openssl or native-tls +//! * Middlewares (`Logger`, `Session`, `CORS`, `DefaultHeaders`) +//! * Built on top of [Actix actor framework](https://github.com/actix/actix). #![cfg_attr(actix_nightly, feature( specialization, // for impl ErrorResponse for std::error::Error From 5b6d7cddbfb83bc556ae00c7738cea7f1f320350 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 1 Mar 2018 18:26:26 -0800 Subject: [PATCH 48/84] Fix payload parse in situation when socket data is not ready --- CHANGES.md | 4 +++- src/server/h1.rs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index bf56f4bc3..a0cd0cdf9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,8 @@ # Changes -## 0.4.1 (2018-03-xx) +## 0.4.1 (2018-03-01) + +* Fix payload parse in situation when socket data is not ready. * Fix Session mutable borrow lifetime #87 diff --git a/src/server/h1.rs b/src/server/h1.rs index 605aaed27..4d528918a 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -431,7 +431,7 @@ impl Reader { break true }, Ok(Async::NotReady) => - break false, + return Ok(Async::NotReady), Err(err) => { payload.tx.set_error(err.into()); return Err(ReaderError::Payload) From 4e13505b92b72e5184d2c07f271b331cf7d88632 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 1 Mar 2018 18:32:31 -0800 Subject: [PATCH 49/84] rename .p to a .filter --- CHANGES.md | 2 ++ guide/src/qs_5.md | 12 ++++++------ src/pred.rs | 4 ++-- src/resource.rs | 8 ++++---- src/route.rs | 12 +++++++++--- 5 files changed, 23 insertions(+), 15 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a0cd0cdf9..ac94fa2f8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,8 @@ ## 0.4.1 (2018-03-01) +* Rename `Route::p()` to `Route::filter()` + * Fix payload parse in situation when socket data is not ready. * Fix Session mutable borrow lifetime #87 diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index c1e8b615c..828a6d6c2 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -68,8 +68,8 @@ fn main() { Application::new() .resource("/path", |resource| resource.route() - .p(pred::Get()) - .p(pred::Header("content-type", "text/plain")) + .filter(pred::Get()) + .filter(pred::Header("content-type", "text/plain")) .f(|req| HTTPOk) ) .finish(); @@ -85,7 +85,7 @@ If resource can not match any route "NOT FOUND" response get returned. [*Route*](../actix_web/struct.Route.html) object. Route can be configured with builder-like pattern. Following configuration methods are available: -* [*Route::p()*](../actix_web/struct.Route.html#method.p) method registers new predicate, +* [*Route::filter()*](../actix_web/struct.Route.html#method.filter) method registers new predicate, any number of predicates could be registered for each route. * [*Route::f()*](../actix_web/struct.Route.html#method.f) method registers handler function @@ -502,7 +502,7 @@ fn main() { Application::new() .resource("/index.html", |r| r.route() - .p(ContentTypeHeader) + .filter(ContentTypeHeader) .h(HTTPOk)); } ``` @@ -530,7 +530,7 @@ fn main() { Application::new() .resource("/index.html", |r| r.route() - .p(pred::Not(pred::Get())) + .filter(pred::Not(pred::Get())) .f(|req| HTTPMethodNotAllowed)) .finish(); } @@ -568,7 +568,7 @@ fn main() { Application::new() .default_resource(|r| { r.method(Method::GET).f(|req| HTTPNotFound); - r.route().p(pred::Not(pred::Get())).f(|req| HTTPMethodNotAllowed); + r.route().filter(pred::Not(pred::Get())).f(|req| HTTPMethodNotAllowed); }) # .finish(); } diff --git a/src/pred.rs b/src/pred.rs index c84325eef..22abf6fec 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -28,7 +28,7 @@ pub trait Predicate { /// fn main() { /// Application::new() /// .resource("/index.html", |r| r.route() -/// .p(pred::Any(pred::Get()).or(pred::Post())) +/// .filter(pred::Any(pred::Get()).or(pred::Post())) /// .h(HTTPMethodNotAllowed)); /// } /// ``` @@ -71,7 +71,7 @@ impl Predicate for AnyPredicate { /// fn main() { /// Application::new() /// .resource("/index.html", |r| r.route() -/// .p(pred::All(pred::Get()) +/// .filter(pred::All(pred::Get()) /// .and(pred::Header("content-type", "plain/text"))) /// .h(HTTPMethodNotAllowed)); /// } diff --git a/src/resource.rs b/src/resource.rs index eb783a227..2e83225ea 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -81,8 +81,8 @@ impl Resource { /// let app = Application::new() /// .resource( /// "/", |r| r.route() - /// .p(pred::Any(pred::Get()).or(pred::Put())) - /// .p(pred::Header("Content-Type", "text/plain")) + /// .filter(pred::Any(pred::Get()).or(pred::Put())) + /// .filter(pred::Header("Content-Type", "text/plain")) /// .f(|r| HttpResponse::Ok())) /// .finish(); /// } @@ -97,11 +97,11 @@ impl Resource { /// This is shortcut for: /// /// ```rust,ignore - /// Resource::resource("/", |r| r.route().p(pred::Get()).f(index) + /// Resource::resource("/", |r| r.route().filter(pred::Get()).f(index) /// ``` pub fn method(&mut self, method: Method) -> &mut Route { self.routes.push(Route::default()); - self.routes.last_mut().unwrap().p(pred::Method(method)) + self.routes.last_mut().unwrap().filter(pred::Method(method)) } /// Register a new route and add handler object. diff --git a/src/route.rs b/src/route.rs index b4d1d2680..5880d669f 100644 --- a/src/route.rs +++ b/src/route.rs @@ -65,18 +65,24 @@ impl Route { /// Application::new() /// .resource("/path", |r| /// r.route() - /// .p(pred::Get()) - /// .p(pred::Header("content-type", "text/plain")) + /// .filter(pred::Get()) + /// .filter(pred::Header("content-type", "text/plain")) /// .f(|req| HTTPOk) /// ) /// # .finish(); /// # } /// ``` - pub fn p + 'static>(&mut self, p: T) -> &mut Self { + pub fn filter + 'static>(&mut self, p: T) -> &mut Self { self.preds.push(Box::new(p)); self } + #[doc(hidden)] + #[deprecated(since="0.4.1", note="please use `.filter()` instead")] + pub fn p + 'static>(&mut self, p: T) -> &mut Self { + self.filter(p) + } + /// Set handler object. Usually call to this method is last call /// during route configuration, because it does not return reference to self. pub fn h>(&mut self, handler: H) { From 206c4e581adb8ec81f9fbf40c70c8d63706305cc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 1 Mar 2018 19:12:59 -0800 Subject: [PATCH 50/84] rename httpcodes --- CHANGES.md | 2 + guide/src/qs_10.md | 6 +- guide/src/qs_14.md | 4 +- guide/src/qs_3.md | 6 +- guide/src/qs_3_5.md | 12 ++-- guide/src/qs_4.md | 4 +- guide/src/qs_4_5.md | 2 +- guide/src/qs_5.md | 24 +++---- guide/src/qs_7.md | 6 +- guide/src/qs_8.md | 12 ++-- src/application.rs | 34 +++++----- src/client/request.rs | 2 +- src/error.rs | 14 ++-- src/fs.rs | 8 +-- src/handler.rs | 2 +- src/httpcodes.rs | 107 +++++++++++++++++++++++++++++++ src/httpmessage.rs | 6 +- src/httprequest.rs | 4 +- src/httpresponse.rs | 6 +- src/json.rs | 2 +- src/middleware/cors.rs | 18 +++--- src/middleware/defaultheaders.rs | 4 +- src/pred.rs | 4 +- src/route.rs | 6 +- src/server/h1.rs | 4 +- src/server/h2.rs | 4 +- src/server/srv.rs | 4 +- src/test.rs | 8 +-- src/ws/mod.rs | 14 ++-- 29 files changed, 219 insertions(+), 110 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ac94fa2f8..ea1b65741 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ * Rename `Route::p()` to `Route::filter()` +* Better naming for http codes + * Fix payload parse in situation when socket data is not ready. * Fix Session mutable borrow lifetime #87 diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index ed36140c7..3e007bcab 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -53,7 +53,7 @@ impl Middleware for Headers { fn main() { Application::new() .middleware(Headers) // <- Register middleware, this method could be called multiple times - .resource("/", |r| r.h(httpcodes::HTTPOk)); + .resource("/", |r| r.h(httpcodes::HttpOk)); } ``` @@ -144,8 +144,8 @@ fn main() { .header("X-Version", "0.2") .finish()) .resource("/test", |r| { - r.method(Method::GET).f(|req| httpcodes::HTTPOk); - r.method(Method::HEAD).f(|req| httpcodes::HTTPMethodNotAllowed); + r.method(Method::GET).f(|req| httpcodes::HttpOk); + r.method(Method::HEAD).f(|req| httpcodes::HttpMethodNotAllowed); }) .finish(); } diff --git a/guide/src/qs_14.md b/guide/src/qs_14.md index c318bcaad..e19d0ea9b 100644 --- a/guide/src/qs_14.md +++ b/guide/src/qs_14.md @@ -110,8 +110,8 @@ fn index(req: HttpRequest) -> Box> .from_err() .and_then(|res| { match res { - Ok(user) => Ok(httpcodes::HTTPOk.build().json(user)?), - Err(_) => Ok(httpcodes::HTTPInternalServerError.into()) + Ok(user) => Ok(httpcodes::HttpOk.build().json(user)?), + Err(_) => Ok(httpcodes::HttpInternalServerError.into()) } }) .responder() diff --git a/guide/src/qs_3.md b/guide/src/qs_3.md index 6d9c1a426..341b62cc0 100644 --- a/guide/src/qs_3.md +++ b/guide/src/qs_3.md @@ -49,12 +49,12 @@ fn main() { HttpServer::new(|| vec![ Application::new() .prefix("/app1") - .resource("/", |r| r.f(|r| httpcodes::HTTPOk)), + .resource("/", |r| r.f(|r| httpcodes::HttpOk)), Application::new() .prefix("/app2") - .resource("/", |r| r.f(|r| httpcodes::HTTPOk)), + .resource("/", |r| r.f(|r| httpcodes::HttpOk)), Application::new() - .resource("/", |r| r.f(|r| httpcodes::HTTPOk)), + .resource("/", |r| r.f(|r| httpcodes::HttpOk)), ]); } ``` diff --git a/guide/src/qs_3_5.md b/guide/src/qs_3_5.md index 99c2bcd9a..3f1fff00e 100644 --- a/guide/src/qs_3_5.md +++ b/guide/src/qs_3_5.md @@ -20,7 +20,7 @@ fn main() { HttpServer::new( || Application::new() - .resource("/", |r| r.h(httpcodes::HTTPOk))) + .resource("/", |r| r.h(httpcodes::HttpOk))) .bind("127.0.0.1:59080").unwrap() .start(); @@ -57,7 +57,7 @@ fn main() { let sys = actix::System::new("http-server"); let addr = HttpServer::new( || Application::new() - .resource("/", |r| r.h(httpcodes::HTTPOk))) + .resource("/", |r| r.h(httpcodes::HttpOk))) .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") .shutdown_timeout(60) // <- Set shutdown timeout to 60 seconds .start(); @@ -85,7 +85,7 @@ use actix_web::*; fn main() { HttpServer::new( || Application::new() - .resource("/", |r| r.h(httpcodes::HTTPOk))) + .resource("/", |r| r.h(httpcodes::HttpOk))) .threads(4); // <- Start 4 workers } ``` @@ -146,7 +146,7 @@ use actix_web::*; fn main() { HttpServer::new(|| Application::new() - .resource("/", |r| r.h(httpcodes::HTTPOk))) + .resource("/", |r| r.h(httpcodes::HttpOk))) .keep_alive(None); // <- Use `SO_KEEPALIVE` socket option. } ``` @@ -155,7 +155,7 @@ If first option is selected then *keep alive* state calculated based on response's *connection-type*. By default `HttpResponse::connection_type` is not defined in that case *keep alive* defined by request's http version. Keep alive is off for *HTTP/1.0* -and is on for *HTTP/1.1* and "HTTP/2.0". +and is on for *HTTP/1.1* and *HTTP/2.0*. *Connection type* could be change with `HttpResponseBuilder::connection_type()` method. @@ -165,7 +165,7 @@ and is on for *HTTP/1.1* and "HTTP/2.0". use actix_web::*; fn index(req: HttpRequest) -> HttpResponse { - HTTPOk.build() + HttpOk.build() .connection_type(headers::ConnectionType::Close) // <- Close connection .force_close() // <- Alternative method .finish().unwrap() diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index c7cbc6c94..486e9df58 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -65,7 +65,7 @@ impl Handler for MyHandler { /// Handle request fn handle(&mut self, req: HttpRequest) -> Self::Result { self.0 += 1; - httpcodes::HTTPOk.into() + httpcodes::HttpOk.into() } } # fn main() {} @@ -90,7 +90,7 @@ impl Handler for MyHandler { /// Handle request fn handle(&mut self, req: HttpRequest) -> Self::Result { self.0.fetch_add(1, Ordering::Relaxed); - httpcodes::HTTPOk.into() + httpcodes::HttpOk.into() } } diff --git a/guide/src/qs_4_5.md b/guide/src/qs_4_5.md index 5a11af733..01808c605 100644 --- a/guide/src/qs_4_5.md +++ b/guide/src/qs_4_5.md @@ -14,7 +14,7 @@ impl> Responder for Result And any error that implements `ResponseError` can be converted into `Error` object. For example if *handler* function returns `io::Error`, it would be converted -into `HTTPInternalServerError` response. Implementation for `io::Error` is provided +into `HttpInternalServerError` response. Implementation for `io::Error` is provided by default. ```rust diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index 828a6d6c2..21b2f8c64 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -32,7 +32,7 @@ fn main() { Application::new() .resource("/prefix", |r| r.f(index)) .resource("/user/{name}", - |r| r.method(Method::GET).f(|req| HTTPOk)) + |r| r.method(Method::GET).f(|req| HttpOk)) .finish(); } ``` @@ -52,7 +52,7 @@ returns *NOT FOUND* http resources. Resource contains set of routes. Each route in turn has set of predicates and handler. New route could be created with `Resource::route()` method which returns reference to new *Route* instance. By default *route* does not contain any predicates, so matches -all requests and default handler is `HTTPNotFound`. +all requests and default handler is `HttpNotFound`. Application routes incoming requests based on route criteria which is defined during resource registration and route registration. Resource matches all routes it contains in @@ -70,7 +70,7 @@ fn main() { resource.route() .filter(pred::Get()) .filter(pred::Header("content-type", "text/plain")) - .f(|req| HTTPOk) + .f(|req| HttpOk) ) .finish(); } @@ -336,14 +336,14 @@ resource with the name "foo" and the pattern "{a}/{b}/{c}", you might do this. # fn index(req: HttpRequest) -> HttpResponse { let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource - HTTPOk.into() + HttpOk.into() } fn main() { let app = Application::new() .resource("/test/{a}/{b}/{c}", |r| { r.name("foo"); // <- set resource name, then it could be used in `url_for` - r.method(Method::GET).f(|_| httpcodes::HTTPOk); + r.method(Method::GET).f(|_| httpcodes::HttpOk); }) .finish(); } @@ -367,7 +367,7 @@ use actix_web::*; fn index(mut req: HttpRequest) -> Result { let url = req.url_for("youtube", &["oHg5SJYRHA0"])?; assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); - Ok(httpcodes::HTTPOk.into()) + Ok(httpcodes::HttpOk.into()) } fn main() { @@ -404,7 +404,7 @@ This handler designed to be use as a handler for application's *default resource # use actix_web::*; # # fn index(req: HttpRequest) -> httpcodes::StaticResponse { -# httpcodes::HTTPOk +# httpcodes::HttpOk # } fn main() { let app = Application::new() @@ -429,7 +429,7 @@ It is possible to register path normalization only for *GET* requests only # use actix_web::*; # # fn index(req: HttpRequest) -> httpcodes::StaticResponse { -# httpcodes::HTTPOk +# httpcodes::HttpOk # } fn main() { let app = Application::new() @@ -503,7 +503,7 @@ fn main() { .resource("/index.html", |r| r.route() .filter(ContentTypeHeader) - .h(HTTPOk)); + .h(HttpOk)); } ``` @@ -531,7 +531,7 @@ fn main() { .resource("/index.html", |r| r.route() .filter(pred::Not(pred::Get())) - .f(|req| HTTPMethodNotAllowed)) + .f(|req| HttpMethodNotAllowed)) .finish(); } ``` @@ -567,8 +567,8 @@ use actix_web::httpcodes::*; fn main() { Application::new() .default_resource(|r| { - r.method(Method::GET).f(|req| HTTPNotFound); - r.route().filter(pred::Not(pred::Get())).f(|req| HTTPMethodNotAllowed); + r.method(Method::GET).f(|req| HttpNotFound); + r.route().filter(pred::Not(pred::Get())).f(|req| HttpMethodNotAllowed); }) # .finish(); } diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 448d28eaf..e7c6bc88b 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -84,7 +84,7 @@ fn index(mut req: HttpRequest) -> Box> { req.json().from_err() .and_then(|val: MyObj| { println!("model: {:?}", val); - Ok(httpcodes::HTTPOk.build().json(val)?) // <- send response + Ok(httpcodes::HttpOk.build().json(val)?) // <- send response }) .responder() } @@ -117,7 +117,7 @@ fn index(req: HttpRequest) -> Box> { // synchronous workflow .and_then(|body| { // <- body is loaded, now we can deserialize json let obj = serde_json::from_slice::(&body)?; - Ok(httpcodes::HTTPOk.build().json(obj)?) // <- send response + Ok(httpcodes::HttpOk.build().json(obj)?) // <- send response }) .responder() } @@ -251,7 +251,7 @@ fn index(mut req: HttpRequest) -> Box> { .from_err() .and_then(|params| { // <- url encoded parameters println!("==== BODY ==== {:?}", params); - ok(httpcodes::HTTPOk.into()) + ok(httpcodes::HttpOk.into()) }) .responder() } diff --git a/guide/src/qs_8.md b/guide/src/qs_8.md index b19e94a45..74e7421d2 100644 --- a/guide/src/qs_8.md +++ b/guide/src/qs_8.md @@ -20,10 +20,10 @@ use actix_web::test::TestRequest; fn index(req: HttpRequest) -> HttpResponse { if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { if let Ok(s) = hdr.to_str() { - return httpcodes::HTTPOk.into() + return httpcodes::HttpOk.into() } } - httpcodes::HTTPBadRequest.into() + httpcodes::HttpBadRequest.into() } fn main() { @@ -59,16 +59,16 @@ use actix_web::*; use actix_web::test::TestServer; fn index(req: HttpRequest) -> HttpResponse { - httpcodes::HTTPOk.into() + httpcodes::HttpOk.into() } fn main() { let mut srv = TestServer::new(|app| app.handler(index)); // <- Start new test server - + let request = srv.get().finish().unwrap(); // <- create client request let response = srv.execute(request.send()).unwrap(); // <- send request to the server assert!(response.status().is_success()); // <- check response - + let bytes = srv.execute(response.body()).unwrap(); // <- read response body } ``` @@ -84,7 +84,7 @@ use actix_web::*; use actix_web::test::TestServer; fn index(req: HttpRequest) -> HttpResponse { - httpcodes::HTTPOk.into() + httpcodes::HttpOk.into() } /// This function get called by http server. diff --git a/src/application.rs b/src/application.rs index f8ac85c9f..9f0e399b3 100644 --- a/src/application.rs +++ b/src/application.rs @@ -183,8 +183,8 @@ impl Application where S: 'static { /// let app = Application::new() /// .prefix("/app") /// .resource("/test", |r| { - /// r.method(Method::GET).f(|_| httpcodes::HTTPOk); - /// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed); + /// r.method(Method::GET).f(|_| httpcodes::HttpOk); + /// r.method(Method::HEAD).f(|_| httpcodes::HttpMethodNotAllowed); /// }) /// .finish(); /// } @@ -226,8 +226,8 @@ impl Application where S: 'static { /// fn main() { /// let app = Application::new() /// .resource("/test", |r| { - /// r.method(Method::GET).f(|_| httpcodes::HTTPOk); - /// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed); + /// r.method(Method::GET).f(|_| httpcodes::HttpOk); + /// r.method(Method::HEAD).f(|_| httpcodes::HttpMethodNotAllowed); /// }); /// } /// ``` @@ -281,7 +281,7 @@ impl Application where S: 'static { /// fn index(mut req: HttpRequest) -> Result { /// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?; /// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); - /// Ok(httpcodes::HTTPOk.into()) + /// Ok(httpcodes::HttpOk.into()) /// } /// /// fn main() { @@ -320,9 +320,9 @@ impl Application where S: 'static { /// let app = Application::new() /// .handler("/app", |req: HttpRequest| { /// match *req.method() { - /// Method::GET => httpcodes::HTTPOk, - /// Method::POST => httpcodes::HTTPMethodNotAllowed, - /// _ => httpcodes::HTTPNotFound, + /// Method::GET => httpcodes::HttpOk, + /// Method::POST => httpcodes::HttpMethodNotAllowed, + /// _ => httpcodes::HttpNotFound, /// }}); /// } /// ``` @@ -394,11 +394,11 @@ impl Application where S: 'static { /// HttpServer::new(|| { vec![ /// Application::with_state(State1) /// .prefix("/app1") - /// .resource("/", |r| r.h(httpcodes::HTTPOk)) + /// .resource("/", |r| r.h(httpcodes::HttpOk)) /// .boxed(), /// Application::with_state(State2) /// .prefix("/app2") - /// .resource("/", |r| r.h(httpcodes::HTTPOk)) + /// .resource("/", |r| r.h(httpcodes::HttpOk)) /// .boxed() ]}) /// .bind("127.0.0.1:8080").unwrap() /// .run() @@ -459,7 +459,7 @@ mod tests { #[test] fn test_default_resource() { let mut app = Application::new() - .resource("/test", |r| r.h(httpcodes::HTTPOk)) + .resource("/test", |r| r.h(httpcodes::HttpOk)) .finish(); let req = TestRequest::with_uri("/test").finish(); @@ -471,7 +471,7 @@ mod tests { assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); let mut app = Application::new() - .default_resource(|r| r.h(httpcodes::HTTPMethodNotAllowed)) + .default_resource(|r| r.h(httpcodes::HttpMethodNotAllowed)) .finish(); let req = TestRequest::with_uri("/blah").finish(); let resp = app.run(req); @@ -482,7 +482,7 @@ mod tests { fn test_unhandled_prefix() { let mut app = Application::new() .prefix("/test") - .resource("/test", |r| r.h(httpcodes::HTTPOk)) + .resource("/test", |r| r.h(httpcodes::HttpOk)) .finish(); assert!(app.handle(HttpRequest::default()).is_err()); } @@ -490,7 +490,7 @@ mod tests { #[test] fn test_state() { let mut app = Application::with_state(10) - .resource("/", |r| r.h(httpcodes::HTTPOk)) + .resource("/", |r| r.h(httpcodes::HttpOk)) .finish(); let req = HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone()); let resp = app.run(req); @@ -501,7 +501,7 @@ mod tests { fn test_prefix() { let mut app = Application::new() .prefix("/test") - .resource("/blah", |r| r.h(httpcodes::HTTPOk)) + .resource("/blah", |r| r.h(httpcodes::HttpOk)) .finish(); let req = TestRequest::with_uri("/test").finish(); let resp = app.handle(req); @@ -523,7 +523,7 @@ mod tests { #[test] fn test_handler() { let mut app = Application::new() - .handler("/test", httpcodes::HTTPOk) + .handler("/test", httpcodes::HttpOk) .finish(); let req = TestRequest::with_uri("/test").finish(); @@ -551,7 +551,7 @@ mod tests { fn test_handler_prefix() { let mut app = Application::new() .prefix("/app") - .handler("/test", httpcodes::HTTPOk) + .handler("/test", httpcodes::HttpOk) .finish(); let req = TestRequest::with_uri("/test").finish(); diff --git a/src/client/request.rs b/src/client/request.rs index 92960d7fe..42682a30c 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -516,7 +516,7 @@ impl ClientRequestBuilder { self.header(header::ACCEPT_ENCODING, "gzip, deflate"); } } - + let mut request = self.request.take().expect("cannot reuse request builder"); // set cookies diff --git a/src/error.rs b/src/error.rs index d0497073a..6abbf7a0f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -24,7 +24,7 @@ use body::Body; use handler::Responder; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use httpcodes::{self, HTTPExpectationFailed}; +use httpcodes::{self, HttpExpectationFailed}; /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) /// for actix web operations @@ -336,7 +336,7 @@ pub enum ExpectError { impl ResponseError for ExpectError { fn error_response(&self) -> HttpResponse { - HTTPExpectationFailed.with_body("Unknown Expect") + HttpExpectationFailed.with_body("Unknown Expect") } } @@ -386,9 +386,9 @@ impl ResponseError for UrlencodedError { fn error_response(&self) -> HttpResponse { match *self { - UrlencodedError::Overflow => httpcodes::HTTPPayloadTooLarge.into(), - UrlencodedError::UnknownLength => httpcodes::HTTPLengthRequired.into(), - _ => httpcodes::HTTPBadRequest.into(), + UrlencodedError::Overflow => httpcodes::HttpPayloadTooLarge.into(), + UrlencodedError::UnknownLength => httpcodes::HttpLengthRequired.into(), + _ => httpcodes::HttpBadRequest.into(), } } } @@ -421,8 +421,8 @@ impl ResponseError for JsonPayloadError { fn error_response(&self) -> HttpResponse { match *self { - JsonPayloadError::Overflow => httpcodes::HTTPPayloadTooLarge.into(), - _ => httpcodes::HTTPBadRequest.into(), + JsonPayloadError::Overflow => httpcodes::HttpPayloadTooLarge.into(), + _ => httpcodes::HttpBadRequest.into(), } } } diff --git a/src/fs.rs b/src/fs.rs index b7588dc51..8525c02fd 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -15,7 +15,7 @@ use handler::{Handler, Responder}; use headers::ContentEncoding; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use httpcodes::{HTTPOk, HTTPFound}; +use httpcodes::{HttpOk, HttpFound}; /// A file with an associated name; responds with the Content-Type based on the /// file extension. @@ -84,7 +84,7 @@ impl Responder for NamedFile { type Error = io::Error; fn respond_to(mut self, _: HttpRequest) -> Result { - let mut resp = HTTPOk.build(); + let mut resp = HttpOk.build(); resp.content_encoding(ContentEncoding::Identity); if let Some(ext) = self.path().extension() { let mime = get_mime_type(&ext.to_string_lossy()); @@ -164,7 +164,7 @@ impl Responder for Directory {
    \ {}\
\n", index_of, index_of, body); - Ok(HTTPOk.build() + Ok(HttpOk.build() .content_type("text/html; charset=utf-8") .body(html).unwrap()) } @@ -289,7 +289,7 @@ impl Handler for StaticFiles { } new_path.push_str(redir_index); Ok(FilesystemElement::Redirect( - HTTPFound + HttpFound .build() .header::<_, &str>("LOCATION", &new_path) .finish().unwrap())) diff --git a/src/handler.rs b/src/handler.rs index b8e074725..4aa5ec5b4 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -309,7 +309,7 @@ impl RouteHandler for AsyncHandler /// # use actix_web::*; /// # /// # fn index(req: HttpRequest) -> httpcodes::StaticResponse { -/// # httpcodes::HTTPOk +/// # httpcodes::HttpOk /// # } /// fn main() { /// let app = Application::new() diff --git a/src/httpcodes.rs b/src/httpcodes.rs index 972a684bd..820b51ed2 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -7,67 +7,174 @@ use handler::{Reply, Handler, RouteHandler, Responder}; use httprequest::HttpRequest; use httpresponse::{HttpResponse, HttpResponseBuilder}; +pub const HttpOk: StaticResponse = StaticResponse(StatusCode::OK); +pub const HttpCreated: StaticResponse = StaticResponse(StatusCode::CREATED); +pub const HttpAccepted: StaticResponse = StaticResponse(StatusCode::ACCEPTED); +pub const HttpNonAuthoritativeInformation: StaticResponse = + StaticResponse(StatusCode::NON_AUTHORITATIVE_INFORMATION); +pub const HttpNoContent: StaticResponse = StaticResponse(StatusCode::NO_CONTENT); +pub const HttpResetContent: StaticResponse = StaticResponse(StatusCode::RESET_CONTENT); +pub const HttpPartialContent: StaticResponse = StaticResponse(StatusCode::PARTIAL_CONTENT); +pub const HttpMultiStatus: StaticResponse = StaticResponse(StatusCode::MULTI_STATUS); +pub const HttpAlreadyReported: StaticResponse = StaticResponse(StatusCode::ALREADY_REPORTED); + +pub const HttpMultipleChoices: StaticResponse = StaticResponse(StatusCode::MULTIPLE_CHOICES); +pub const HttpMovedPermanenty: StaticResponse = StaticResponse(StatusCode::MOVED_PERMANENTLY); +pub const HttpFound: StaticResponse = StaticResponse(StatusCode::FOUND); +pub const HttpSeeOther: StaticResponse = StaticResponse(StatusCode::SEE_OTHER); +pub const HttpNotModified: StaticResponse = StaticResponse(StatusCode::NOT_MODIFIED); +pub const HttpUseProxy: StaticResponse = StaticResponse(StatusCode::USE_PROXY); +pub const HttpTemporaryRedirect: StaticResponse = + StaticResponse(StatusCode::TEMPORARY_REDIRECT); +pub const HttpPermanentRedirect: StaticResponse = + StaticResponse(StatusCode::PERMANENT_REDIRECT); + +pub const HttpBadRequest: StaticResponse = StaticResponse(StatusCode::BAD_REQUEST); +pub const HttpUnauthorized: StaticResponse = StaticResponse(StatusCode::UNAUTHORIZED); +pub const HttpPaymentRequired: StaticResponse = StaticResponse(StatusCode::PAYMENT_REQUIRED); +pub const HttpForbidden: StaticResponse = StaticResponse(StatusCode::FORBIDDEN); +pub const HttpNotFound: StaticResponse = StaticResponse(StatusCode::NOT_FOUND); +pub const HttpMethodNotAllowed: StaticResponse = + StaticResponse(StatusCode::METHOD_NOT_ALLOWED); +pub const HttpNotAcceptable: StaticResponse = StaticResponse(StatusCode::NOT_ACCEPTABLE); +pub const HttpProxyAuthenticationRequired: StaticResponse = + StaticResponse(StatusCode::PROXY_AUTHENTICATION_REQUIRED); +pub const HttpRequestTimeout: StaticResponse = StaticResponse(StatusCode::REQUEST_TIMEOUT); +pub const HttpConflict: StaticResponse = StaticResponse(StatusCode::CONFLICT); +pub const HttpGone: StaticResponse = StaticResponse(StatusCode::GONE); +pub const HttpLengthRequired: StaticResponse = StaticResponse(StatusCode::LENGTH_REQUIRED); +pub const HttpPreconditionFailed: StaticResponse = + StaticResponse(StatusCode::PRECONDITION_FAILED); +pub const HttpPayloadTooLarge: StaticResponse = StaticResponse(StatusCode::PAYLOAD_TOO_LARGE); +pub const HttpUriTooLong: StaticResponse = StaticResponse(StatusCode::URI_TOO_LONG); +pub const HttpUnsupportedMediaType: StaticResponse = + StaticResponse(StatusCode::UNSUPPORTED_MEDIA_TYPE); +pub const HttpRangeNotSatisfiable: StaticResponse = + StaticResponse(StatusCode::RANGE_NOT_SATISFIABLE); +pub const HttpExpectationFailed: StaticResponse = + StaticResponse(StatusCode::EXPECTATION_FAILED); + +pub const HttpInternalServerError: StaticResponse = + StaticResponse(StatusCode::INTERNAL_SERVER_ERROR); +pub const HttpNotImplemented: StaticResponse = StaticResponse(StatusCode::NOT_IMPLEMENTED); +pub const HttpBadGateway: StaticResponse = StaticResponse(StatusCode::BAD_GATEWAY); +pub const HttpServiceUnavailable: StaticResponse = + StaticResponse(StatusCode::SERVICE_UNAVAILABLE); +pub const HttpGatewayTimeout: StaticResponse = + StaticResponse(StatusCode::GATEWAY_TIMEOUT); +pub const HttpVersionNotSupported: StaticResponse = + StaticResponse(StatusCode::HTTP_VERSION_NOT_SUPPORTED); +pub const HttpVariantAlsoNegotiates: StaticResponse = + StaticResponse(StatusCode::VARIANT_ALSO_NEGOTIATES); +pub const HttpInsufficientStorage: StaticResponse = + StaticResponse(StatusCode::INSUFFICIENT_STORAGE); +pub const HttpLoopDetected: StaticResponse = StaticResponse(StatusCode::LOOP_DETECTED); + +#[doc(hidden)] pub const HTTPOk: StaticResponse = StaticResponse(StatusCode::OK); +#[doc(hidden)] pub const HTTPCreated: StaticResponse = StaticResponse(StatusCode::CREATED); +#[doc(hidden)] pub const HTTPAccepted: StaticResponse = StaticResponse(StatusCode::ACCEPTED); +#[doc(hidden)] pub const HTTPNonAuthoritativeInformation: StaticResponse = StaticResponse(StatusCode::NON_AUTHORITATIVE_INFORMATION); +#[doc(hidden)] pub const HTTPNoContent: StaticResponse = StaticResponse(StatusCode::NO_CONTENT); +#[doc(hidden)] pub const HTTPResetContent: StaticResponse = StaticResponse(StatusCode::RESET_CONTENT); +#[doc(hidden)] pub const HTTPPartialContent: StaticResponse = StaticResponse(StatusCode::PARTIAL_CONTENT); +#[doc(hidden)] pub const HTTPMultiStatus: StaticResponse = StaticResponse(StatusCode::MULTI_STATUS); +#[doc(hidden)] pub const HTTPAlreadyReported: StaticResponse = StaticResponse(StatusCode::ALREADY_REPORTED); +#[doc(hidden)] pub const HTTPMultipleChoices: StaticResponse = StaticResponse(StatusCode::MULTIPLE_CHOICES); +#[doc(hidden)] pub const HTTPMovedPermanenty: StaticResponse = StaticResponse(StatusCode::MOVED_PERMANENTLY); +#[doc(hidden)] pub const HTTPFound: StaticResponse = StaticResponse(StatusCode::FOUND); +#[doc(hidden)] pub const HTTPSeeOther: StaticResponse = StaticResponse(StatusCode::SEE_OTHER); +#[doc(hidden)] pub const HTTPNotModified: StaticResponse = StaticResponse(StatusCode::NOT_MODIFIED); +#[doc(hidden)] pub const HTTPUseProxy: StaticResponse = StaticResponse(StatusCode::USE_PROXY); +#[doc(hidden)] pub const HTTPTemporaryRedirect: StaticResponse = StaticResponse(StatusCode::TEMPORARY_REDIRECT); +#[doc(hidden)] pub const HTTPPermanentRedirect: StaticResponse = StaticResponse(StatusCode::PERMANENT_REDIRECT); +#[doc(hidden)] pub const HTTPBadRequest: StaticResponse = StaticResponse(StatusCode::BAD_REQUEST); +#[doc(hidden)] pub const HTTPUnauthorized: StaticResponse = StaticResponse(StatusCode::UNAUTHORIZED); +#[doc(hidden)] pub const HTTPPaymentRequired: StaticResponse = StaticResponse(StatusCode::PAYMENT_REQUIRED); +#[doc(hidden)] pub const HTTPForbidden: StaticResponse = StaticResponse(StatusCode::FORBIDDEN); +#[doc(hidden)] pub const HTTPNotFound: StaticResponse = StaticResponse(StatusCode::NOT_FOUND); +#[doc(hidden)] pub const HTTPMethodNotAllowed: StaticResponse = StaticResponse(StatusCode::METHOD_NOT_ALLOWED); +#[doc(hidden)] pub const HTTPNotAcceptable: StaticResponse = StaticResponse(StatusCode::NOT_ACCEPTABLE); +#[doc(hidden)] pub const HTTPProxyAuthenticationRequired: StaticResponse = StaticResponse(StatusCode::PROXY_AUTHENTICATION_REQUIRED); +#[doc(hidden)] pub const HTTPRequestTimeout: StaticResponse = StaticResponse(StatusCode::REQUEST_TIMEOUT); +#[doc(hidden)] pub const HTTPConflict: StaticResponse = StaticResponse(StatusCode::CONFLICT); +#[doc(hidden)] pub const HTTPGone: StaticResponse = StaticResponse(StatusCode::GONE); +#[doc(hidden)] pub const HTTPLengthRequired: StaticResponse = StaticResponse(StatusCode::LENGTH_REQUIRED); +#[doc(hidden)] pub const HTTPPreconditionFailed: StaticResponse = StaticResponse(StatusCode::PRECONDITION_FAILED); +#[doc(hidden)] pub const HTTPPayloadTooLarge: StaticResponse = StaticResponse(StatusCode::PAYLOAD_TOO_LARGE); +#[doc(hidden)] pub const HTTPUriTooLong: StaticResponse = StaticResponse(StatusCode::URI_TOO_LONG); +#[doc(hidden)] pub const HTTPUnsupportedMediaType: StaticResponse = StaticResponse(StatusCode::UNSUPPORTED_MEDIA_TYPE); +#[doc(hidden)] pub const HTTPRangeNotSatisfiable: StaticResponse = StaticResponse(StatusCode::RANGE_NOT_SATISFIABLE); +#[doc(hidden)] pub const HTTPExpectationFailed: StaticResponse = StaticResponse(StatusCode::EXPECTATION_FAILED); +#[doc(hidden)] pub const HTTPInternalServerError: StaticResponse = StaticResponse(StatusCode::INTERNAL_SERVER_ERROR); +#[doc(hidden)] pub const HTTPNotImplemented: StaticResponse = StaticResponse(StatusCode::NOT_IMPLEMENTED); +#[doc(hidden)] pub const HTTPBadGateway: StaticResponse = StaticResponse(StatusCode::BAD_GATEWAY); +#[doc(hidden)] pub const HTTPServiceUnavailable: StaticResponse = StaticResponse(StatusCode::SERVICE_UNAVAILABLE); +#[doc(hidden)] pub const HTTPGatewayTimeout: StaticResponse = StaticResponse(StatusCode::GATEWAY_TIMEOUT); +#[doc(hidden)] pub const HTTPVersionNotSupported: StaticResponse = StaticResponse(StatusCode::HTTP_VERSION_NOT_SUPPORTED); +#[doc(hidden)] pub const HTTPVariantAlsoNegotiates: StaticResponse = StaticResponse(StatusCode::VARIANT_ALSO_NEGOTIATES); +#[doc(hidden)] pub const HTTPInsufficientStorage: StaticResponse = StaticResponse(StatusCode::INSUFFICIENT_STORAGE); +#[doc(hidden)] pub const HTTPLoopDetected: StaticResponse = StaticResponse(StatusCode::LOOP_DETECTED); diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 2d8b1659d..60132136c 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -114,7 +114,7 @@ pub trait HttpMessage { /// .from_err() /// .and_then(|bytes: Bytes| { // <- complete body /// println!("==== BODY ==== {:?}", bytes); - /// Ok(httpcodes::HTTPOk.into()) + /// Ok(httpcodes::HttpOk.into()) /// }).responder() /// } /// # fn main() {} @@ -148,7 +148,7 @@ pub trait HttpMessage { /// .from_err() /// .and_then(|params| { // <- url encoded parameters /// println!("==== BODY ==== {:?}", params); - /// ok(httpcodes::HTTPOk.into()) + /// ok(httpcodes::HttpOk.into()) /// }) /// .responder() /// } @@ -187,7 +187,7 @@ pub trait HttpMessage { /// .from_err() /// .and_then(|val: MyObj| { // <- deserialized value /// println!("==== BODY ==== {:?}", val); - /// Ok(httpcodes::HTTPOk.into()) + /// Ok(httpcodes::HttpOk.into()) /// }).responder() /// } /// # fn main() {} diff --git a/src/httprequest.rs b/src/httprequest.rs index b6a5fdcba..ae7c21ba9 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -249,14 +249,14 @@ impl HttpRequest { /// # /// fn index(req: HttpRequest) -> HttpResponse { /// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource - /// HTTPOk.into() + /// HttpOk.into() /// } /// /// fn main() { /// let app = Application::new() /// .resource("/test/{one}/{two}/{three}", |r| { /// r.name("foo"); // <- set resource name, then it could be used in `url_for` - /// r.method(Method::GET).f(|_| httpcodes::HTTPOk); + /// r.method(Method::GET).f(|_| httpcodes::HttpOk); /// }) /// .finish(); /// } diff --git a/src/httpresponse.rs b/src/httpresponse.rs index cf702c67d..9af932b12 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -252,7 +252,7 @@ impl HttpResponseBuilder { /// use http::header; /// /// fn index(req: HttpRequest) -> Result { - /// Ok(HTTPOk.build() + /// Ok(HttpOk.build() /// .header("X-TEST", "value") /// .header(header::CONTENT_TYPE, "application/json") /// .finish()?) @@ -372,7 +372,7 @@ impl HttpResponseBuilder { /// use actix_web::headers::Cookie; /// /// fn index(req: HttpRequest) -> Result { - /// Ok(HTTPOk.build() + /// Ok(HttpOk.build() /// .cookie( /// Cookie::build("name", "value") /// .domain("www.rust-lang.org") @@ -753,7 +753,7 @@ mod tests { Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); let cookies = req.cookies().unwrap(); - let resp = httpcodes::HTTPOk + let resp = httpcodes::HttpOk .build() .cookie(headers::Cookie::build("name", "value") .domain("www.rust-lang.org") diff --git a/src/json.rs b/src/json.rs index 56b2a46aa..a41125b41 100644 --- a/src/json.rs +++ b/src/json.rs @@ -75,7 +75,7 @@ impl Responder for Json { /// .from_err() /// .and_then(|val: MyObj| { // <- deserialized value /// println!("==== BODY ==== {:?}", val); -/// Ok(httpcodes::HTTPOk.into()) +/// Ok(httpcodes::HttpOk.into()) /// }).responder() /// } /// # fn main() {} diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index c949bcc49..25ae747ce 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -38,8 +38,8 @@ //! .max_age(3600) //! .finish().expect("Can not create CORS middleware") //! .register(r); // <- Register CORS middleware -//! r.method(Method::GET).f(|_| httpcodes::HTTPOk); -//! r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed); +//! r.method(Method::GET).f(|_| httpcodes::HttpOk); +//! r.method(Method::HEAD).f(|_| httpcodes::HttpMethodNotAllowed); //! }) //! .finish(); //! } @@ -58,7 +58,7 @@ use resource::Resource; use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use httpcodes::{HTTPOk, HTTPBadRequest}; +use httpcodes::{HttpOk, HttpBadRequest}; use middleware::{Middleware, Response, Started}; /// A set of errors that can occur during processing CORS @@ -110,7 +110,7 @@ pub enum CorsBuilderError { impl ResponseError for CorsError { fn error_response(&self) -> HttpResponse { - HTTPBadRequest.build().body(format!("{}", self)).unwrap() + HttpBadRequest.build().body(format!("{}", self)).unwrap() } } @@ -219,7 +219,7 @@ impl Cors { /// method, but in that case *Cors* middleware wont be able to handle *OPTIONS* /// requests. pub fn register(self, resource: &mut Resource) { - resource.method(Method::OPTIONS).h(HTTPOk); + resource.method(Method::OPTIONS).h(HttpOk); resource.middleware(self); } @@ -307,7 +307,7 @@ impl Middleware for Cors { }; Ok(Started::Response( - HTTPOk.build() + HttpOk.build() .if_some(self.max_age.as_ref(), |max_age, resp| { let _ = resp.header( header::ACCESS_CONTROL_MAX_AGE, format!("{}", max_age).as_str());}) @@ -823,7 +823,7 @@ mod tests { .method(Method::OPTIONS) .finish(); - let resp: HttpResponse = HTTPOk.into(); + let resp: HttpResponse = HttpOk.into(); let resp = cors.response(&mut req, resp).unwrap().response(); assert_eq!( &b"*"[..], @@ -832,7 +832,7 @@ mod tests { &b"Origin"[..], resp.headers().get(header::VARY).unwrap().as_bytes()); - let resp: HttpResponse = HTTPOk.build() + let resp: HttpResponse = HttpOk.build() .header(header::VARY, "Accept") .finish().unwrap(); let resp = cors.response(&mut req, resp).unwrap().response(); @@ -844,7 +844,7 @@ mod tests { .disable_vary_header() .allowed_origin("https://www.example.com") .finish().unwrap(); - let resp: HttpResponse = HTTPOk.into(); + let resp: HttpResponse = HttpOk.into(); let resp = cors.response(&mut req, resp).unwrap().response(); assert_eq!( &b"https://www.example.com"[..], diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index 344c69a6a..0dfd38511 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -22,8 +22,8 @@ use middleware::{Response, Middleware}; /// .header("X-Version", "0.2") /// .finish()) /// .resource("/test", |r| { -/// r.method(Method::GET).f(|_| httpcodes::HTTPOk); -/// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed); +/// r.method(Method::GET).f(|_| httpcodes::HttpOk); +/// r.method(Method::HEAD).f(|_| httpcodes::HttpMethodNotAllowed); /// }) /// .finish(); /// } diff --git a/src/pred.rs b/src/pred.rs index 22abf6fec..b49d4ec58 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -29,7 +29,7 @@ pub trait Predicate { /// Application::new() /// .resource("/index.html", |r| r.route() /// .filter(pred::Any(pred::Get()).or(pred::Post())) -/// .h(HTTPMethodNotAllowed)); +/// .h(HttpMethodNotAllowed)); /// } /// ``` pub fn Any + 'static>(pred: P) -> AnyPredicate @@ -73,7 +73,7 @@ impl Predicate for AnyPredicate { /// .resource("/index.html", |r| r.route() /// .filter(pred::All(pred::Get()) /// .and(pred::Header("content-type", "plain/text"))) -/// .h(HTTPMethodNotAllowed)); +/// .h(HttpMethodNotAllowed)); /// } /// ``` pub fn All + 'static>(pred: P) -> AllPredicate { diff --git a/src/route.rs b/src/route.rs index 5880d669f..856d6fa85 100644 --- a/src/route.rs +++ b/src/route.rs @@ -8,7 +8,7 @@ use pred::Predicate; use handler::{Reply, ReplyItem, Handler, Responder, RouteHandler, AsyncHandler, WrapHandler}; use middleware::{Middleware, Response as MiddlewareResponse, Started as MiddlewareStarted}; -use httpcodes::HTTPNotFound; +use httpcodes::HttpNotFound; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -26,7 +26,7 @@ impl Default for Route { fn default() -> Route { Route { preds: Vec::new(), - handler: InnerHandler::new(|_| HTTPNotFound), + handler: InnerHandler::new(|_| HttpNotFound), } } } @@ -67,7 +67,7 @@ impl Route { /// r.route() /// .filter(pred::Get()) /// .filter(pred::Header("content-type", "text/plain")) - /// .f(|req| HTTPOk) + /// .f(|req| HttpOk) /// ) /// # .finish(); /// # } diff --git a/src/server/h1.rs b/src/server/h1.rs index 4d528918a..8f0917dac 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -15,7 +15,7 @@ use futures::{Future, Poll, Async}; use tokio_core::reactor::Timeout; use pipeline::Pipeline; -use httpcodes::HTTPNotFound; +use httpcodes::HttpNotFound; use httprequest::HttpRequest; use error::{ParseError, PayloadError, ResponseError}; use payload::{Payload, PayloadWriter, PayloadStatus}; @@ -151,7 +151,7 @@ impl Http1 } self.tasks.push_back( - Entry {pipe: Pipeline::error(HTTPNotFound), + Entry {pipe: Pipeline::error(HttpNotFound), flags: EntryFlags::empty()}); continue }, diff --git a/src/server/h2.rs b/src/server/h2.rs index ed75c97f3..02951593e 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -18,7 +18,7 @@ use tokio_core::reactor::Timeout; use pipeline::Pipeline; use error::PayloadError; -use httpcodes::HTTPNotFound; +use httpcodes::HttpNotFound; use httpmessage::HttpMessage; use httprequest::HttpRequest; use payload::{Payload, PayloadWriter, PayloadStatus}; @@ -298,7 +298,7 @@ impl Entry { } } - Entry {task: task.unwrap_or_else(|| Pipeline::error(HTTPNotFound)), + Entry {task: task.unwrap_or_else(|| Pipeline::error(HttpNotFound)), payload: psender, stream: H2Writer::new(resp, settings.get_shared_bytes()), flags: EntryFlags::empty(), diff --git a/src/server/srv.rs b/src/server/srv.rs index 63f23d245..d886d4c59 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -261,7 +261,7 @@ impl HttpServer /// /// HttpServer::new( /// || Application::new() - /// .resource("/", |r| r.h(httpcodes::HTTPOk))) + /// .resource("/", |r| r.h(httpcodes::HttpOk))) /// .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") /// .start(); /// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); @@ -312,7 +312,7 @@ impl HttpServer /// fn main() { /// HttpServer::new( /// || Application::new() - /// .resource("/", |r| r.h(httpcodes::HTTPOk))) + /// .resource("/", |r| r.h(httpcodes::HttpOk))) /// .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") /// .run(); /// } diff --git a/src/test.rs b/src/test.rs index b1d467b69..fa5cf7a10 100644 --- a/src/test.rs +++ b/src/test.rs @@ -41,7 +41,7 @@ use client::{ClientRequest, ClientRequestBuilder}; /// # use actix_web::*; /// # /// # fn my_handler(req: HttpRequest) -> HttpResponse { -/// # httpcodes::HTTPOk.into() +/// # httpcodes::HttpOk.into() /// # } /// # /// # fn main() { @@ -282,9 +282,9 @@ impl Iterator for TestApp { /// /// fn index(req: HttpRequest) -> HttpResponse { /// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { -/// httpcodes::HTTPOk.into() +/// httpcodes::HttpOk.into() /// } else { -/// httpcodes::HTTPBadRequest.into() +/// httpcodes::HttpBadRequest.into() /// } /// } /// @@ -403,7 +403,7 @@ impl TestRequest { self.payload = Some(payload); self } - + /// Complete request creation and generate `HttpRequest` instance pub fn finish(self) -> HttpRequest { let TestRequest { state, method, uri, version, headers, params, cookies, payload } = self; diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 8cd4d9207..fb0936574 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -55,7 +55,7 @@ use error::{Error, PayloadError, ResponseError}; use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder}; -use httpcodes::{HTTPBadRequest, HTTPMethodNotAllowed}; +use httpcodes::{HttpBadRequest, HttpMethodNotAllowed}; mod frame; mod proto; @@ -139,22 +139,22 @@ impl ResponseError for WsHandshakeError { fn error_response(&self) -> HttpResponse { match *self { WsHandshakeError::GetMethodRequired => { - HTTPMethodNotAllowed + HttpMethodNotAllowed .build() .header(header::ALLOW, "GET") .finish() .unwrap() } WsHandshakeError::NoWebsocketUpgrade => - HTTPBadRequest.with_reason("No WebSocket UPGRADE header found"), + HttpBadRequest.with_reason("No WebSocket UPGRADE header found"), WsHandshakeError::NoConnectionUpgrade => - HTTPBadRequest.with_reason("No CONNECTION upgrade"), + HttpBadRequest.with_reason("No CONNECTION upgrade"), WsHandshakeError::NoVersionHeader => - HTTPBadRequest.with_reason("Websocket version header is required"), + HttpBadRequest.with_reason("Websocket version header is required"), WsHandshakeError::UnsupportedVersion => - HTTPBadRequest.with_reason("Unsupported version"), + HttpBadRequest.with_reason("Unsupported version"), WsHandshakeError::BadWebsocketKey => - HTTPBadRequest.with_reason("Handshake error"), + HttpBadRequest.with_reason("Handshake error"), } } } From 1fea4bd9a645808fafd5f14566b609580867333a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 1 Mar 2018 20:01:25 -0800 Subject: [PATCH 51/84] prepare release --- Cargo.toml | 2 +- src/server/utils.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cf050ed6c..a5642e507 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.4.0" +version = "0.4.1" authors = ["Nikolay Kim "] description = "Actix web is a small, pragmatic, extremely fast, web framework for Rust." readme = "README.md" diff --git a/src/server/utils.rs b/src/server/utils.rs index 79e0a11c5..5bb59521b 100644 --- a/src/server/utils.rs +++ b/src/server/utils.rs @@ -4,8 +4,8 @@ use futures::{Async, Poll}; use super::IoStream; -const LW_BUFFER_SIZE: usize = 4096; -const HW_BUFFER_SIZE: usize = 16_384; +const LW_BUFFER_SIZE: usize = 8192; +const HW_BUFFER_SIZE: usize = 32_768; pub fn read_from_io(io: &mut T, buf: &mut BytesMut) -> Poll { From b640b49b05a93be973c130468ee9c8dff56e3ca2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 1 Mar 2018 20:13:50 -0800 Subject: [PATCH 52/84] adjust low buf size --- src/server/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/utils.rs b/src/server/utils.rs index 5bb59521b..bbc890e94 100644 --- a/src/server/utils.rs +++ b/src/server/utils.rs @@ -4,7 +4,7 @@ use futures::{Async, Poll}; use super::IoStream; -const LW_BUFFER_SIZE: usize = 8192; +const LW_BUFFER_SIZE: usize = 4096; const HW_BUFFER_SIZE: usize = 32_768; From 10f57dac31287b9c5deb1d4044fdd50f01e2e1a4 Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Fri, 2 Mar 2018 19:35:41 +0100 Subject: [PATCH 53/84] add csrf filter middleware --- src/middleware/csrf.rs | 266 +++++++++++++++++++++++++++++++++++++++++ src/middleware/mod.rs | 1 + 2 files changed, 267 insertions(+) create mode 100644 src/middleware/csrf.rs diff --git a/src/middleware/csrf.rs b/src/middleware/csrf.rs new file mode 100644 index 000000000..5385f5d4d --- /dev/null +++ b/src/middleware/csrf.rs @@ -0,0 +1,266 @@ +//! A filter for cross-site request forgery (CSRF). +//! +//! This middleware is stateless and [based on request +//! headers](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Verifying_Same_Origin_with_Standard_Headers). +//! +//! By default requests are allowed only if one of these is true: +//! +//! * The request method is safe (`GET`, `HEAD`, `OPTIONS`). It is the +//! applications responsibility to ensure these methods cannot be used to +//! execute unwanted actions. Note that upgrade requests for websockets are +//! also considered safe. +//! * The `Origin` header (added automatically by the browser) matches one +//! of the allowed origins. +//! * There is no `Origin` header but the `Referer` header matches one of +//! the allowed origins. +//! +//! Use [`CsrfFilterBuilder::allow_xhr()`](struct.CsrfFilterBuilder.html#method.allow_xhr) +//! if you want to allow requests with unsafe methods via +//! [CORS](../cors/struct.Cors.html). +//! +//! # Example +//! +//! ``` +//! # extern crate actix_web; +//! # use actix_web::*; +//! +//! use actix_web::middleware::csrf; +//! +//! fn handle_post(_req: HttpRequest) -> &'static str { +//! "This action should only be triggered with requests from the same site" +//! } +//! +//! fn main() { +//! let app = Application::new() +//! .middleware( +//! csrf::CsrfFilter::build() +//! .allowed_origin("https://www.example.com") +//! .finish()) +//! .resource("/", |r| { +//! r.method(Method::GET).f(|_| httpcodes::HttpOk); +//! r.method(Method::POST).f(handle_post); +//! }) +//! .finish(); +//! } +//! ``` +//! +//! In this example the entire application is protected from CSRF. + +use std::borrow::Cow; +use std::collections::HashSet; + +use bytes::Bytes; +use error::{Result, ResponseError}; +use http::{HeaderMap, HttpTryFrom, Uri, header}; +use httprequest::HttpRequest; +use httpresponse::HttpResponse; +use httpmessage::HttpMessage; +use httpcodes::HttpForbidden; +use middleware::{Middleware, Started}; + +/// Potential cross-site request forgery detected. +#[derive(Debug, Fail)] +pub enum CsrfError { + /// The HTTP request header `Origin` was required but not provided. + #[fail(display="Origin header required")] + MissingOrigin, + /// The HTTP request header `Origin` could not be parsed correctly. + #[fail(display="Could not parse Origin header")] + BadOrigin, + /// The cross-site request was denied. + #[fail(display="Cross-site request denied")] + CsrDenied, +} + +impl ResponseError for CsrfError { + fn error_response(&self) -> HttpResponse { + HttpForbidden.build().body(self.to_string()).unwrap() + } +} + +fn uri_origin(uri: &Uri) -> Option { + match (uri.scheme_part(), uri.host(), uri.port()) { + (Some(scheme), Some(host), Some(port)) => { + Some(format!("{}://{}:{}", scheme, host, port)) + } + (Some(scheme), Some(host), None) => { + Some(format!("{}://{}", scheme, host)) + } + _ => None + } +} + +fn origin(headers: &HeaderMap) -> Option, CsrfError>> { + headers.get(header::ORIGIN) + .map(|origin| { + origin + .to_str() + .map_err(|_| CsrfError::BadOrigin) + .map(|o| o.into()) + }) + .or_else(|| { + headers.get(header::REFERER) + .map(|referer| { + Uri::try_from(Bytes::from(referer.as_bytes())) + .ok() + .as_ref() + .and_then(uri_origin) + .ok_or(CsrfError::BadOrigin) + .map(|o| o.into()) + }) + }) +} + +/// A middleware that filters cross-site requests. +pub struct CsrfFilter { + origins: HashSet, + allow_xhr: bool, + allow_missing_origin: bool, +} + +impl CsrfFilter { + /// Start building a `CsrfFilter`. + pub fn build() -> CsrfFilterBuilder { + CsrfFilterBuilder { + cors: CsrfFilter { + origins: HashSet::new(), + allow_xhr: false, + allow_missing_origin: false, + } + } + } + + fn validate(&self, req: &mut HttpRequest) -> Result<(), CsrfError> { + if req.method().is_safe() || (self.allow_xhr && req.headers().contains_key("x-requested-with")) { + Ok(()) + } else if let Some(header) = origin(req.headers()) { + match header { + Ok(ref origin) if self.origins.contains(origin.as_ref()) => Ok(()), + Ok(_) => Err(CsrfError::CsrDenied), + Err(err) => Err(err), + } + } else if self.allow_missing_origin { + Ok(()) + } else { + Err(CsrfError::MissingOrigin) + } + } +} + +impl Middleware for CsrfFilter { + fn start(&self, req: &mut HttpRequest) -> Result { + self.validate(req)?; + Ok(Started::Done) + } +} + +/// Used to build a `CsrfFilter`. +/// +/// To construct a CSRF filter: +/// +/// 1. Call [`CsrfFilter::build`](struct.CsrfFilter.html#method.build) to +/// start building. +/// 2. [Add](struct.CsrfFilterBuilder.html#method.allowed_origin) allowed +/// origins. +/// 3. Call [finish](struct.CsrfFilterBuilder.html#method.finish) to retrieve +/// the constructed filter. +/// +/// # Example +/// +/// ``` +/// use actix_web::middleware::csrf; +/// +/// let csrf = csrf::CsrfFilter::build() +/// .allowed_origin("https://www.example.com") +/// .finish(); +/// ``` +pub struct CsrfFilterBuilder { + cors: CsrfFilter, +} + +impl CsrfFilterBuilder { + /// Add an origin that is allowed to make requests. Will be verified + /// against the `Origin` request header. + pub fn allowed_origin(mut self, origin: &str) -> CsrfFilterBuilder { + self.cors.origins.insert(origin.to_owned()); + self + } + + /// Allow all requests with an `X-Requested-With` header. + /// + /// A cross-site attacker should not be able to send requests with custom + /// headers unless a CORS policy whitelists them. Therefore it should be + /// safe to allow requests with an `X-Requested-With` header (added + /// automatically by many JavaScript libraries). + /// + /// This is disabled by default, because in Safari it is possible to + /// circumvent this using redirects and Flash. + /// + /// Use this method to enable more lax filtering. + pub fn allow_xhr(mut self) -> CsrfFilterBuilder { + self.cors.allow_xhr = true; + self + } + + /// Allow requests if the expected `Origin` header is missing (and + /// there is no `Referer` to fall back on). + /// + /// The filter is conservative by default, but it should be safe to allow + /// missing `Origin` headers because a cross-site attacker cannot prevent + /// the browser from sending `Origin` on unsafe requests. + pub fn allow_missing_origin(mut self) -> CsrfFilterBuilder { + self.cors.allow_missing_origin = true; + self + } + + /// Finishes building the `CsrfFilter` instance. + pub fn finish(self) -> CsrfFilter { + self.cors + } +} + +#[cfg(test)] +mod tests { + use super::*; + use http::Method; + use test::TestRequest; + + #[test] + fn test_safe() { + let csrf = CsrfFilter::build() + .allowed_origin("https://www.example.com") + .finish(); + + let mut req = TestRequest::with_header("Origin", "https://www.w3.org") + .method(Method::HEAD) + .finish(); + + assert!(csrf.start(&mut req).is_ok()); + } + + #[test] + fn test_csrf() { + let csrf = CsrfFilter::build() + .allowed_origin("https://www.example.com") + .finish(); + + let mut req = TestRequest::with_header("Origin", "https://www.w3.org") + .method(Method::POST) + .finish(); + + assert!(csrf.start(&mut req).is_err()); + } + + #[test] + fn test_referer() { + let csrf = CsrfFilter::build() + .allowed_origin("https://www.example.com") + .finish(); + + let mut req = TestRequest::with_header("Referer", "https://www.example.com/some/path?query=param") + .method(Method::POST) + .finish(); + + assert!(csrf.start(&mut req).is_ok()); + } +} diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 4270c477b..cfda04b9e 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -9,6 +9,7 @@ mod logger; mod session; mod defaultheaders; pub mod cors; +pub mod csrf; pub use self::logger::Logger; pub use self::defaultheaders::{DefaultHeaders, DefaultHeadersBuilder}; pub use self::session::{RequestSession, Session, SessionImpl, SessionBackend, SessionStorage, From 3b2928a3914c8263f6f934388cff101287bcf65c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 2 Mar 2018 11:29:55 -0800 Subject: [PATCH 54/84] Better naming for websockets implementation --- CHANGES.md | 5 + examples/state/src/main.rs | 2 +- examples/websocket-chat/src/main.rs | 2 +- examples/websocket/src/client.rs | 8 +- examples/websocket/src/main.rs | 2 +- guide/src/qs_9.md | 2 +- src/test.rs | 6 +- src/ws/client.rs | 164 +++++++++++++++------------- src/ws/frame.rs | 20 ++-- src/ws/mod.rs | 87 ++++++++------- tests/test_server.rs | 2 +- tests/test_ws.rs | 2 +- 12 files changed, 168 insertions(+), 134 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ea1b65741..dc2cbc49b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## 0.4.2 (2018-03-xx) + +* Better naming for websockets implementation + + ## 0.4.1 (2018-03-01) * Rename `Route::p()` to `Route::filter()` diff --git a/examples/state/src/main.rs b/examples/state/src/main.rs index f40f779ed..0f7e0ec3b 100644 --- a/examples/state/src/main.rs +++ b/examples/state/src/main.rs @@ -36,7 +36,7 @@ impl Actor for MyWebSocket { type Context = ws::WebsocketContext; } -impl StreamHandler for MyWebSocket { +impl StreamHandler for MyWebSocket { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { self.counter += 1; diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs index b6783e83e..dccd768aa 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -92,7 +92,7 @@ impl Handler for WsChatSession { } /// WebSocket message handler -impl StreamHandler for WsChatSession { +impl StreamHandler for WsChatSession { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { println!("WEBSOCKET MESSAGE: {:?}", msg); diff --git a/examples/websocket/src/client.rs b/examples/websocket/src/client.rs index dddc53b7b..5eeb3bc41 100644 --- a/examples/websocket/src/client.rs +++ b/examples/websocket/src/client.rs @@ -12,7 +12,7 @@ use std::time::Duration; use actix::*; use futures::Future; -use actix_web::ws::{Message, WsError, WsClient, WsClientWriter}; +use actix_web::ws::{Message, ProtocolError, Client, ClientWriter}; fn main() { @@ -21,7 +21,7 @@ fn main() { let sys = actix::System::new("ws-example"); Arbiter::handle().spawn( - WsClient::new("http://127.0.0.1:8080/ws/") + Client::new("http://127.0.0.1:8080/ws/") .connect() .map_err(|e| { println!("Error: {}", e); @@ -53,7 +53,7 @@ fn main() { } -struct ChatClient(WsClientWriter); +struct ChatClient(ClientWriter); #[derive(Message)] struct ClientCommand(String); @@ -93,7 +93,7 @@ impl Handler for ChatClient { } /// Handle server websocket messages -impl StreamHandler for ChatClient { +impl StreamHandler for ChatClient { fn handle(&mut self, msg: Message, ctx: &mut Context) { match msg { diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index 7e0824546..f97b948de 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -25,7 +25,7 @@ impl Actor for MyWebSocket { } /// Handler for `ws::Message` -impl StreamHandler for MyWebSocket { +impl StreamHandler for MyWebSocket { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { // process websocket messages diff --git a/guide/src/qs_9.md b/guide/src/qs_9.md index 8200435e0..fa8b979ae 100644 --- a/guide/src/qs_9.md +++ b/guide/src/qs_9.md @@ -22,7 +22,7 @@ impl Actor for Ws { } /// Handler for ws::Message message -impl StreamHandler for Ws { +impl StreamHandler for Ws { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { match msg { diff --git a/src/test.rs b/src/test.rs index fa5cf7a10..8f5519459 100644 --- a/src/test.rs +++ b/src/test.rs @@ -14,6 +14,7 @@ use tokio_core::net::TcpListener; use tokio_core::reactor::Core; use net2::TcpBuilder; +use ws; use body::Binary; use error::Error; use handler::{Handler, Responder, ReplyItem}; @@ -25,7 +26,6 @@ use payload::Payload; use httprequest::HttpRequest; use httpresponse::HttpResponse; use server::{HttpServer, IntoHttpHandler, ServerSettings}; -use ws::{WsClient, WsClientError, WsClientReader, WsClientWriter}; use client::{ClientRequest, ClientRequestBuilder}; /// The `TestServer` type. @@ -180,9 +180,9 @@ impl TestServer { } /// Connect to websocket server - pub fn ws(&mut self) -> Result<(WsClientReader, WsClientWriter), WsClientError> { + pub fn ws(&mut self) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { let url = self.url("/"); - self.system.run_until_complete(WsClient::new(url).connect()) + self.system.run_until_complete(ws::Client::new(url).connect()) } /// Create `GET` request diff --git a/src/ws/client.rs b/src/ws/client.rs index e160016be..17c2e8320 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -25,14 +25,32 @@ use client::{ClientRequest, ClientRequestBuilder, ClientResponse, ClientConnector, SendRequest, SendRequestError, HttpResponseParserError}; -use super::{Message, WsError}; +use super::{Message, ProtocolError}; use super::frame::Frame; use super::proto::{CloseCode, OpCode}; +/// Backward compatibility +#[doc(hidden)] +#[deprecated(since="0.4.2", note="please use `ws::Client` instead")] +pub type WsClient = Client; +#[doc(hidden)] +#[deprecated(since="0.4.2", note="please use `ws::ClientError` instead")] +pub type WsClientError = ClientError; +#[doc(hidden)] +#[deprecated(since="0.4.2", note="please use `ws::ClientReader` instead")] +pub type WsClientReader = ClientReader; +#[doc(hidden)] +#[deprecated(since="0.4.2", note="please use `ws::ClientWriter` instead")] +pub type WsClientWriter = ClientWriter; +#[doc(hidden)] +#[deprecated(since="0.4.2", note="please use `ws::ClientHandshake` instead")] +pub type WsClientHandshake = ClientHandshake; + + /// Websocket client error #[derive(Fail, Debug)] -pub enum WsClientError { +pub enum ClientError { #[fail(display="Invalid url")] InvalidUrl, #[fail(display="Invalid response status")] @@ -56,46 +74,46 @@ pub enum WsClientError { #[fail(display="{}", _0)] SendRequest(SendRequestError), #[fail(display="{}", _0)] - Protocol(#[cause] WsError), + Protocol(#[cause] ProtocolError), #[fail(display="{}", _0)] Io(io::Error), #[fail(display="Disconnected")] Disconnected, } -impl From for WsClientError { - fn from(err: HttpError) -> WsClientError { - WsClientError::Http(err) +impl From for ClientError { + fn from(err: HttpError) -> ClientError { + ClientError::Http(err) } } -impl From for WsClientError { - fn from(err: UrlParseError) -> WsClientError { - WsClientError::Url(err) +impl From for ClientError { + fn from(err: UrlParseError) -> ClientError { + ClientError::Url(err) } } -impl From for WsClientError { - fn from(err: SendRequestError) -> WsClientError { - WsClientError::SendRequest(err) +impl From for ClientError { + fn from(err: SendRequestError) -> ClientError { + ClientError::SendRequest(err) } } -impl From for WsClientError { - fn from(err: WsError) -> WsClientError { - WsClientError::Protocol(err) +impl From for ClientError { + fn from(err: ProtocolError) -> ClientError { + ClientError::Protocol(err) } } -impl From for WsClientError { - fn from(err: io::Error) -> WsClientError { - WsClientError::Io(err) +impl From for ClientError { + fn from(err: io::Error) -> ClientError { + ClientError::Io(err) } } -impl From for WsClientError { - fn from(err: HttpResponseParserError) -> WsClientError { - WsClientError::ResponseParseError(err) +impl From for ClientError { + fn from(err: HttpResponseParserError) -> ClientError { + ClientError::ResponseParseError(err) } } @@ -104,9 +122,9 @@ impl From for WsClientError { /// Example of `WebSocket` client usage is available in /// [websocket example]( /// https://github.com/actix/actix-web/blob/master/examples/websocket/src/client.rs#L24) -pub struct WsClient { +pub struct Client { request: ClientRequestBuilder, - err: Option, + err: Option, http_err: Option, origin: Option, protocols: Option, @@ -114,16 +132,16 @@ pub struct WsClient { max_size: usize, } -impl WsClient { +impl Client { /// Create new websocket connection - pub fn new>(uri: S) -> WsClient { - WsClient::with_connector(uri, ClientConnector::from_registry()) + pub fn new>(uri: S) -> Client { + Client::with_connector(uri, ClientConnector::from_registry()) } /// Create new websocket connection with custom `ClientConnector` - pub fn with_connector>(uri: S, conn: Addr) -> WsClient { - let mut cl = WsClient { + pub fn with_connector>(uri: S, conn: Addr) -> Client { + let mut cl = Client { request: ClientRequest::build(), err: None, http_err: None, @@ -182,12 +200,12 @@ impl WsClient { } /// Connect to websocket server and do ws handshake - pub fn connect(&mut self) -> WsClientHandshake { + pub fn connect(&mut self) -> ClientHandshake { if let Some(e) = self.err.take() { - WsClientHandshake::error(e) + ClientHandshake::error(e) } else if let Some(e) = self.http_err.take() { - WsClientHandshake::error(e.into()) + ClientHandshake::error(e.into()) } else { // origin if let Some(origin) = self.origin.take() { @@ -205,42 +223,42 @@ impl WsClient { } let request = match self.request.finish() { Ok(req) => req, - Err(err) => return WsClientHandshake::error(err.into()), + Err(err) => return ClientHandshake::error(err.into()), }; if request.uri().host().is_none() { - return WsClientHandshake::error(WsClientError::InvalidUrl) + return ClientHandshake::error(ClientError::InvalidUrl) } if let Some(scheme) = request.uri().scheme_part() { if scheme != "http" && scheme != "https" && scheme != "ws" && scheme != "wss" { - return WsClientHandshake::error(WsClientError::InvalidUrl) + return ClientHandshake::error(ClientError::InvalidUrl) } } else { - return WsClientHandshake::error(WsClientError::InvalidUrl) + return ClientHandshake::error(ClientError::InvalidUrl) } // start handshake - WsClientHandshake::new(request, self.max_size) + ClientHandshake::new(request, self.max_size) } } } -struct WsInner { +struct Inner { tx: UnboundedSender, rx: PayloadHelper, closed: bool, } -pub struct WsClientHandshake { +pub struct ClientHandshake { request: Option, tx: Option>, key: String, - error: Option, + error: Option, max_size: usize, } -impl WsClientHandshake { - fn new(mut request: ClientRequest, max_size: usize) -> WsClientHandshake +impl ClientHandshake { + fn new(mut request: ClientRequest, max_size: usize) -> ClientHandshake { // Generate a random key for the `Sec-WebSocket-Key` header. // a base64-encoded (see Section 4 of [RFC4648]) value that, @@ -257,7 +275,7 @@ impl WsClientHandshake { Box::new(rx.map_err(|_| io::Error::new( io::ErrorKind::Other, "disconnected").into())))); - WsClientHandshake { + ClientHandshake { key, max_size, request: Some(request.send()), @@ -266,8 +284,8 @@ impl WsClientHandshake { } } - fn error(err: WsClientError) -> WsClientHandshake { - WsClientHandshake { + fn error(err: ClientError) -> ClientHandshake { + ClientHandshake { key: String::new(), request: None, tx: None, @@ -277,9 +295,9 @@ impl WsClientHandshake { } } -impl Future for WsClientHandshake { - type Item = (WsClientReader, WsClientWriter); - type Error = WsClientError; +impl Future for ClientHandshake { + type Item = (ClientReader, ClientWriter); + type Error = ClientError; fn poll(&mut self) -> Poll { if let Some(err) = self.error.take() { @@ -296,7 +314,7 @@ impl Future for WsClientHandshake { // verify response if resp.status() != StatusCode::SWITCHING_PROTOCOLS { - return Err(WsClientError::InvalidResponseStatus(resp.status())) + return Err(ClientError::InvalidResponseStatus(resp.status())) } // Check for "UPGRADE" to websocket header let has_hdr = if let Some(hdr) = resp.headers().get(header::UPGRADE) { @@ -310,22 +328,22 @@ impl Future for WsClientHandshake { }; if !has_hdr { trace!("Invalid upgrade header"); - return Err(WsClientError::InvalidUpgradeHeader) + return Err(ClientError::InvalidUpgradeHeader) } // Check for "CONNECTION" header if let Some(conn) = resp.headers().get(header::CONNECTION) { if let Ok(s) = conn.to_str() { if !s.to_lowercase().contains("upgrade") { trace!("Invalid connection header: {}", s); - return Err(WsClientError::InvalidConnectionHeader(conn.clone())) + return Err(ClientError::InvalidConnectionHeader(conn.clone())) } } else { trace!("Invalid connection header: {:?}", conn); - return Err(WsClientError::InvalidConnectionHeader(conn.clone())) + return Err(ClientError::InvalidConnectionHeader(conn.clone())) } } else { trace!("Missing connection header"); - return Err(WsClientError::MissingConnectionHeader) + return Err(ClientError::MissingConnectionHeader) } if let Some(key) = resp.headers().get(header::SEC_WEBSOCKET_ACCEPT) @@ -341,14 +359,14 @@ impl Future for WsClientHandshake { trace!( "Invalid challenge response: expected: {} received: {:?}", encoded, key); - return Err(WsClientError::InvalidChallengeResponse(encoded, key.clone())); + return Err(ClientError::InvalidChallengeResponse(encoded, key.clone())); } } else { trace!("Missing SEC-WEBSOCKET-ACCEPT header"); - return Err(WsClientError::MissingWebSocketAcceptHeader) + return Err(ClientError::MissingWebSocketAcceptHeader) }; - let inner = WsInner { + let inner = Inner { tx: self.tx.take().unwrap(), rx: PayloadHelper::new(resp), closed: false, @@ -356,33 +374,33 @@ impl Future for WsClientHandshake { let inner = Rc::new(UnsafeCell::new(inner)); Ok(Async::Ready( - (WsClientReader{inner: Rc::clone(&inner), max_size: self.max_size}, - WsClientWriter{inner}))) + (ClientReader{inner: Rc::clone(&inner), max_size: self.max_size}, + ClientWriter{inner}))) } } -pub struct WsClientReader { - inner: Rc>, +pub struct ClientReader { + inner: Rc>, max_size: usize, } -impl fmt::Debug for WsClientReader { +impl fmt::Debug for ClientReader { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "WsClientReader()") + write!(f, "ws::ClientReader()") } } -impl WsClientReader { +impl ClientReader { #[inline] - fn as_mut(&mut self) -> &mut WsInner { + fn as_mut(&mut self) -> &mut Inner { unsafe{ &mut *self.inner.get() } } } -impl Stream for WsClientReader { +impl Stream for ClientReader { type Item = Message; - type Error = WsError; + type Error = ProtocolError; fn poll(&mut self) -> Poll, Self::Error> { let max_size = self.max_size; @@ -399,14 +417,14 @@ impl Stream for WsClientReader { // continuation is not supported if !finished { inner.closed = true; - return Err(WsError::NoContinuation) + return Err(ProtocolError::NoContinuation) } match opcode { OpCode::Continue => unimplemented!(), OpCode::Bad => { inner.closed = true; - Err(WsError::BadOpCode) + Err(ProtocolError::BadOpCode) }, OpCode::Close => { inner.closed = true; @@ -430,7 +448,7 @@ impl Stream for WsClientReader { Ok(Async::Ready(Some(Message::Text(s)))), Err(_) => { inner.closed = true; - Err(WsError::BadEncoding) + Err(ProtocolError::BadEncoding) } } } @@ -446,18 +464,18 @@ impl Stream for WsClientReader { } } -pub struct WsClientWriter { - inner: Rc> +pub struct ClientWriter { + inner: Rc> } -impl WsClientWriter { +impl ClientWriter { #[inline] - fn as_mut(&mut self) -> &mut WsInner { + fn as_mut(&mut self) -> &mut Inner { unsafe{ &mut *self.inner.get() } } } -impl WsClientWriter { +impl ClientWriter { /// Write payload #[inline] diff --git a/src/ws/frame.rs b/src/ws/frame.rs index 320566585..96162b5c6 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -9,7 +9,7 @@ use body::Binary; use error::{PayloadError}; use payload::PayloadHelper; -use ws::WsError; +use ws::ProtocolError; use ws::proto::{OpCode, CloseCode}; use ws::mask::apply_mask; @@ -53,7 +53,7 @@ impl Frame { /// Parse the input stream into a frame. pub fn parse(pl: &mut PayloadHelper, server: bool, max_size: usize) - -> Poll, WsError> + -> Poll, ProtocolError> where S: Stream { let mut idx = 2; @@ -69,9 +69,9 @@ impl Frame { // check masking let masked = second & 0x80 != 0; if !masked && server { - return Err(WsError::UnmaskedFrame) + return Err(ProtocolError::UnmaskedFrame) } else if masked && !server { - return Err(WsError::MaskedFrame) + return Err(ProtocolError::MaskedFrame) } let rsv1 = first & 0x40 != 0; @@ -104,7 +104,7 @@ impl Frame { // check for max allowed size if length > max_size { - return Err(WsError::Overflow) + return Err(ProtocolError::Overflow) } let mask = if server { @@ -133,13 +133,13 @@ impl Frame { // Disallow bad opcode if let OpCode::Bad = opcode { - return Err(WsError::InvalidOpcode(first & 0x0F)) + return Err(ProtocolError::InvalidOpcode(first & 0x0F)) } // control frames must have length <= 125 match opcode { OpCode::Ping | OpCode::Pong if length > 125 => { - return Err(WsError::InvalidLength(length)) + return Err(ProtocolError::InvalidLength(length)) } OpCode::Close if length > 125 => { debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame."); @@ -257,14 +257,14 @@ mod tests { use super::*; use futures::stream::once; - fn is_none(frm: Poll, WsError>) -> bool { + fn is_none(frm: Poll, ProtocolError>) -> bool { match frm { Ok(Async::Ready(None)) => true, _ => false, } } - fn extract(frm: Poll, WsError>) -> Frame { + fn extract(frm: Poll, ProtocolError>) -> Frame { match frm { Ok(Async::Ready(Some(frame))) => frame, _ => panic!("error"), @@ -370,7 +370,7 @@ mod tests { assert!(Frame::parse(&mut buf, true, 1).is_err()); - if let Err(WsError::Overflow) = Frame::parse(&mut buf, false, 0) { + if let Err(ProtocolError::Overflow) = Frame::parse(&mut buf, false, 0) { } else { panic!("error"); } diff --git a/src/ws/mod.rs b/src/ws/mod.rs index fb0936574..bf31189ca 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -67,13 +67,24 @@ use self::frame::Frame; use self::proto::{hash_key, OpCode}; pub use self::proto::CloseCode; pub use self::context::WebsocketContext; +pub use self::client::{Client, ClientError, + ClientReader, ClientWriter, ClientHandshake}; + +#[allow(deprecated)] pub use self::client::{WsClient, WsClientError, WsClientReader, WsClientWriter, WsClientHandshake}; +/// Backward compatibility +#[doc(hidden)] +#[deprecated(since="0.4.2", note="please use `ws::ProtocolError` instead")] +pub type WsError = ProtocolError; +#[doc(hidden)] +#[deprecated(since="0.4.2", note="please use `ws::HandshakeError` instead")] +pub type WsHandshakeError = HandshakeError; /// Websocket errors #[derive(Fail, Debug)] -pub enum WsError { +pub enum ProtocolError { /// Received an unmasked frame from client #[fail(display="Received an unmasked frame from client")] UnmaskedFrame, @@ -103,17 +114,17 @@ pub enum WsError { Payload(#[cause] PayloadError), } -impl ResponseError for WsError {} +impl ResponseError for ProtocolError {} -impl From for WsError { - fn from(err: PayloadError) -> WsError { - WsError::Payload(err) +impl From for ProtocolError { + fn from(err: PayloadError) -> ProtocolError { + ProtocolError::Payload(err) } } /// Websocket handshake errors #[derive(Fail, PartialEq, Debug)] -pub enum WsHandshakeError { +pub enum HandshakeError { /// Only get method is allowed #[fail(display="Method not allowed")] GetMethodRequired, @@ -134,26 +145,26 @@ pub enum WsHandshakeError { BadWebsocketKey, } -impl ResponseError for WsHandshakeError { +impl ResponseError for HandshakeError { fn error_response(&self) -> HttpResponse { match *self { - WsHandshakeError::GetMethodRequired => { + HandshakeError::GetMethodRequired => { HttpMethodNotAllowed .build() .header(header::ALLOW, "GET") .finish() .unwrap() } - WsHandshakeError::NoWebsocketUpgrade => + HandshakeError::NoWebsocketUpgrade => HttpBadRequest.with_reason("No WebSocket UPGRADE header found"), - WsHandshakeError::NoConnectionUpgrade => + HandshakeError::NoConnectionUpgrade => HttpBadRequest.with_reason("No CONNECTION upgrade"), - WsHandshakeError::NoVersionHeader => + HandshakeError::NoVersionHeader => HttpBadRequest.with_reason("Websocket version header is required"), - WsHandshakeError::UnsupportedVersion => + HandshakeError::UnsupportedVersion => HttpBadRequest.with_reason("Unsupported version"), - WsHandshakeError::BadWebsocketKey => + HandshakeError::BadWebsocketKey => HttpBadRequest.with_reason("Handshake error"), } } @@ -171,7 +182,7 @@ pub enum Message { /// Do websocket handshake and start actor pub fn start(req: HttpRequest, actor: A) -> Result - where A: Actor> + StreamHandler, + where A: Actor> + StreamHandler, S: 'static { let mut resp = handshake(&req)?; @@ -191,10 +202,10 @@ pub fn start(req: HttpRequest, actor: A) -> Result // /// `protocols` is a sequence of known protocols. On successful handshake, // /// the returned response headers contain the first protocol in this list // /// which the server also knows. -pub fn handshake(req: &HttpRequest) -> Result { +pub fn handshake(req: &HttpRequest) -> Result { // WebSocket accepts only GET if *req.method() != Method::GET { - return Err(WsHandshakeError::GetMethodRequired) + return Err(HandshakeError::GetMethodRequired) } // Check for "UPGRADE" to websocket header @@ -208,17 +219,17 @@ pub fn handshake(req: &HttpRequest) -> Result(req: &HttpRequest) -> Result WsStream where S: Stream { impl Stream for WsStream where S: Stream { type Item = Message; - type Error = WsError; + type Error = ProtocolError; fn poll(&mut self) -> Poll, Self::Error> { if self.closed { @@ -289,14 +300,14 @@ impl Stream for WsStream where S: Stream { // continuation is not supported if !finished { self.closed = true; - return Err(WsError::NoContinuation) + return Err(ProtocolError::NoContinuation) } match opcode { OpCode::Continue => unimplemented!(), OpCode::Bad => { self.closed = true; - Err(WsError::BadOpCode) + Err(ProtocolError::BadOpCode) } OpCode::Close => { self.closed = true; @@ -320,7 +331,7 @@ impl Stream for WsStream where S: Stream { Ok(Async::Ready(Some(Message::Text(s)))), Err(_) => { self.closed = true; - Err(WsError::BadEncoding) + Err(ProtocolError::BadEncoding) } } } @@ -346,25 +357,25 @@ mod tests { fn test_handshake() { let req = HttpRequest::new(Method::POST, Uri::from_str("/").unwrap(), Version::HTTP_11, HeaderMap::new(), None); - assert_eq!(WsHandshakeError::GetMethodRequired, handshake(&req).err().unwrap()); + assert_eq!(HandshakeError::GetMethodRequired, handshake(&req).err().unwrap()); let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, HeaderMap::new(), None); - assert_eq!(WsHandshakeError::NoWebsocketUpgrade, handshake(&req).err().unwrap()); + assert_eq!(HandshakeError::NoWebsocketUpgrade, handshake(&req).err().unwrap()); let mut headers = HeaderMap::new(); headers.insert(header::UPGRADE, header::HeaderValue::from_static("test")); let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); - assert_eq!(WsHandshakeError::NoWebsocketUpgrade, handshake(&req).err().unwrap()); + assert_eq!(HandshakeError::NoWebsocketUpgrade, handshake(&req).err().unwrap()); let mut headers = HeaderMap::new(); headers.insert(header::UPGRADE, header::HeaderValue::from_static("websocket")); let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); - assert_eq!(WsHandshakeError::NoConnectionUpgrade, handshake(&req).err().unwrap()); + assert_eq!(HandshakeError::NoConnectionUpgrade, handshake(&req).err().unwrap()); let mut headers = HeaderMap::new(); headers.insert(header::UPGRADE, @@ -373,7 +384,7 @@ mod tests { header::HeaderValue::from_static("upgrade")); let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); - assert_eq!(WsHandshakeError::NoVersionHeader, handshake(&req).err().unwrap()); + assert_eq!(HandshakeError::NoVersionHeader, handshake(&req).err().unwrap()); let mut headers = HeaderMap::new(); headers.insert(header::UPGRADE, @@ -384,7 +395,7 @@ mod tests { header::HeaderValue::from_static("5")); let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); - assert_eq!(WsHandshakeError::UnsupportedVersion, handshake(&req).err().unwrap()); + assert_eq!(HandshakeError::UnsupportedVersion, handshake(&req).err().unwrap()); let mut headers = HeaderMap::new(); headers.insert(header::UPGRADE, @@ -395,7 +406,7 @@ mod tests { header::HeaderValue::from_static("13")); let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); - assert_eq!(WsHandshakeError::BadWebsocketKey, handshake(&req).err().unwrap()); + assert_eq!(HandshakeError::BadWebsocketKey, handshake(&req).err().unwrap()); let mut headers = HeaderMap::new(); headers.insert(header::UPGRADE, @@ -414,17 +425,17 @@ mod tests { #[test] fn test_wserror_http_response() { - let resp: HttpResponse = WsHandshakeError::GetMethodRequired.error_response(); + let resp: HttpResponse = HandshakeError::GetMethodRequired.error_response(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let resp: HttpResponse = WsHandshakeError::NoWebsocketUpgrade.error_response(); + let resp: HttpResponse = HandshakeError::NoWebsocketUpgrade.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = WsHandshakeError::NoConnectionUpgrade.error_response(); + let resp: HttpResponse = HandshakeError::NoConnectionUpgrade.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = WsHandshakeError::NoVersionHeader.error_response(); + let resp: HttpResponse = HandshakeError::NoVersionHeader.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = WsHandshakeError::UnsupportedVersion.error_response(); + let resp: HttpResponse = HandshakeError::UnsupportedVersion.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = WsHandshakeError::BadWebsocketKey.error_response(); + let resp: HttpResponse = HandshakeError::BadWebsocketKey.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } } diff --git a/tests/test_server.rs b/tests/test_server.rs index 7714ad1fc..1db242057 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -163,7 +163,7 @@ fn test_headers() { // read response let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(Bytes::from(bytes), Bytes::from_static(STR.as_ref())); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } #[test] diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 13aeef486..edda3f64b 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -16,7 +16,7 @@ impl Actor for Ws { type Context = ws::WebsocketContext; } -impl StreamHandler for Ws { +impl StreamHandler for Ws { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { match msg { From bebfc6c9b5a2d47d1e8e02ea34ab4c32bed0f13a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 2 Mar 2018 11:32:37 -0800 Subject: [PATCH 55/84] sleep for test --- tests/test_server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_server.rs b/tests/test_server.rs index 1db242057..32f4ab6f2 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -121,7 +121,7 @@ fn test_shutdown() { assert!(response.status().is_success()); } - thread::sleep(time::Duration::from_millis(100)); + thread::sleep(time::Duration::from_millis(1000)); assert!(net::TcpStream::connect(addr).is_err()); } From 343888017ef6a0440a24c8ef307e927415f5b5f5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 2 Mar 2018 12:26:31 -0800 Subject: [PATCH 56/84] Update CHANGES.md --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index dc2cbc49b..0d7bf1e4b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ * Better naming for websockets implementation +* A filter for cross-site request forgery #89 + ## 0.4.1 (2018-03-01) From feba5aeffd9b5c331e17dafb17dc9ed5f6550d84 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 2 Mar 2018 14:31:23 -0800 Subject: [PATCH 57/84] bump version --- .travis.yml | 2 +- Cargo.toml | 2 +- src/client/pipeline.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1d8784f93..33fe904de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,7 +47,7 @@ script: USE_SKEPTIC=1 cargo test --features=alpn else cargo clean - cargo test + cargo test -- --nocapture # --features=alpn fi diff --git a/Cargo.toml b/Cargo.toml index a5642e507..44cd078f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.4.1" +version = "0.4.2" authors = ["Nikolay Kim "] description = "Actix web is a small, pragmatic, extremely fast, web framework for Rust." readme = "README.md" diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index bd35d975b..baa84da9d 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -111,7 +111,7 @@ impl Future for SendRequest { _ => IoBody::Done, }; - let mut pl = Box::new(Pipeline { + let pl = Box::new(Pipeline { body, conn, writer, parser: Some(HttpResponseParser::default()), parser_buf: BytesMut::new(), From 2158ad29ee97bae21c3a7a05e4867f3123f37b88 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 2 Mar 2018 20:39:22 -0800 Subject: [PATCH 58/84] add Pattern::with_prefix, make it usable outside of actix --- CHANGES.md | 4 +++- src/router.rs | 17 ++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0d7bf1e4b..383cc430e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,7 +4,9 @@ * Better naming for websockets implementation -* A filter for cross-site request forgery #89 +* Add `Pattern::with_prefix()`, make it more usable outside of actix + +* Add csrf middleware for filter for cross-site request forgery #89 ## 0.4.1 (2018-03-01) diff --git a/src/router.rs b/src/router.rs index 63cc9045c..fc01bd3be 100644 --- a/src/router.rs +++ b/src/router.rs @@ -148,7 +148,12 @@ impl Pattern { /// /// Panics if path pattern is wrong. pub fn new(name: &str, path: &str) -> Self { - let (pattern, elements, is_dynamic) = Pattern::parse(path); + Pattern::with_prefix(name, path, "/") + } + + /// Parse path pattern and create new `Pattern` instance with custom prefix + pub fn with_prefix(name: &str, path: &str, prefix: &str) -> Self { + let (pattern, elements, is_dynamic) = Pattern::parse(path, prefix); let tp = if is_dynamic { let re = match Regex::new(&pattern) { @@ -188,7 +193,9 @@ impl Pattern { } } - pub fn match_with_params<'a>(&'a self, path: &'a str, params: &'a mut Params<'a>) -> bool { + pub fn match_with_params<'a>(&'a self, path: &'a str, params: &'a mut Params<'a>) + -> bool + { match self.tp { PatternType::Static(ref s) => s == path, PatternType::Dynamic(ref re, ref names) => { @@ -236,11 +243,11 @@ impl Pattern { Ok(path) } - fn parse(pattern: &str) -> (String, Vec, bool) { + fn parse(pattern: &str, prefix: &str) -> (String, Vec, bool) { const DEFAULT_PATTERN: &str = "[^/]+"; - let mut re1 = String::from("^/"); - let mut re2 = String::from("/"); + let mut re1 = String::from("^") + prefix; + let mut re2 = String::from(prefix); let mut el = String::new(); let mut in_param = false; let mut in_param_pattern = false; From 16c05f07ba8ef1574e2e16e51bd1c08d933664b6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 2 Mar 2018 20:40:08 -0800 Subject: [PATCH 59/84] make HttpRequest::match_info_mut() public --- src/client/parser.rs | 15 ++++++++------- src/httprequest.rs | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/client/parser.rs b/src/client/parser.rs index 1eeb021b1..22b8f78aa 100644 --- a/src/client/parser.rs +++ b/src/client/parser.rs @@ -39,10 +39,8 @@ impl HttpResponseParser { // if buf is empty parse_message will always return NotReady, let's avoid that let read = if buf.is_empty() { match utils::read_from_io(io, buf) { - Ok(Async::Ready(0)) => { - // debug!("Ignored premature client disconnection"); - return Err(HttpResponseParserError::Disconnect); - }, + Ok(Async::Ready(0)) => + return Err(HttpResponseParserError::Disconnect), Ok(Async::Ready(_)) => (), Ok(Async::NotReady) => return Ok(Async::NotReady), @@ -66,10 +64,12 @@ impl HttpResponseParser { } if read || buf.remaining_mut() == 0 { match utils::read_from_io(io, buf) { - Ok(Async::Ready(0)) => return Err(HttpResponseParserError::Disconnect), + Ok(Async::Ready(0)) => + return Err(HttpResponseParserError::Disconnect), Ok(Async::Ready(_)) => (), Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => return Err(HttpResponseParserError::Error(err.into())), + Err(err) => + return Err(HttpResponseParserError::Error(err.into())), } } else { return Ok(Async::NotReady) @@ -109,7 +109,8 @@ impl HttpResponseParser { } } - fn parse_message(buf: &mut BytesMut) -> Poll<(ClientResponse, Option), ParseError> + fn parse_message(buf: &mut BytesMut) + -> Poll<(ClientResponse, Option), ParseError> { // Parse http message let bytes_ptr = buf.as_ref().as_ptr() as usize; diff --git a/src/httprequest.rs b/src/httprequest.rs index ae7c21ba9..688bea7a4 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -365,7 +365,7 @@ impl HttpRequest { /// Get mutable reference to request's Params. #[inline] - pub(crate) fn match_info_mut(&mut self) -> &mut Params { + pub fn match_info_mut(&mut self) -> &mut Params { unsafe{ mem::transmute(&mut self.as_mut().params) } } From c2d8abcee71b5289fb483813fc3f22b5cdb9d876 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 2 Mar 2018 20:47:23 -0800 Subject: [PATCH 60/84] Fix disconnect on idle connections --- CHANGES.md | 2 ++ src/payload.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 383cc430e..5d3e172bf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,8 @@ * Add csrf middleware for filter for cross-site request forgery #89 +* Fix disconnect on idle connections + ## 0.4.1 (2018-03-01) diff --git a/src/payload.rs b/src/payload.rs index 3c0f41532..3cefcf718 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -168,7 +168,7 @@ impl Inner { len: 0, err: None, items: VecDeque::new(), - need_read: false, + need_read: true, } } From 791a980e2db66e4cc82285d7b9c3210447e83c01 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 2 Mar 2018 22:08:56 -0800 Subject: [PATCH 61/84] update tests --- Cargo.toml | 2 +- examples/basics/src/main.rs | 2 +- src/pipeline.rs | 3 ++- src/server/h1.rs | 9 +++++++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 44cd078f6..fef3bad0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,7 +81,7 @@ openssl = { version="0.10", optional = true } tokio-openssl = { version="0.2", optional = true } [dependencies.actix] -version = "0.5" +version = "^0.5.1" [dev-dependencies] env_logger = "0.5" diff --git a/examples/basics/src/main.rs b/examples/basics/src/main.rs index b93f5f20b..55e4485e0 100644 --- a/examples/basics/src/main.rs +++ b/examples/basics/src/main.rs @@ -139,7 +139,7 @@ fn main() { // default .default_resource(|r| { r.method(Method::GET).f(p404); - r.route().p(pred::Not(pred::Get())).f(|req| httpcodes::HTTPMethodNotAllowed); + r.route().filter(pred::Not(pred::Get())).f(|req| httpcodes::HTTPMethodNotAllowed); })) .bind("127.0.0.1:8080").expect("Can not bind to 127.0.0.1:8080") diff --git a/src/pipeline.rs b/src/pipeline.rs index 2fd21ec20..bd7801a36 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -173,7 +173,8 @@ impl> HttpHandlerTask for Pipeline { PipelineState::None => return Ok(Async::Ready(true)), PipelineState::Error => - return Err(io::Error::new(io::ErrorKind::Other, "Internal error").into()), + return Err(io::Error::new( + io::ErrorKind::Other, "Internal error").into()), _ => (), } diff --git a/src/server/h1.rs b/src/server/h1.rs index 8f0917dac..6d8f8df66 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -125,8 +125,8 @@ impl Http1 // TODO: refactor pub fn poll_io(&mut self) -> Poll { // read incoming data - let need_read = - if !self.flags.contains(Flags::ERROR) && self.tasks.len() < MAX_PIPELINED_MESSAGES + let need_read = if !self.flags.intersects(Flags::ERROR) && + self.tasks.len() < MAX_PIPELINED_MESSAGES { 'outer: loop { match self.reader.parse(self.stream.get_mut(), @@ -1413,6 +1413,10 @@ mod tests { assert!(req.chunked().unwrap()); assert!(!req.payload().eof()); + buf.feed_data("4\r\n1111\r\n"); + not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); + assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"1111"); + buf.feed_data("4\r\ndata\r"); not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); @@ -1430,6 +1434,7 @@ mod tests { buf.feed_data("ne\r\n0\r\n"); not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); + //trailers //buf.feed_data("test: test\r\n"); //not_ready!(reader.parse(&mut buf, &mut readbuf)); From 6acb6dd4e773257c41d4d0bd82fad15e6458b52c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 2 Mar 2018 22:31:58 -0800 Subject: [PATCH 62/84] set release date --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 5d3e172bf..f1c83cbde 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## 0.4.2 (2018-03-xx) +## 0.4.2 (2018-03-02) * Better naming for websockets implementation From 4e41347de88caba72c122272a086e89f4698f15e Mon Sep 17 00:00:00 2001 From: Anti Revoluzzer Date: Fri, 2 Mar 2018 22:57:11 -0800 Subject: [PATCH 63/84] move reuse_address before bind --- src/server/srv.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/srv.rs b/src/server/srv.rs index d886d4c59..e219049ba 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -697,7 +697,7 @@ fn create_tcp_listener(addr: net::SocketAddr, backlog: i32) -> io::Result TcpBuilder::new_v4()?, net::SocketAddr::V6(_) => TcpBuilder::new_v6()?, }; - builder.bind(addr)?; builder.reuse_address(true)?; + builder.bind(addr)?; Ok(builder.listen(backlog)?) } From 83fcdfd91fa647025035b307a8405bbfd584a198 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 3 Mar 2018 09:27:54 -0800 Subject: [PATCH 64/84] fix potential bug in payload processing --- src/server/h1.rs | 67 +++++++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/src/server/h1.rs b/src/server/h1.rs index 6d8f8df66..d21aa4f48 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -402,40 +402,49 @@ impl Reader { // read payload let done = { if let Some(ref mut payload) = self.payload { - match utils::read_from_io(io, buf) { - Ok(Async::Ready(0)) => { - payload.tx.set_error(PayloadError::Incomplete); + 'buf: loop { + match utils::read_from_io(io, buf) { + Ok(Async::Ready(0)) => { + payload.tx.set_error(PayloadError::Incomplete); - // http channel should not deal with payload errors - return Err(ReaderError::Payload) - }, - Err(err) => { - payload.tx.set_error(err.into()); - - // http channel should not deal with payload errors - return Err(ReaderError::Payload) - } - _ => (), - } - loop { - match payload.decoder.decode(buf) { - Ok(Async::Ready(Some(bytes))) => { - payload.tx.feed_data(bytes); - if payload.decoder.is_eof() { - payload.tx.feed_eof(); - break true - } + // http channel should not deal with payload errors + return Err(ReaderError::Payload) }, - Ok(Async::Ready(None)) => { - payload.tx.feed_eof(); - break true - }, - Ok(Async::NotReady) => - return Ok(Async::NotReady), Err(err) => { payload.tx.set_error(err.into()); + + // http channel should not deal with payload errors return Err(ReaderError::Payload) } + _ => (), + } + let is_full = buf.remaining_mut() == 0; + loop { + match payload.decoder.decode(buf) { + Ok(Async::Ready(Some(bytes))) => { + payload.tx.feed_data(bytes); + if payload.decoder.is_eof() { + payload.tx.feed_eof(); + break 'buf true + } + }, + Ok(Async::Ready(None)) => { + payload.tx.feed_eof(); + break 'buf true + }, + Ok(Async::NotReady) => { + // if buffer is full then + // socket still can contain more data + if is_full { + continue 'buf + } + return Ok(Async::NotReady) + }, + Err(err) => { + payload.tx.set_error(err.into()); + return Err(ReaderError::Payload) + } + } } } } else { @@ -470,7 +479,7 @@ impl Reader { return Ok(Async::Ready(msg)); }, Async::NotReady => { - if buf.capacity() >= MAX_BUFFER_SIZE { + if buf.len() >= MAX_BUFFER_SIZE { error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); return Err(ReaderError::Error(ParseError::TooLarge)); } From 16afeda79c0ee785cef2be3ef864f4efd194bf91 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 3 Mar 2018 09:29:36 -0800 Subject: [PATCH 65/84] update changes --- CHANGES.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index f1c83cbde..d5e60ee32 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## 0.4.3 (2018-03-xx) + +* Fix request body read bug + +* Set reuse address before bind #90 + + ## 0.4.2 (2018-03-02) * Better naming for websockets implementation From f456be0309b7f596c85acf6f99bb80fa1d6de3c9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 3 Mar 2018 10:06:13 -0800 Subject: [PATCH 66/84] simplify linked nodes --- src/server/channel.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/server/channel.rs b/src/server/channel.rs index 8cf23ed30..febd5fe77 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -95,7 +95,7 @@ impl Future for HttpChannel where T: IoStream, H: HttpHandler + 'sta match result { Ok(Async::Ready(())) | Err(_) => { h1.settings().remove_channel(); - self.node.as_ref().unwrap().remove(); + self.node.as_mut().unwrap().remove(); }, _ => (), } @@ -106,7 +106,7 @@ impl Future for HttpChannel where T: IoStream, H: HttpHandler + 'sta match result { Ok(Async::Ready(())) | Err(_) => { h2.settings().remove_channel(); - self.node.as_ref().unwrap().remove(); + self.node.as_mut().unwrap().remove(); }, _ => (), } @@ -186,13 +186,15 @@ impl Node } } - fn remove(&self) { + fn remove(&mut self) { #[allow(mutable_transmutes)] unsafe { - if let Some(ref prev) = self.prev { + let mut prev = self.prev.take(); + let next = self.next.take(); + + if let Some(ref mut prev) = prev { let p: &mut Node<()> = mem::transmute(prev.as_ref().unwrap()); - let slf: &mut Node = mem::transmute(self); - p.next = slf.next.take(); + p.next = next; } } } From 058630d0412797e1f1c1f306beaf59a4bcd44ee3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 3 Mar 2018 11:16:55 -0800 Subject: [PATCH 67/84] simplify channels list management --- src/server/channel.rs | 49 ++++++++++++++++++------------------------ src/server/settings.rs | 4 ++-- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/src/server/channel.rs b/src/server/channel.rs index febd5fe77..2416a3f66 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -18,15 +18,6 @@ enum HttpProtocol { Unknown(Rc>, Option, T, BytesMut), } -impl HttpProtocol { - fn is_unknown(&self) -> bool { - match *self { - HttpProtocol::Unknown(_, _, _, _) => true, - _ => false - } - } -} - enum ProtocolKind { Http1, Http2, @@ -44,15 +35,14 @@ impl HttpChannel where T: IoStream, H: HttpHandler + 'static io: T, peer: Option, http2: bool) -> HttpChannel { settings.add_channel(); + if http2 { HttpChannel { - node: None, - proto: Some(HttpProtocol::H2( + node: None, proto: Some(HttpProtocol::H2( h2::Http2::new(settings, io, peer, Bytes::new()))) } } else { HttpChannel { - node: None, - proto: Some(HttpProtocol::Unknown( + node: None, proto: Some(HttpProtocol::Unknown( settings, peer, io, BytesMut::with_capacity(4096))) } } } @@ -78,15 +68,18 @@ impl Future for HttpChannel where T: IoStream, H: HttpHandler + 'sta type Error = (); fn poll(&mut self) -> Poll { - if !self.proto.as_ref().map(|p| p.is_unknown()).unwrap_or(false) && self.node.is_none() { - self.node = Some(Node::new(self)); - match self.proto { + if !self.node.is_none() { + let el = self as *mut _; + self.node = Some(Node::new(el)); + let _ = match self.proto { Some(HttpProtocol::H1(ref mut h1)) => - h1.settings().head().insert(self.node.as_ref().unwrap()), + self.node.as_ref().map(|n| h1.settings().head().insert(n)), Some(HttpProtocol::H2(ref mut h2)) => - h2.settings().head().insert(self.node.as_ref().unwrap()), - _ => (), - } + self.node.as_ref().map(|n| h2.settings().head().insert(n)), + Some(HttpProtocol::Unknown(ref mut settings, _, _, _)) => + self.node.as_ref().map(|n| settings.head().insert(n)), + None => unreachable!(), + }; } let kind = match self.proto { @@ -95,7 +88,7 @@ impl Future for HttpChannel where T: IoStream, H: HttpHandler + 'sta match result { Ok(Async::Ready(())) | Err(_) => { h1.settings().remove_channel(); - self.node.as_mut().unwrap().remove(); + self.node.as_mut().map(|n| n.remove()); }, _ => (), } @@ -106,7 +99,7 @@ impl Future for HttpChannel where T: IoStream, H: HttpHandler + 'sta match result { Ok(Async::Ready(())) | Err(_) => { h2.settings().remove_channel(); - self.node.as_mut().unwrap().remove(); + self.node.as_mut().map(|n| n.remove()); }, _ => (), } @@ -117,6 +110,7 @@ impl Future for HttpChannel where T: IoStream, H: HttpHandler + 'sta Ok(Async::Ready(0)) | Err(_) => { debug!("Ignored premature client disconnection"); settings.remove_channel(); + self.node.as_mut().map(|n| n.remove()); return Err(()) }, _ => (), @@ -163,11 +157,11 @@ pub(crate) struct Node impl Node { - fn new(el: &mut T) -> Self { + fn new(el: *mut T) -> Self { Node { next: None, prev: None, - element: el as *mut _, + element: el, } } @@ -187,14 +181,13 @@ impl Node } fn remove(&mut self) { - #[allow(mutable_transmutes)] unsafe { - let mut prev = self.prev.take(); + self.element = ptr::null_mut(); let next = self.next.take(); + let mut prev = self.prev.take(); if let Some(ref mut prev) = prev { - let p: &mut Node<()> = mem::transmute(prev.as_ref().unwrap()); - p.next = next; + prev.as_mut().unwrap().next = next; } } } diff --git a/src/server/settings.rs b/src/server/settings.rs index 50be1f7e7..b0b8eb552 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -63,7 +63,7 @@ pub(crate) struct WorkerSettings { bytes: Rc, messages: Rc, channels: Cell, - node: Node<()>, + node: Box>, } impl WorkerSettings { @@ -75,7 +75,7 @@ impl WorkerSettings { bytes: Rc::new(SharedBytesPool::new()), messages: Rc::new(helpers::SharedMessagePool::new()), channels: Cell::new(0), - node: Node::head(), + node: Box::new(Node::head()), } } From 2ccbd5fa181dc15ead0adb843e6354f11e097a61 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 3 Mar 2018 12:17:26 -0800 Subject: [PATCH 68/84] fix socket polling --- src/client/parser.rs | 72 +++++++++++++++++++++++--------------------- src/server/h1.rs | 41 +++++++++++-------------- 2 files changed, 55 insertions(+), 58 deletions(-) diff --git a/src/client/parser.rs b/src/client/parser.rs index 22b8f78aa..8fe399009 100644 --- a/src/client/parser.rs +++ b/src/client/parser.rs @@ -2,7 +2,7 @@ use std::mem; use httparse; use http::{Version, HttpTryFrom, HeaderMap, StatusCode}; use http::header::{self, HeaderName, HeaderValue}; -use bytes::{Bytes, BytesMut, BufMut}; +use bytes::{Bytes, BytesMut}; use futures::{Poll, Async}; use error::{ParseError, PayloadError}; @@ -37,7 +37,7 @@ impl HttpResponseParser { where T: IoStream { // if buf is empty parse_message will always return NotReady, let's avoid that - let read = if buf.is_empty() { + if buf.is_empty() { match utils::read_from_io(io, buf) { Ok(Async::Ready(0)) => return Err(HttpResponseParserError::Disconnect), @@ -47,13 +47,12 @@ impl HttpResponseParser { Err(err) => return Err(HttpResponseParserError::Error(err.into())) } - false - } else { - true - }; + } loop { - match HttpResponseParser::parse_message(buf).map_err(HttpResponseParserError::Error)? { + match HttpResponseParser::parse_message(buf) + .map_err(HttpResponseParserError::Error)? + { Async::Ready((msg, decoder)) => { self.decoder = decoder; return Ok(Async::Ready(msg)); @@ -62,17 +61,13 @@ impl HttpResponseParser { if buf.capacity() >= MAX_BUFFER_SIZE { return Err(HttpResponseParserError::Error(ParseError::TooLarge)); } - if read || buf.remaining_mut() == 0 { - match utils::read_from_io(io, buf) { - Ok(Async::Ready(0)) => - return Err(HttpResponseParserError::Disconnect), - Ok(Async::Ready(_)) => (), - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => - return Err(HttpResponseParserError::Error(err.into())), - } - } else { - return Ok(Async::NotReady) + match utils::read_from_io(io, buf) { + Ok(Async::Ready(0)) => + return Err(HttpResponseParserError::Disconnect), + Ok(Async::Ready(_)) => (), + Ok(Async::NotReady) => return Ok(Async::NotReady), + Err(err) => + return Err(HttpResponseParserError::Error(err.into())), } }, } @@ -84,25 +79,34 @@ impl HttpResponseParser { where T: IoStream { if self.decoder.is_some() { - // read payload - match utils::read_from_io(io, buf) { - Ok(Async::Ready(0)) => { - if buf.is_empty() { - return Err(PayloadError::Incomplete) + loop { + // read payload + let not_ready = match utils::read_from_io(io, buf) { + Ok(Async::Ready(0)) => { + if buf.is_empty() { + return Err(PayloadError::Incomplete) + } + true } - } - Err(err) => return Err(err.into()), - _ => (), - } + Err(err) => return Err(err.into()), + Ok(Async::NotReady) => true, + _ => false, + }; - match self.decoder.as_mut().unwrap().decode(buf) { - Ok(Async::Ready(Some(b))) => Ok(Async::Ready(Some(b))), - Ok(Async::Ready(None)) => { - self.decoder.take(); - Ok(Async::Ready(None)) + match self.decoder.as_mut().unwrap().decode(buf) { + Ok(Async::Ready(Some(b))) => + return Ok(Async::Ready(Some(b))), + Ok(Async::Ready(None)) => { + self.decoder.take(); + return Ok(Async::Ready(None)) + } + Ok(Async::NotReady) => { + if not_ready { + return Ok(Async::NotReady) + } + } + Err(err) => return Err(err.into()), } - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(err) => Err(err.into()), } } else { Ok(Async::Ready(None)) diff --git a/src/server/h1.rs b/src/server/h1.rs index d21aa4f48..a55ac2799 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -10,7 +10,7 @@ use actix::Arbiter; use httparse; use http::{Uri, Method, Version, HttpTryFrom, HeaderMap}; use http::header::{self, HeaderName, HeaderValue}; -use bytes::{Bytes, BytesMut, BufMut}; +use bytes::{Bytes, BytesMut}; use futures::{Future, Poll, Async}; use tokio_core::reactor::Timeout; @@ -403,22 +403,22 @@ impl Reader { let done = { if let Some(ref mut payload) = self.payload { 'buf: loop { - match utils::read_from_io(io, buf) { + let not_ready = match utils::read_from_io(io, buf) { Ok(Async::Ready(0)) => { payload.tx.set_error(PayloadError::Incomplete); // http channel should not deal with payload errors return Err(ReaderError::Payload) }, + Ok(Async::NotReady) => true, Err(err) => { payload.tx.set_error(err.into()); // http channel should not deal with payload errors return Err(ReaderError::Payload) } - _ => (), - } - let is_full = buf.remaining_mut() == 0; + _ => false, + }; loop { match payload.decoder.decode(buf) { Ok(Async::Ready(Some(bytes))) => { @@ -435,10 +435,10 @@ impl Reader { Ok(Async::NotReady) => { // if buffer is full then // socket still can contain more data - if is_full { - continue 'buf + if not_ready { + return Ok(Async::NotReady) } - return Ok(Async::NotReady) + continue 'buf }, Err(err) => { payload.tx.set_error(err.into()); @@ -454,16 +454,13 @@ impl Reader { if done { self.payload = None } // if buf is empty parse_message will always return NotReady, let's avoid that - let read = if buf.is_empty() { + if buf.is_empty() { match utils::read_from_io(io, buf) { Ok(Async::Ready(0)) => return Err(ReaderError::Disconnect), Ok(Async::Ready(_)) => (), Ok(Async::NotReady) => return Ok(Async::NotReady), Err(err) => return Err(ReaderError::Error(err.into())) } - false - } else { - true }; loop { @@ -483,18 +480,14 @@ impl Reader { error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); return Err(ReaderError::Error(ParseError::TooLarge)); } - if read || buf.remaining_mut() == 0 { - match utils::read_from_io(io, buf) { - Ok(Async::Ready(0)) => { - debug!("Ignored premature client disconnection"); - return Err(ReaderError::Disconnect); - }, - Ok(Async::Ready(_)) => (), - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => return Err(ReaderError::Error(err.into())), - } - } else { - return Ok(Async::NotReady) + match utils::read_from_io(io, buf) { + Ok(Async::Ready(0)) => { + debug!("Ignored premature client disconnection"); + return Err(ReaderError::Disconnect); + }, + Ok(Async::Ready(_)) => (), + Ok(Async::NotReady) => return Ok(Async::NotReady), + Err(err) => return Err(ReaderError::Error(err.into())), } }, } From 327df159c6a3b6e29900d674e558db31b4619b8e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 3 Mar 2018 18:46:22 -0800 Subject: [PATCH 69/84] prepare release --- CHANGES.md | 4 +++- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d5e60ee32..a1f2b6de9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,11 @@ # Changes -## 0.4.3 (2018-03-xx) +## 0.4.3 (2018-03-03) * Fix request body read bug +* Fix segmentation fault #79 + * Set reuse address before bind #90 diff --git a/Cargo.toml b/Cargo.toml index fef3bad0e..2c035c5d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.4.2" +version = "0.4.3" authors = ["Nikolay Kim "] description = "Actix web is a small, pragmatic, extremely fast, web framework for Rust." readme = "README.md" From ab978a18ff381d0da09e8a16bc9ad6c3b0327569 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 3 Mar 2018 18:50:00 -0800 Subject: [PATCH 70/84] unix only test --- tests/test_server.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_server.rs b/tests/test_server.rs index 32f4ab6f2..44d0316d1 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -94,6 +94,7 @@ fn test_start() { } #[test] +#[cfg(unix)] fn test_shutdown() { let _ = test::TestServer::unused_addr(); let (tx, rx) = mpsc::channel(); From f673dba7596552d7b8dd78784b17c05cb6478b3e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 4 Mar 2018 09:48:23 -0800 Subject: [PATCH 71/84] Fix handling of requests with an encoded body with a length > 8192 #93 --- CHANGES.md | 4 + Cargo.toml | 2 +- src/server/encoding.rs | 370 ++++++++++++++++++++++------------------- tests/test_client.rs | 26 +++ tests/test_server.rs | 161 +++++++++++++++--- 5 files changed, 369 insertions(+), 194 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a1f2b6de9..163d136fb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## 0.4.4 (2018-03-xx) + +* Fix handling of requests with an encoded body with a length > 8192 #93 + ## 0.4.3 (2018-03-03) * Fix request body read bug diff --git a/Cargo.toml b/Cargo.toml index 2c035c5d9..eeca59366 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.4.3" +version = "0.4.4" authors = ["Nikolay Kim "] description = "Actix web is a small, pragmatic, extremely fast, web framework for Rust." readme = "README.md" diff --git a/src/server/encoding.rs b/src/server/encoding.rs index 5e0ac7b0f..23f4aef7f 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -11,7 +11,7 @@ use flate2::Compression; use flate2::read::GzDecoder; use flate2::write::{GzEncoder, DeflateDecoder, DeflateEncoder}; use brotli2::write::{BrotliDecoder, BrotliEncoder}; -use bytes::{Bytes, BytesMut, BufMut, Writer}; +use bytes::{Bytes, BytesMut, BufMut}; use headers::ContentEncoding; use body::{Body, Binary}; @@ -128,177 +128,6 @@ impl PayloadWriter for PayloadType { } } -pub(crate) enum Decoder { - Deflate(Box>>), - Gzip(Option>>), - Br(Box>>), - Identity, -} - -// should go after write::GzDecoder get implemented -#[derive(Debug)] -pub(crate) struct Wrapper { - pub buf: BytesMut, - pub eof: bool, -} - -impl io::Read for Wrapper { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - let len = cmp::min(buf.len(), self.buf.len()); - buf[..len].copy_from_slice(&self.buf[..len]); - self.buf.split_to(len); - if len == 0 { - if self.eof { - Ok(0) - } else { - Err(io::Error::new(io::ErrorKind::WouldBlock, "")) - } - } else { - Ok(len) - } - } -} - -impl io::Write for Wrapper { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.buf.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -/// Payload stream with decompression support -pub(crate) struct PayloadStream { - decoder: Decoder, - dst: BytesMut, -} - -impl PayloadStream { - pub fn new(enc: ContentEncoding) -> PayloadStream { - let dec = match enc { - ContentEncoding::Br => Decoder::Br( - Box::new(BrotliDecoder::new(BytesMut::with_capacity(8192).writer()))), - ContentEncoding::Deflate => Decoder::Deflate( - Box::new(DeflateDecoder::new(BytesMut::with_capacity(8192).writer()))), - ContentEncoding::Gzip => Decoder::Gzip(None), - _ => Decoder::Identity, - }; - PayloadStream{ decoder: dec, dst: BytesMut::new() } - } -} - -impl PayloadStream { - - pub fn feed_eof(&mut self) -> io::Result> { - match self.decoder { - Decoder::Br(ref mut decoder) => { - match decoder.finish() { - Ok(mut writer) => { - let b = writer.get_mut().take().freeze(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - }, - Err(err) => Err(err), - } - }, - Decoder::Gzip(ref mut decoder) => { - if let Some(ref mut decoder) = *decoder { - decoder.as_mut().get_mut().eof = true; - - loop { - self.dst.reserve(8192); - match decoder.read(unsafe{self.dst.bytes_mut()}) { - Ok(n) => { - if n == 0 { - return Ok(Some(self.dst.take().freeze())) - } else { - unsafe{self.dst.set_len(n)}; - } - } - Err(err) => return Err(err), - } - } - } else { - Ok(None) - } - }, - Decoder::Deflate(ref mut decoder) => { - match decoder.try_finish() { - Ok(_) => { - let b = decoder.get_mut().get_mut().take().freeze(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - }, - Err(err) => Err(err), - } - }, - Decoder::Identity => Ok(None), - } - } - - pub fn feed_data(&mut self, data: Bytes) -> io::Result> { - match self.decoder { - Decoder::Br(ref mut decoder) => { - match decoder.write(&data).and_then(|_| decoder.flush()) { - Ok(_) => { - let b = decoder.get_mut().get_mut().take().freeze(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - }, - Err(err) => Err(err) - } - }, - Decoder::Gzip(ref mut decoder) => { - if decoder.is_none() { - *decoder = Some( - Box::new(GzDecoder::new( - Wrapper{buf: BytesMut::from(data), eof: false}))); - } else { - let _ = decoder.as_mut().unwrap().write(&data); - } - - loop { - self.dst.reserve(8192); - match decoder.as_mut().as_mut().unwrap().read(unsafe{self.dst.bytes_mut()}) { - Ok(n) => { - if n == 0 { - return Ok(Some(self.dst.split_to(n).freeze())); - } else { - unsafe{self.dst.set_len(n)}; - } - } - Err(e) => return Err(e), - } - } - }, - Decoder::Deflate(ref mut decoder) => { - match decoder.write(&data).and_then(|_| decoder.flush()) { - Ok(_) => { - let b = decoder.get_mut().get_mut().take().freeze(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - }, - Err(e) => Err(e), - } - }, - Decoder::Identity => Ok(Some(data)), - } - } -} /// Payload wrapper with content decompression support pub(crate) struct EncodedPayload { @@ -357,6 +186,203 @@ impl PayloadWriter for EncodedPayload { } } +pub(crate) enum Decoder { + Deflate(Box>), + Gzip(Option>>), + Br(Box>), + Identity, +} + +// should go after write::GzDecoder get implemented +#[derive(Debug)] +pub(crate) struct Wrapper { + pub buf: BytesMut, + pub eof: bool, +} + +impl io::Read for Wrapper { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let len = cmp::min(buf.len(), self.buf.len()); + buf[..len].copy_from_slice(&self.buf[..len]); + self.buf.split_to(len); + if len == 0 { + if self.eof { + Ok(0) + } else { + Err(io::Error::new(io::ErrorKind::WouldBlock, "")) + } + } else { + Ok(len) + } + } +} + +impl io::Write for Wrapper { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.buf.extend_from_slice(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub(crate) struct Writer { + buf: BytesMut, +} + +impl Writer { + fn new() -> Writer { + Writer{buf: BytesMut::with_capacity(8192)} + } + fn take(&mut self) -> Bytes { + self.buf.take().freeze() + } +} + +impl io::Write for Writer { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.buf.extend_from_slice(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +/// Payload stream with decompression support +pub(crate) struct PayloadStream { + decoder: Decoder, + dst: BytesMut, +} + +impl PayloadStream { + pub fn new(enc: ContentEncoding) -> PayloadStream { + let dec = match enc { + ContentEncoding::Br => Decoder::Br( + Box::new(BrotliDecoder::new(Writer::new()))), + ContentEncoding::Deflate => Decoder::Deflate( + Box::new(DeflateDecoder::new(Writer::new()))), + ContentEncoding::Gzip => Decoder::Gzip(None), + _ => Decoder::Identity, + }; + PayloadStream{ decoder: dec, dst: BytesMut::new() } + } +} + +impl PayloadStream { + + pub fn feed_eof(&mut self) -> io::Result> { + match self.decoder { + Decoder::Br(ref mut decoder) => { + match decoder.finish() { + Ok(mut writer) => { + let b = writer.take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + }, + Err(e) => Err(e), + } + }, + Decoder::Gzip(ref mut decoder) => { + if let Some(ref mut decoder) = *decoder { + decoder.as_mut().get_mut().eof = true; + + loop { + self.dst.reserve(8192); + match decoder.read(unsafe{self.dst.bytes_mut()}) { + Ok(n) => { + if n == 0 { + return Ok(Some(self.dst.take().freeze())) + } else { + unsafe{self.dst.advance_mut(n)}; + } + } + Err(e) => return Err(e), + } + } + } else { + Ok(None) + } + }, + Decoder::Deflate(ref mut decoder) => { + match decoder.try_finish() { + Ok(_) => { + let b = decoder.get_mut().take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + }, + Err(e) => Err(e), + } + }, + Decoder::Identity => Ok(None), + } + } + + pub fn feed_data(&mut self, data: Bytes) -> io::Result> { + match self.decoder { + Decoder::Br(ref mut decoder) => { + match decoder.write(&data).and_then(|_| decoder.flush()) { + Ok(_) => { + let b = decoder.get_mut().take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + }, + Err(e) => Err(e) + } + }, + Decoder::Gzip(ref mut decoder) => { + if decoder.is_none() { + *decoder = Some( + Box::new(GzDecoder::new( + Wrapper{buf: BytesMut::from(data), eof: false}))); + } else { + let _ = decoder.as_mut().unwrap().write(&data); + } + + loop { + self.dst.reserve(8192); + match decoder.as_mut().as_mut().unwrap().read(unsafe{self.dst.bytes_mut()}) { + Ok(n) => { + if n == 0 { + return Ok(Some(self.dst.take().freeze())); + } else { + unsafe{self.dst.advance_mut(n)}; + } + } + Err(e) => { + return Err(e) + } + } + } + }, + Decoder::Deflate(ref mut decoder) => { + match decoder.write(&data).and_then(|_| decoder.flush()) { + Ok(_) => { + let b = decoder.get_mut().take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + }, + Err(e) => Err(e), + } + }, + Decoder::Identity => Ok(Some(data)), + } + } +} + pub(crate) enum ContentEncoder { Deflate(DeflateEncoder), Gzip(GzEncoder), diff --git a/tests/test_client.rs b/tests/test_client.rs index cac1ab78e..b29bc522b 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -116,6 +116,32 @@ fn test_client_gzip_encoding() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } +#[test] +fn test_client_gzip_encoding_large() { + let data = STR.to_owned() + STR + STR + STR + STR + STR + STR + STR + STR + STR; + + let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(httpcodes::HTTPOk + .build() + .content_encoding(headers::ContentEncoding::Deflate) + .body(bytes)) + }).responder()} + )); + + // client request + let request = srv.post() + .content_encoding(headers::ContentEncoding::Gzip) + .body(data.clone()).unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from(data)); +} + #[test] fn test_client_brotli_encoding() { let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { diff --git a/tests/test_server.rs b/tests/test_server.rs index 44d0316d1..53d7ea49f 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -136,27 +136,32 @@ fn test_simple() { #[test] fn test_headers() { + let data = STR.to_owned() + STR + STR + STR + STR + STR + STR + STR + STR + STR; + let srv_data = Arc::new(data.clone()); let mut srv = test::TestServer::new( - |app| app.handler(|_| { - let mut builder = httpcodes::HTTPOk.build(); - for idx in 0..90 { - builder.header( - format!("X-TEST-{}", idx).as_str(), - "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST "); - } - builder.body(STR)})); + move |app| { + let data = srv_data.clone(); + app.handler(move |_| { + let mut builder = httpcodes::HTTPOk.build(); + for idx in 0..90 { + builder.header( + format!("X-TEST-{}", idx).as_str(), + "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST "); + } + builder.body(data.as_ref())}) + }); let request = srv.get().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); @@ -164,7 +169,7 @@ fn test_headers() { // read response let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + assert_eq!(bytes, Bytes::from(data)); } #[test] @@ -203,6 +208,33 @@ fn test_body_gzip() { assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } +#[test] +fn test_body_gzip_large() { + let data = STR.to_owned() + STR + STR + STR + STR + STR + STR + STR + STR + STR; + let srv_data = Arc::new(data.clone()); + + let mut srv = test::TestServer::new( + move |app| { + let data = srv_data.clone(); + app.handler( + move |_| httpcodes::HTTPOk.build() + .content_encoding(headers::ContentEncoding::Gzip) + .body(data.as_ref()))}); + + let request = srv.get().disable_decompress().finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + + // decode + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from(data)); +} + #[test] fn test_body_chunked_implicit() { let mut srv = test::TestServer::new( @@ -430,6 +462,35 @@ fn test_gzip_encoding() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } +#[test] +fn test_gzip_encoding_large() { + let data = STR.to_owned() + STR + STR + STR + STR + STR + STR + STR + STR + STR; + let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(httpcodes::HTTPOk + .build() + .content_encoding(headers::ContentEncoding::Identity) + .body(bytes)) + }).responder()} + )); + + // client request + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); + + let request = srv.post() + .header(header::CONTENT_ENCODING, "gzip") + .body(enc.clone()).unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from(data)); +} + #[test] fn test_deflate_encoding() { let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { @@ -458,6 +519,35 @@ fn test_deflate_encoding() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } +#[test] +fn test_deflate_encoding_large() { + let data = STR.to_owned() + STR + STR + STR + STR + STR + STR + STR + STR + STR + STR; + let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(httpcodes::HTTPOk + .build() + .content_encoding(headers::ContentEncoding::Identity) + .body(bytes)) + }).responder()} + )); + + let mut e = DeflateEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); + + // client request + let request = srv.post() + .header(header::CONTENT_ENCODING, "deflate") + .body(enc).unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from(data)); +} + #[test] fn test_brotli_encoding() { let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { @@ -486,6 +576,35 @@ fn test_brotli_encoding() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } +#[test] +fn test_brotli_encoding_large() { + let data = STR.to_owned() + STR + STR + STR + STR + STR + STR + STR + STR + STR + STR; + let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(httpcodes::HTTPOk + .build() + .content_encoding(headers::ContentEncoding::Identity) + .body(bytes)) + }).responder()} + )); + + let mut e = BrotliEncoder::new(Vec::new(), 5); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); + + // client request + let request = srv.post() + .header(header::CONTENT_ENCODING, "br") + .body(enc).unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from(data)); +} + #[test] fn test_h2() { let srv = test::TestServer::new(|app| app.handler(|_|{ From 631fe72a46588292beda8ed7f2ea2abc916281d2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 4 Mar 2018 10:18:42 -0800 Subject: [PATCH 72/84] websockets text() is more generic --- src/ws/client.rs | 2 +- src/ws/context.rs | 2 +- tools/wsload/src/wsclient.rs | 24 ++++++++++++------------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/ws/client.rs b/src/ws/client.rs index 17c2e8320..c8fdec0ff 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -489,7 +489,7 @@ impl ClientWriter { /// Send text frame #[inline] - pub fn text>(&mut self, text: T) { + pub fn text>(&mut self, text: T) { self.write(Frame::message(text.into(), OpCode::Text, true, true)); } diff --git a/src/ws/context.rs b/src/ws/context.rs index 56320c895..4b0775f6a 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -132,7 +132,7 @@ impl WebsocketContext where A: Actor { /// Send text frame #[inline] - pub fn text>(&mut self, text: T) { + pub fn text>(&mut self, text: T) { self.write(Frame::message(text.into(), OpCode::Text, true, false)); } diff --git a/tools/wsload/src/wsclient.rs b/tools/wsload/src/wsclient.rs index e6438c634..2d8db7fb7 100644 --- a/tools/wsload/src/wsclient.rs +++ b/tools/wsload/src/wsclient.rs @@ -19,7 +19,7 @@ use futures::Future; use rand::{thread_rng, Rng}; use actix::prelude::*; -use actix_web::ws::{Message, WsClientError, WsClient, WsClientWriter}; +use actix_web::ws; fn main() { @@ -71,21 +71,21 @@ fn main() { let perf = perf_counters.clone(); let addr = Arbiter::new(format!("test {}", t)); - addr.send(actix::msgs::Execute::new(move || -> Result<(), ()> { + addr.do_send(actix::msgs::Execute::new(move || -> Result<(), ()> { let mut reps = report; for _ in 0..concurrency { let pl2 = pl.clone(); let perf2 = perf.clone(); Arbiter::handle().spawn( - WsClient::new(&ws).connect().unwrap() + ws::Client::new(&ws).connect() .map_err(|e| { println!("Error: {}", e); - Arbiter::system().send(actix::msgs::SystemExit(0)); + Arbiter::system().do_send(actix::msgs::SystemExit(0)); () }) .map(move |(reader, writer)| { - let addr: SyncAddress<_> = ChatClient::create(move |ctx| { + let addr: Addr = ChatClient::create(move |ctx| { ChatClient::add_stream(reader, ctx); ChatClient{conn: writer, payload: pl2, @@ -114,7 +114,7 @@ fn parse_u64_default(input: Option<&str>, default: u64) -> u64 { } struct ChatClient{ - conn: WsClientWriter, + conn: ws::ClientWriter, payload: Arc, ts: u64, bin: bool, @@ -133,9 +133,9 @@ impl Actor for ChatClient { } } - fn stopping(&mut self, _: &mut Context) -> bool { - Arbiter::system().send(actix::msgs::SystemExit(0)); - true + fn stopping(&mut self, _: &mut Context) -> Running { + Arbiter::system().do_send(actix::msgs::SystemExit(0)); + Running::Stop } } @@ -171,15 +171,15 @@ impl ChatClient { } /// Handle server websocket messages -impl StreamHandler for ChatClient { +impl StreamHandler for ChatClient { fn finished(&mut self, ctx: &mut Context) { ctx.stop() } - fn handle(&mut self, msg: Message, ctx: &mut Context) { + fn handle(&mut self, msg: ws::Message, ctx: &mut Context) { match msg { - Message::Text(txt) => { + ws::Message::Text(txt) => { if txt == self.payload.as_ref().as_str() { self.perf_counters.register_request(); self.perf_counters.register_latency(time::precise_time_ns() - self.ts); From 11347e3c7dca973ef76b2626b731a2d24e1e606e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 4 Mar 2018 10:33:18 -0800 Subject: [PATCH 73/84] Allow to use Arc> as response/request body --- CHANGES.md | 2 ++ src/body.rs | 31 +++++++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 163d136fb..faa7c9f40 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,8 @@ ## 0.4.4 (2018-03-xx) +* Allow to use Arc> as response/request body + * Fix handling of requests with an encoded body with a length > 8192 #93 ## 0.4.3 (2018-03-03) diff --git a/src/body.rs b/src/body.rs index ebd011e9c..fe6303438 100644 --- a/src/body.rs +++ b/src/body.rs @@ -36,6 +36,8 @@ pub enum Binary { /// Shared string body #[doc(hidden)] ArcSharedString(Arc), + /// Shared vec body + SharedVec(Arc>), } impl Body { @@ -115,6 +117,7 @@ impl Binary { Binary::Slice(slice) => slice.len(), Binary::SharedString(ref s) => s.len(), Binary::ArcSharedString(ref s) => s.len(), + Binary::SharedVec(ref s) => s.len(), } } @@ -134,8 +137,9 @@ impl Clone for Binary { match *self { Binary::Bytes(ref bytes) => Binary::Bytes(bytes.clone()), Binary::Slice(slice) => Binary::Bytes(Bytes::from(slice)), - Binary::SharedString(ref s) => Binary::Bytes(Bytes::from(s.as_str())), - Binary::ArcSharedString(ref s) => Binary::Bytes(Bytes::from(s.as_str())), + Binary::SharedString(ref s) => Binary::SharedString(s.clone()), + Binary::ArcSharedString(ref s) => Binary::ArcSharedString(s.clone()), + Binary::SharedVec(ref s) => Binary::SharedVec(s.clone()), } } } @@ -147,6 +151,7 @@ impl Into for Binary { Binary::Slice(slice) => Bytes::from(slice), Binary::SharedString(s) => Bytes::from(s.as_str()), Binary::ArcSharedString(s) => Bytes::from(s.as_str()), + Binary::SharedVec(s) => Bytes::from(AsRef::<[u8]>::as_ref(s.as_ref())), } } } @@ -217,6 +222,18 @@ impl<'a> From<&'a Arc> for Binary { } } +impl From>> for Binary { + fn from(body: Arc>) -> Binary { + Binary::SharedVec(body) + } +} + +impl<'a> From<&'a Arc>> for Binary { + fn from(body: &'a Arc>) -> Binary { + Binary::SharedVec(Arc::clone(body)) + } +} + impl AsRef<[u8]> for Binary { fn as_ref(&self) -> &[u8] { match *self { @@ -224,6 +241,7 @@ impl AsRef<[u8]> for Binary { Binary::Slice(slice) => slice, Binary::SharedString(ref s) => s.as_bytes(), Binary::ArcSharedString(ref s) => s.as_bytes(), + Binary::SharedVec(ref s) => s.as_ref().as_ref(), } } } @@ -304,6 +322,15 @@ mod tests { assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes()); } + #[test] + fn test_shared_vec() { + let b = Arc::new(Vec::from(&b"test"[..])); + assert_eq!(Binary::from(b.clone()).len(), 4); + assert_eq!(Binary::from(b.clone()).as_ref(), &b"test"[..]); + assert_eq!(Binary::from(&b).len(), 4); + assert_eq!(Binary::from(&b).as_ref(), &b"test"[..]); + } + #[test] fn test_bytes_mut() { let b = BytesMut::from("test"); From dbfa1f0ac8bae940689be3f811310f432f72c135 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 4 Mar 2018 10:44:41 -0800 Subject: [PATCH 74/84] fix example --- examples/websocket/src/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/websocket/src/client.rs b/examples/websocket/src/client.rs index 5eeb3bc41..34ff24372 100644 --- a/examples/websocket/src/client.rs +++ b/examples/websocket/src/client.rs @@ -88,7 +88,7 @@ impl Handler for ChatClient { type Result = (); fn handle(&mut self, msg: ClientCommand, ctx: &mut Context) { - self.0.text(msg.0.as_str()) + self.0.text(msg.0) } } From 0adb7e855327bfb4c83de7b27c531954353d7783 Mon Sep 17 00:00:00 2001 From: messense Date: Mon, 5 Mar 2018 09:54:58 +0800 Subject: [PATCH 75/84] Use str::repeat --- tests/test_client.rs | 2 +- tests/test_server.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_client.rs b/tests/test_client.rs index b29bc522b..aaa3fa786 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -118,7 +118,7 @@ fn test_client_gzip_encoding() { #[test] fn test_client_gzip_encoding_large() { - let data = STR.to_owned() + STR + STR + STR + STR + STR + STR + STR + STR + STR; + let data = STR.repeat(10); let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { req.body() diff --git a/tests/test_server.rs b/tests/test_server.rs index 53d7ea49f..92a876b5c 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -136,7 +136,7 @@ fn test_simple() { #[test] fn test_headers() { - let data = STR.to_owned() + STR + STR + STR + STR + STR + STR + STR + STR + STR; + let data = STR.repeat(10); let srv_data = Arc::new(data.clone()); let mut srv = test::TestServer::new( move |app| { @@ -210,7 +210,7 @@ fn test_body_gzip() { #[test] fn test_body_gzip_large() { - let data = STR.to_owned() + STR + STR + STR + STR + STR + STR + STR + STR + STR; + let data = STR.repeat(10); let srv_data = Arc::new(data.clone()); let mut srv = test::TestServer::new( @@ -464,7 +464,7 @@ fn test_gzip_encoding() { #[test] fn test_gzip_encoding_large() { - let data = STR.to_owned() + STR + STR + STR + STR + STR + STR + STR + STR + STR; + let data = STR.repeat(10); let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { req.body() .and_then(|bytes: Bytes| { @@ -521,7 +521,7 @@ fn test_deflate_encoding() { #[test] fn test_deflate_encoding_large() { - let data = STR.to_owned() + STR + STR + STR + STR + STR + STR + STR + STR + STR + STR; + let data = STR.repeat(10); let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { req.body() .and_then(|bytes: Bytes| { @@ -578,7 +578,7 @@ fn test_brotli_encoding() { #[test] fn test_brotli_encoding_large() { - let data = STR.to_owned() + STR + STR + STR + STR + STR + STR + STR + STR + STR + STR; + let data = STR.repeat(10); let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { req.body() .and_then(|bytes: Bytes| { From cbb821148b3a51c546c17033105aca2465c3912f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 4 Mar 2018 20:14:58 -0800 Subject: [PATCH 76/84] explicitly set tcp nodelay --- src/server/channel.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/server/channel.rs b/src/server/channel.rs index 2416a3f66..390aaee87 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -32,9 +32,10 @@ pub struct HttpChannel where T: IoStream, H: HttpHandler + 'static { impl HttpChannel where T: IoStream, H: HttpHandler + 'static { pub(crate) fn new(settings: Rc>, - io: T, peer: Option, http2: bool) -> HttpChannel + mut io: T, peer: Option, http2: bool) -> HttpChannel { settings.add_channel(); + let _ = io.set_nodelay(true); if http2 { HttpChannel { From e708f511560301fa39331b6a6284cae6a9b50b79 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 4 Mar 2018 20:28:06 -0800 Subject: [PATCH 77/84] prep release --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index faa7c9f40..7d823e865 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## 0.4.4 (2018-03-xx) +## 0.4.4 (2018-03-04) * Allow to use Arc> as response/request body From c2741054bb7219f407cfcc055b8d5741d6b652c3 Mon Sep 17 00:00:00 2001 From: messense Date: Mon, 5 Mar 2018 21:09:13 +0800 Subject: [PATCH 78/84] Add unix domain socket example --- .travis.yml | 1 + Cargo.toml | 1 + examples/unix-socket/Cargo.toml | 10 ++++++++++ examples/unix-socket/src/main.rs | 31 +++++++++++++++++++++++++++++++ 4 files changed, 43 insertions(+) create mode 100644 examples/unix-socket/Cargo.toml create mode 100644 examples/unix-socket/src/main.rs diff --git a/.travis.yml b/.travis.yml index 33fe904de..ddaaae14b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -65,6 +65,7 @@ script: cd examples/tls && cargo check && cd ../.. cd examples/websocket-chat && cargo check && cd ../.. cd examples/websocket && cargo check && cd ../.. + cd examples/unix-socket && cargo check && cd ../.. fi - | if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then diff --git a/Cargo.toml b/Cargo.toml index eeca59366..9387ad6f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,5 +114,6 @@ members = [ "examples/websocket", "examples/websocket-chat", "examples/web-cors/backend", + "examples/unix-socket", "tools/wsload/", ] diff --git a/examples/unix-socket/Cargo.toml b/examples/unix-socket/Cargo.toml new file mode 100644 index 000000000..a7c31f212 --- /dev/null +++ b/examples/unix-socket/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "unix-socket" +version = "0.1.0" +authors = ["Messense Lv "] + +[dependencies] +env_logger = "0.5" +actix = "0.5" +actix-web = { path = "../../" } +tokio-uds = "0.1" diff --git a/examples/unix-socket/src/main.rs b/examples/unix-socket/src/main.rs new file mode 100644 index 000000000..a56d428a7 --- /dev/null +++ b/examples/unix-socket/src/main.rs @@ -0,0 +1,31 @@ +extern crate actix; +extern crate actix_web; +extern crate env_logger; +extern crate tokio_uds; + +use actix::*; +use actix_web::*; +use tokio_uds::UnixListener; + + +fn index(_req: HttpRequest) -> &'static str { + "Hello world!" +} + +fn main() { + ::std::env::set_var("RUST_LOG", "actix_web=info"); + let _ = env_logger::init(); + let sys = actix::System::new("unix-socket"); + + let listener = UnixListener::bind("/tmp/actix-uds.socket", Arbiter::handle()).expect("bind failed"); + let _addr = HttpServer::new( + || Application::new() + // enable logger + .middleware(middleware::Logger::default()) + .resource("/index.html", |r| r.f(|_| "Hello world!")) + .resource("/", |r| r.f(index))) + .start_incoming(listener.incoming(), false); + + println!("Started http server: /tmp/actix-uds.socket"); + let _ = sys.run(); +} From 2b942ec5f2af1b60d05c7469ce9d209c27888c58 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 5 Mar 2018 09:47:17 -0800 Subject: [PATCH 79/84] add uds example readme --- examples/unix-socket/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 examples/unix-socket/README.md diff --git a/examples/unix-socket/README.md b/examples/unix-socket/README.md new file mode 100644 index 000000000..03b0066a2 --- /dev/null +++ b/examples/unix-socket/README.md @@ -0,0 +1,14 @@ +## Unix domain socket example + +```bash +$ curl --unix-socket /tmp/actix-uds.socket http://localhost/ +Hello world! +``` + +Although this will only one thread for handling incoming connections +according to the +[documentation](https://actix.github.io/actix-web/actix_web/struct.HttpServer.html#method.start_incoming). + +And it does not delete the socket file (`/tmp/actix-uds.socket`) when stopping +the server so it will fail to start next time you run it unless you delete +the socket file manually. From ea2a8f6908a78d0e4d34b21bac80feb7f4fd532c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 5 Mar 2018 11:12:19 -0800 Subject: [PATCH 80/84] add http proxy example --- .travis.yml | 1 + Cargo.toml | 1 + examples/http-proxy/Cargo.toml | 11 ++++++++ examples/http-proxy/src/main.rs | 47 +++++++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+) create mode 100644 examples/http-proxy/Cargo.toml create mode 100644 examples/http-proxy/src/main.rs diff --git a/.travis.yml b/.travis.yml index ddaaae14b..640aa1b92 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,6 +55,7 @@ script: if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then cd examples/basics && cargo check && cd ../.. cd examples/hello-world && cargo check && cd ../.. + cd examples/http-proxy && cargo check && cd ../.. cd examples/multipart && cargo check && cd ../.. cd examples/json && cargo check && cd ../.. cd examples/juniper && cargo check && cd ../.. diff --git a/Cargo.toml b/Cargo.toml index 9387ad6f2..9b69e2560 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,6 +106,7 @@ members = [ "examples/r2d2", "examples/json", "examples/hello-world", + "examples/http-proxy", "examples/multipart", "examples/state", "examples/redis-session", diff --git a/examples/http-proxy/Cargo.toml b/examples/http-proxy/Cargo.toml new file mode 100644 index 000000000..7b9597bff --- /dev/null +++ b/examples/http-proxy/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "http-proxy" +version = "0.1.0" +authors = ["Nikolay Kim "] +workspace = "../.." + +[dependencies] +env_logger = "0.5" +futures = "0.1" +actix = "0.5" +actix-web = { path = "../../", features=["alpn"] } diff --git a/examples/http-proxy/src/main.rs b/examples/http-proxy/src/main.rs new file mode 100644 index 000000000..52416d4ac --- /dev/null +++ b/examples/http-proxy/src/main.rs @@ -0,0 +1,47 @@ +extern crate actix; +extern crate actix_web; +extern crate futures; +extern crate env_logger; + +use actix_web::*; +use futures::Future; +use futures::future::{ok, err, Either}; + + +fn index(_req: HttpRequest) -> Box> { + client::ClientRequest::get("https://www.rust-lang.org/en-US/") + .finish().unwrap() + .send() + .map_err(|e| error::Error::from(error::ErrorInternalServerError(e))) + .then(|result| match result { + Ok(resp) => { + Either::A(resp.body().from_err().and_then(|body| { + match httpcodes::HttpOk.build().body(body) { + Ok(resp) => ok(resp), + Err(e) => err(e.into()), + } + })) + }, + Err(e) => { + Either::B(err(error::Error::from(e))) + } + }) + .responder() +} + +fn main() { + ::std::env::set_var("RUST_LOG", "actix_web=info"); + let _ = env_logger::init(); + let sys = actix::System::new("ws-example"); + + let _addr = HttpServer::new( + || Application::new() + // enable logger + .middleware(middleware::Logger::default()) + .resource("/", |r| r.f(index))) + .bind("127.0.0.1:8080").unwrap() + .start(); + + println!("Started http server: 127.0.0.1:8080"); + let _ = sys.run(); +} From b282ec106e3ae2161064b3ad3841009b1eebe4eb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 5 Mar 2018 13:02:31 -0800 Subject: [PATCH 81/84] Add ResponseError impl for SendRequestError --- CHANGES.md | 6 +++++ examples/http-proxy/src/main.rs | 46 +++++++++++++++++++++------------ src/client/mod.rs | 17 ++++++++++++ 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7d823e865..b758e8b39 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## 0.4.5 (2018-03-xx) + +* Add `ResponseError` impl for `SendRequestError`. + This improves ergonomics of http client. + + ## 0.4.4 (2018-03-04) * Allow to use Arc> as response/request body diff --git a/examples/http-proxy/src/main.rs b/examples/http-proxy/src/main.rs index 52416d4ac..4808dc11b 100644 --- a/examples/http-proxy/src/main.rs +++ b/examples/http-proxy/src/main.rs @@ -4,40 +4,52 @@ extern crate futures; extern crate env_logger; use actix_web::*; -use futures::Future; -use futures::future::{ok, err, Either}; +use futures::{Future, Stream}; +/// Stream client request response and then send body to a server response fn index(_req: HttpRequest) -> Box> { client::ClientRequest::get("https://www.rust-lang.org/en-US/") .finish().unwrap() .send() - .map_err(|e| error::Error::from(error::ErrorInternalServerError(e))) - .then(|result| match result { - Ok(resp) => { - Either::A(resp.body().from_err().and_then(|body| { - match httpcodes::HttpOk.build().body(body) { - Ok(resp) => ok(resp), - Err(e) => err(e.into()), - } + .map_err(error::Error::from) // <- convert SendRequestError to an Error + .and_then( + |resp| resp.body() // <- this is MessageBody type, resolves to complete body + .from_err() // <- convet PayloadError to a Error + .and_then(|body| { // <- we got complete body, now send as server response + httpcodes::HttpOk.build() + .body(body) + .map_err(error::Error::from) })) - }, - Err(e) => { - Either::B(err(error::Error::from(e))) - } + .responder() +} + +/// stream client request to a server response +fn streaming(_req: HttpRequest) -> Box> { + // send client request + client::ClientRequest::get("https://www.rust-lang.org/en-US/") + .finish().unwrap() + .send() // <- connect to host and send request + .map_err(error::Error::from) // <- convert SendRequestError to an Error + .and_then(|resp| { // <- we received client response + httpcodes::HttpOk.build() + // read one chunk from client response and send this chunk to a server response + // .from_err() converts PayloadError to a Error + .body(Body::Streaming(Box::new(resp.from_err()))) + .map_err(|e| e.into()) // HttpOk::build() mayb return HttpError, we need to convert it to a Error }) .responder() } fn main() { ::std::env::set_var("RUST_LOG", "actix_web=info"); - let _ = env_logger::init(); - let sys = actix::System::new("ws-example"); + env_logger::init(); + let sys = actix::System::new("http-proxy"); let _addr = HttpServer::new( || Application::new() - // enable logger .middleware(middleware::Logger::default()) + .resource("/streaming", |r| r.f(streaming)) .resource("/", |r| r.f(index))) .bind("127.0.0.1:8080").unwrap() .start(); diff --git a/src/client/mod.rs b/src/client/mod.rs index f3d8172ba..a4ee14178 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -12,3 +12,20 @@ pub use self::response::ClientResponse; pub use self::connector::{Connect, Connection, ClientConnector, ClientConnectorError}; pub(crate) use self::writer::HttpClientWriter; pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError}; + + +use httpcodes; +use httpresponse::HttpResponse; +use error::ResponseError; + + +/// Convert `SendRequestError` to a `HttpResponse` +impl ResponseError for SendRequestError { + + fn error_response(&self) -> HttpResponse { + match *self { + SendRequestError::Connector(_) => httpcodes::HttpBadGateway.into(), + _ => httpcodes::HttpInternalServerError.into(), + } + } +} From c8844425ade02ae2cbc7cc47d7cd3c75affd7f6e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 5 Mar 2018 13:31:30 -0800 Subject: [PATCH 82/84] Enable compression support for NamedFile --- CHANGES.md | 2 ++ examples/http-proxy/src/main.rs | 2 +- src/fs.rs | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b758e8b39..278e4780e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,8 @@ ## 0.4.5 (2018-03-xx) +* Enable compression support for `NamedFile` + * Add `ResponseError` impl for `SendRequestError`. This improves ergonomics of http client. diff --git a/examples/http-proxy/src/main.rs b/examples/http-proxy/src/main.rs index 4808dc11b..551101c97 100644 --- a/examples/http-proxy/src/main.rs +++ b/examples/http-proxy/src/main.rs @@ -24,7 +24,7 @@ fn index(_req: HttpRequest) -> Box> { .responder() } -/// stream client request to a server response +/// streaming client request to a streaming server response fn streaming(_req: HttpRequest) -> Box> { // send client request client::ClientRequest::get("https://www.rust-lang.org/en-US/") diff --git a/src/fs.rs b/src/fs.rs index 8525c02fd..a0b932bbc 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -85,7 +85,6 @@ impl Responder for NamedFile { fn respond_to(mut self, _: HttpRequest) -> Result { let mut resp = HttpOk.build(); - resp.content_encoding(ContentEncoding::Identity); if let Some(ext) = self.path().extension() { let mime = get_mime_type(&ext.to_string_lossy()); resp.content_type(format!("{}", mime).as_str()); From 67f5a949a4056c74f44e3bf73d1cb30103692b68 Mon Sep 17 00:00:00 2001 From: flip111 Date: Tue, 6 Mar 2018 01:35:41 +0100 Subject: [PATCH 83/84] Update qs_14.md fix syntax error on use statement --- guide/src/qs_14.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/src/qs_14.md b/guide/src/qs_14.md index e19d0ea9b..034832cd8 100644 --- a/guide/src/qs_14.md +++ b/guide/src/qs_14.md @@ -12,7 +12,7 @@ We have to define sync actor and connection that this actor will use. Same appro could be used for other databases. ```rust,ignore -use actix::prelude::*;* +use actix::prelude::*; struct DbExecutor(SqliteConnection); From 5b530f11b5a2597b6e3651f971205c20e8291e9a Mon Sep 17 00:00:00 2001 From: flip111 Date: Tue, 6 Mar 2018 01:46:16 +0100 Subject: [PATCH 84/84] Update qs_14.md fix missing semicolon --- guide/src/qs_14.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/src/qs_14.md b/guide/src/qs_14.md index 034832cd8..72827e4eb 100644 --- a/guide/src/qs_14.md +++ b/guide/src/qs_14.md @@ -36,7 +36,7 @@ We can send `CreateUser` message to `DbExecutor` actor, and as result we get ```rust,ignore impl Handler for DbExecutor { - type Result = Result + type Result = Result; fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result {