diff --git a/.travis.yml b/.travis.yml index 00f64d246..b1b0769e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,9 +11,9 @@ matrix: - rust: 1.31.0 - rust: stable - rust: beta - - rust: nightly-2019-03-02 + - rust: nightly-2019-04-02 allow_failures: - - rust: nightly-2019-03-02 + - rust: nightly-2019-04-02 env: global: @@ -26,7 +26,7 @@ before_install: - sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev before_cache: | - if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then + if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-04-02" ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin fi @@ -50,7 +50,7 @@ after_success: echo "Uploaded documentation" fi - | - if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then + if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-04-02" ]]; then taskset -c 0 cargo tarpaulin --out Xml --all --all-features bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" diff --git a/CHANGES.md b/CHANGES.md index 7fb3b8341..3965a7d16 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,13 +1,27 @@ # Changes +## [1.0.0-alpha.4] - 2019-04-xx + +### Added + +* `App::configure()` allow to offload app configuration to different methods + +* Added `URLPath` option for logger + +### Changed + +* Move multipart support to actix-multipart crate + + +## [1.0.0-alpha.3] - 2019-04-02 + ### Changed * Renamed `TestRequest::to_service()` to `TestRequest::to_srv_request()` * Renamed `TestRequest::to_response()` to `TestRequest::to_srv_response()` -* Added `URLPath` option for logger - +* Removed `Deref` impls ### Removed diff --git a/Cargo.toml b/Cargo.toml index b5d0e8769..507be4bb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,8 +30,10 @@ members = [ "actix-http", "actix-files", "actix-session", + "actix-multipart", "actix-web-actors", "actix-web-codegen", + "test-server", ] [package.metadata.docs.rs] @@ -71,18 +73,17 @@ actix-utils = "0.3.4" actix-router = "0.1.0" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-alpha.1" -actix-http = { version = "0.1.0-alpha.2", features=["fail"] } +actix-http = { version = "0.1.0-alpha.3", features=["fail"] } actix-server = "0.4.2" actix-server-config = "0.1.0" actix-threadpool = "0.1.0" -awc = { version = "0.1.0-alpha.2", optional = true } +awc = { version = "0.1.0-alpha.3", optional = true } bytes = "0.4" derive_more = "0.14" encoding = "0.2" futures = "0.1" hashbrown = "0.1.8" -httparse = "1.3" log = "0.4" mime = "0.3" net2 = "0.2.33" @@ -100,8 +101,8 @@ openssl = { version="0.10", optional = true } rustls = { version = "^0.15", optional = true } [dev-dependencies] -actix-http = { version = "0.1.0-alpha.2", features=["ssl", "brotli", "flate2-zlib"] } -actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] } +actix-http = { version = "0.1.0-alpha.3", features=["ssl", "brotli", "flate2-zlib"] } +actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } rand = "0.6" env_logger = "0.6" serde_derive = "1.0" @@ -116,4 +117,11 @@ codegen-units = 1 [patch.crates-io] actix = { git = "https://github.com/actix/actix.git" } +actix-web = { path = "." } actix-http = { path = "actix-http" } +actix-http-test = { path = "test-server" } +actix-web-codegen = { path = "actix-web-codegen" } +actix-web-actors = { path = "actix-web-actors" } +actix-session = { path = "actix-session" } +actix-files = { path = "actix-files" } +awc = { path = "awc" } diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 4fe8fadb3..7c46b40f7 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.1.0-alpha.2] - 2019-04-xx +## [0.1.0-alpha.2] - 2019-04-02 * Add default handler support diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 3f1bad69e..a1044c6da 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -18,7 +18,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = "1.0.0-alpha.2" +actix-web = "1.0.0-alpha.3" actix-service = "0.3.4" bitflags = "1" bytes = "0.4" @@ -31,4 +31,4 @@ percent-encoding = "1.0" v_htmlescape = "0.4" [dev-dependencies] -actix-web = { version = "1.0.0-alpha.2", features=["ssl"] } +actix-web = { version = "1.0.0-alpha.3", features=["ssl"] } diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index d5a47653e..8404ab319 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -552,7 +552,7 @@ impl

FromRequest

for PathBufWrp { type Future = Result; fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - PathBufWrp::get_pathbuf(req.match_info().path()) + PathBufWrp::get_pathbuf(req.request().match_info().path()) } } @@ -1049,7 +1049,7 @@ mod tests { .new_service(&()), ) .unwrap(); - let req = TestRequest::with_uri("/missing").to_service(); + let req = TestRequest::with_uri("/missing").to_srv_request(); let mut resp = test::call_success(&mut st, req); assert_eq!(resp.status(), StatusCode::OK); diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 79187e7a5..742ff8d3e 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,20 @@ # Changes -## [0.1.0-alpha.3] - 2019-04-xx +## [0.1.0-alpha.4] - 2019-04-xx + +### Changed + +* Export IntoHeaderValue + +### Deleted + +* Removed PayloadBuffer + +## [0.1.0-alpha.3] - 2019-04-02 + +### Added + +* Warn when an unsealed private cookie isn't valid UTF-8 ### Fixed @@ -8,6 +22,8 @@ * Preallocate read buffer for h1 codec +* Detect socket disconnection during protocol selection + ## [0.1.0-alpha.2] - 2019-03-29 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index a9fda44eb..9d4e15216 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -100,7 +100,7 @@ openssl = { version="0.10", optional = true } actix-rt = "0.2.2" actix-server = { version = "0.4.1", features=["ssl"] } actix-connect = { version = "0.1.0", features=["ssl"] } -actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] } +actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" openssl = { version="0.10" } diff --git a/actix-http/src/h1/client.rs b/actix-http/src/h1/client.rs index 6a50c0271..f93bc496a 100644 --- a/actix-http/src/h1/client.rs +++ b/actix-http/src/h1/client.rs @@ -10,7 +10,7 @@ use http::header::{ use http::{Method, Version}; use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; -use super::{decoder, encoder}; +use super::{decoder, encoder, reserve_readbuf}; use super::{Message, MessageType}; use crate::body::BodySize; use crate::config::ServiceConfig; @@ -150,6 +150,7 @@ impl Decoder for ClientCodec { } else { self.inner.payload = None; } + reserve_readbuf(src); Ok(Some(req)) } else { Ok(None) @@ -168,7 +169,10 @@ impl Decoder for ClientPayloadCodec { ); Ok(match self.inner.payload.as_mut().unwrap().decode(src)? { - Some(PayloadItem::Chunk(chunk)) => Some(Some(chunk)), + Some(PayloadItem::Chunk(chunk)) => { + reserve_readbuf(src); + Some(Some(chunk)) + } Some(PayloadItem::Eof) => { self.inner.payload.take(); Some(None) diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index e4895f2dd..6e891e7cd 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -9,7 +9,7 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCOD use http::{Method, StatusCode, Version}; use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; -use super::{decoder, encoder}; +use super::{decoder, encoder, reserve_readbuf}; use super::{Message, MessageType}; use crate::body::BodySize; use crate::config::ServiceConfig; @@ -19,9 +19,6 @@ use crate::message::{ConnectionType, Head, ResponseHead}; use crate::request::Request; use crate::response::Response; -const LW: usize = 2 * 1024; -const HW: usize = 32 * 1024; - bitflags! { struct Flags: u8 { const HEAD = 0b0000_0001; @@ -108,14 +105,12 @@ impl Decoder for Codec { type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - let cap = src.capacity(); - if cap < LW { - src.reserve(HW - cap); - } - if self.payload.is_some() { Ok(match self.payload.as_mut().unwrap().decode(src)? { - Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))), + Some(PayloadItem::Chunk(chunk)) => { + reserve_readbuf(src); + Some(Message::Chunk(Some(chunk))) + } Some(PayloadItem::Eof) => { self.payload.take(); Some(Message::Chunk(None)) @@ -140,6 +135,7 @@ impl Decoder for Codec { self.flags.insert(Flags::STREAM); } } + reserve_readbuf(src); Ok(Some(Message::Item(req))) } else { Ok(None) diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs index e3d63c521..a05f2800c 100644 --- a/actix-http/src/h1/mod.rs +++ b/actix-http/src/h1/mod.rs @@ -1,5 +1,5 @@ //! HTTP/1 implementation -use bytes::Bytes; +use bytes::{Bytes, BytesMut}; mod client; mod codec; @@ -12,7 +12,7 @@ mod service; pub use self::client::{ClientCodec, ClientPayloadCodec}; pub use self::codec::Codec; pub use self::dispatcher::Dispatcher; -pub use self::payload::{Payload, PayloadBuffer}; +pub use self::payload::{Payload, PayloadWriter}; pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; #[derive(Debug)] @@ -38,6 +38,16 @@ pub enum MessageType { Stream, } +const LW: usize = 2 * 1024; +const HW: usize = 32 * 1024; + +pub(crate) fn reserve_readbuf(src: &mut BytesMut) { + let cap = src.capacity(); + if cap < LW { + src.reserve(HW - cap); + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 73d05c4bb..bef87f7dc 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -1,10 +1,9 @@ //! Payload stream use std::cell::RefCell; -use std::cmp; use std::collections::VecDeque; use std::rc::{Rc, Weak}; -use bytes::{Bytes, BytesMut}; +use bytes::Bytes; use futures::task::current as current_task; use futures::task::Task; use futures::{Async, Poll, Stream}; @@ -15,7 +14,7 @@ use crate::error::PayloadError; pub(crate) const MAX_BUFFER_SIZE: usize = 32_768; #[derive(Debug, PartialEq)] -pub(crate) enum PayloadStatus { +pub enum PayloadStatus { Read, Pause, Dropped, @@ -107,7 +106,7 @@ impl Clone for Payload { } /// Payload writer interface. -pub(crate) trait PayloadWriter { +pub trait PayloadWriter { /// Set stream error. fn set_error(&mut self, err: PayloadError); @@ -258,407 +257,12 @@ impl Inner { } } -/// Payload buffer -pub struct PayloadBuffer { - len: usize, - items: VecDeque, - stream: S, -} - -impl PayloadBuffer -where - S: Stream, -{ - /// Create new `PayloadBuffer` instance - pub fn new(stream: S) -> Self { - PayloadBuffer { - len: 0, - items: VecDeque::new(), - stream, - } - } - - /// Get mutable reference to an inner stream. - pub fn get_mut(&mut self) -> &mut S { - &mut self.stream - } - - #[inline] - 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, - }) - } - - /// Read first available chunk of bytes - #[inline] - 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), - } - } - } - - /// Check if buffer contains enough bytes - #[inline] - pub fn can_read(&mut self, size: usize) -> Poll, PayloadError> { - if size <= self.len { - Ok(Async::Ready(Some(true))) - } else { - match self.poll_stream()? { - Async::Ready(true) => self.can_read(size), - Async::Ready(false) => Ok(Async::Ready(None)), - Async::NotReady => Ok(Async::NotReady), - } - } - } - - /// Return reference to the first chunk of data - #[inline] - pub fn get_chunk(&mut self) -> Poll, PayloadError> { - if self.items.is_empty() { - match self.poll_stream()? { - Async::Ready(true) => (), - Async::Ready(false) => return Ok(Async::Ready(None)), - Async::NotReady => return Ok(Async::NotReady), - } - } - match self.items.front().map(|c| c.as_ref()) { - Some(chunk) => Ok(Async::Ready(Some(chunk))), - None => Ok(Async::NotReady), - } - } - - /// Read exact number of bytes - #[inline] - pub fn read_exact(&mut self, size: usize) -> Poll, PayloadError> { - if size <= self.len { - self.len -= size; - let mut chunk = self.items.pop_front().unwrap(); - if size < chunk.len() { - let buf = chunk.split_to(size); - self.items.push_front(chunk); - Ok(Async::Ready(Some(buf))) - } else if size == chunk.len() { - Ok(Async::Ready(Some(chunk))) - } else { - let mut buf = BytesMut::with_capacity(size); - buf.extend_from_slice(&chunk); - - while buf.len() < size { - let mut chunk = self.items.pop_front().unwrap(); - let rem = cmp::min(size - buf.len(), chunk.len()); - buf.extend_from_slice(&chunk.split_to(rem)); - if !chunk.is_empty() { - self.items.push_front(chunk); - } - } - Ok(Async::Ready(Some(buf.freeze()))) - } - } else { - match self.poll_stream()? { - Async::Ready(true) => self.read_exact(size), - Async::Ready(false) => Ok(Async::Ready(None)), - Async::NotReady => Ok(Async::NotReady), - } - } - } - - /// Remove specified amount if bytes from buffer - #[inline] - pub fn drop_bytes(&mut self, size: usize) { - if size <= self.len { - self.len -= size; - - let mut len = 0; - while len < size { - let mut chunk = self.items.pop_front().unwrap(); - let rem = cmp::min(size - len, chunk.len()); - len += rem; - if rem < chunk.len() { - chunk.split_to(rem); - self.items.push_front(chunk); - } - } - } - } - - /// Copy buffered data - 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.copy(size), - Async::Ready(false) => Ok(Async::Ready(None)), - Async::NotReady => Ok(Async::NotReady), - } - } - - /// Read until specified ending - pub fn read_until(&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.read_until(line), - Async::Ready(false) => Ok(Async::Ready(None)), - Async::NotReady => Ok(Async::NotReady), - } - } - - /// Read bytes until new line delimiter - pub fn readline(&mut self) -> Poll, PayloadError> { - self.read_until(b"\n") - } - - /// Put unprocessed data back to the buffer - pub fn unprocessed(&mut self, data: Bytes) { - self.len += data.len(); - self.items.push_front(data); - } - - /// Get remaining data from the buffer - 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::*; use actix_rt::Runtime; use futures::future::{lazy, result}; - #[test] - fn test_basic() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (_, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(payload.len, 0); - assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); - - let res: Result<(), ()> = Ok(()); - result(res) - })) - .unwrap(); - } - - #[test] - fn test_eof() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (mut sender, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); - sender.feed_data(Bytes::from("data")); - sender.feed_eof(); - - assert_eq!( - Async::Ready(Some(Bytes::from("data"))), - payload.readany().ok().unwrap() - ); - assert_eq!(payload.len, 0); - assert_eq!(Async::Ready(None), payload.readany().ok().unwrap()); - - let res: Result<(), ()> = Ok(()); - result(res) - })) - .unwrap(); - } - - #[test] - fn test_err() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (mut sender, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); - - sender.set_error(PayloadError::Incomplete(None)); - payload.readany().err().unwrap(); - let res: Result<(), ()> = Ok(()); - result(res) - })) - .unwrap(); - } - - #[test] - fn test_readany() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (mut sender, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); - - sender.feed_data(Bytes::from("line1")); - sender.feed_data(Bytes::from("line2")); - - assert_eq!( - Async::Ready(Some(Bytes::from("line1"))), - 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) - })) - .unwrap(); - } - - #[test] - fn test_readexactly() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (mut sender, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(Async::NotReady, payload.read_exact(2).ok().unwrap()); - - sender.feed_data(Bytes::from("line1")); - sender.feed_data(Bytes::from("line2")); - - assert_eq!( - Async::Ready(Some(Bytes::from_static(b"li"))), - payload.read_exact(2).ok().unwrap() - ); - assert_eq!(payload.len, 3); - - assert_eq!( - Async::Ready(Some(Bytes::from_static(b"ne1l"))), - payload.read_exact(4).ok().unwrap() - ); - assert_eq!(payload.len, 4); - - sender.set_error(PayloadError::Incomplete(None)); - payload.read_exact(10).err().unwrap(); - - let res: Result<(), ()> = Ok(()); - result(res) - })) - .unwrap(); - } - - #[test] - fn test_readuntil() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (mut sender, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap()); - - sender.feed_data(Bytes::from("line1")); - sender.feed_data(Bytes::from("line2")); - - assert_eq!( - Async::Ready(Some(Bytes::from("line"))), - payload.read_until(b"ne").ok().unwrap() - ); - assert_eq!(payload.len, 1); - - assert_eq!( - Async::Ready(Some(Bytes::from("1line2"))), - payload.read_until(b"2").ok().unwrap() - ); - assert_eq!(payload.len, 0); - - sender.set_error(PayloadError::Incomplete(None)); - payload.read_until(b"b").err().unwrap(); - - let res: Result<(), ()> = Ok(()); - result(res) - })) - .unwrap(); - } - #[test] fn test_unread_data() { Runtime::new() diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 1ef1bd198..deedd693f 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -20,7 +20,6 @@ pub use self::common::*; #[doc(hidden)] pub use self::shared::*; -#[doc(hidden)] /// A trait for any object that will represent a header field and value. pub trait Header where @@ -33,7 +32,6 @@ where fn parse(msg: &T) -> Result; } -#[doc(hidden)] /// A trait for any object that can be Converted to a `HeaderValue` pub trait IntoHeaderValue: Sized { /// The type returned in the event of a conversion error. @@ -97,6 +95,26 @@ impl IntoHeaderValue for String { } } +impl IntoHeaderValue for usize { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + let s = format!("{}", self); + HeaderValue::from_shared(Bytes::from(s)) + } +} + +impl IntoHeaderValue for u64 { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + let s = format!("{}", self); + HeaderValue::from_shared(Bytes::from(s)) + } +} + impl IntoHeaderValue for Mime { type Error = InvalidHeaderValueBytes; diff --git a/actix-http/src/service/service.rs b/actix-http/src/service/service.rs index 0bc1634d8..50a1a6bdf 100644 --- a/actix-http/src/service/service.rs +++ b/actix-http/src/service/service.rs @@ -247,7 +247,10 @@ where loop { unsafe { let b = item.1.bytes_mut(); - let n = { try_ready!(item.0.poll_read(b)) }; + let n = try_ready!(item.0.poll_read(b)); + if n == 0 { + return Ok(Async::Ready(())); + } item.1.advance_mut(n); if item.1.len() >= HTTP2_PREFACE.len() { break; diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index ea0c5eb9a..817164f81 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -35,10 +35,10 @@ fn test_h1_v2() { .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) }); - let response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get("/").send()).unwrap(); assert!(response.status().is_success()); - let request = srv.get().header("x-test", "111").send(); + let request = srv.get("/").header("x-test", "111").send(); let response = srv.block_on(request).unwrap(); assert!(response.status().is_success()); @@ -46,7 +46,7 @@ fn test_h1_v2() { let bytes = srv.load_body(response).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - let response = srv.block_on(srv.post().send()).unwrap(); + let response = srv.block_on(srv.post("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -61,7 +61,7 @@ fn test_connection_close() { .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) }); - let response = srv.block_on(srv.get().close_connection().send()).unwrap(); + let response = srv.block_on(srv.get("/").force_close().send()).unwrap(); assert!(response.status().is_success()); } diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index a18d19626..85cab929c 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -37,7 +37,7 @@ fn test_h1() { .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); - let response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get("/").send()).unwrap(); assert!(response.status().is_success()); } @@ -55,7 +55,7 @@ fn test_h1_2() { .map(|_| ()) }); - let response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get("/").send()).unwrap(); assert!(response.status().is_success()); } @@ -98,7 +98,7 @@ fn test_h2() -> std::io::Result<()> { ) }); - let response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget("/").send()).unwrap(); assert!(response.status().is_success()); Ok(()) } @@ -121,7 +121,7 @@ fn test_h2_1() -> std::io::Result<()> { ) }); - let response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget("/").send()).unwrap(); assert!(response.status().is_success()); Ok(()) } @@ -145,7 +145,7 @@ fn test_h2_body() -> std::io::Result<()> { ) }); - let response = srv.block_on(srv.sget().send_body(data.clone())).unwrap(); + let response = srv.block_on(srv.sget("/").send_body(data.clone())).unwrap(); assert!(response.status().is_success()); let body = srv.load_body(response).unwrap(); @@ -437,7 +437,7 @@ fn test_h1_headers() { }) }); - let response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -482,7 +482,7 @@ fn test_h2_headers() { }).map_err(|_| ())) }); - let response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -518,7 +518,7 @@ fn test_h1_body() { HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().body(STR))) }); - let response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -541,7 +541,7 @@ fn test_h2_body2() { ) }); - let response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -555,7 +555,7 @@ fn test_h1_head_empty() { HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) }); - let response = srv.block_on(srv.head().send()).unwrap(); + let response = srv.block_on(srv.head("/").send()).unwrap(); assert!(response.status().is_success()); { @@ -586,7 +586,7 @@ fn test_h2_head_empty() { ) }); - let response = srv.block_on(srv.shead().send()).unwrap(); + let response = srv.block_on(srv.shead("/").send()).unwrap(); assert!(response.status().is_success()); assert_eq!(response.version(), http::Version::HTTP_2); @@ -611,7 +611,7 @@ fn test_h1_head_binary() { }) }); - let response = srv.block_on(srv.head().send()).unwrap(); + let response = srv.block_on(srv.head("/").send()).unwrap(); assert!(response.status().is_success()); { @@ -646,7 +646,7 @@ fn test_h2_head_binary() { ) }); - let response = srv.block_on(srv.shead().send()).unwrap(); + let response = srv.block_on(srv.shead("/").send()).unwrap(); assert!(response.status().is_success()); { @@ -668,7 +668,7 @@ fn test_h1_head_binary2() { HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) }); - let response = srv.block_on(srv.head().send()).unwrap(); + let response = srv.block_on(srv.head("/").send()).unwrap(); assert!(response.status().is_success()); { @@ -695,7 +695,7 @@ fn test_h2_head_binary2() { ) }); - let response = srv.block_on(srv.shead().send()).unwrap(); + let response = srv.block_on(srv.shead("/").send()).unwrap(); assert!(response.status().is_success()); { @@ -719,7 +719,7 @@ fn test_h1_body_length() { }) }); - let response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -747,7 +747,7 @@ fn test_h2_body_length() { ) }); - let response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -768,7 +768,7 @@ fn test_h1_body_chunked_explicit() { }) }); - let response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get("/").send()).unwrap(); assert!(response.status().is_success()); assert_eq!( response @@ -810,7 +810,7 @@ fn test_h2_body_chunked_explicit() { ) }); - let response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget("/").send()).unwrap(); assert!(response.status().is_success()); assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); @@ -830,7 +830,7 @@ fn test_h1_body_chunked_implicit() { }) }); - let response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get("/").send()).unwrap(); assert!(response.status().is_success()); assert_eq!( response @@ -862,7 +862,7 @@ fn test_h1_response_http_error_handling() { })) }); - let response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get("/").send()).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response @@ -895,7 +895,7 @@ fn test_h2_response_http_error_handling() { ) }); - let response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget("/").send()).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response @@ -910,7 +910,7 @@ fn test_h1_service_error() { .h1(|_| Err::(error::ErrorBadRequest("error"))) }); - let response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get("/").send()).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response @@ -934,7 +934,7 @@ fn test_h2_service_error() { ) }); - let response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget("/").send()).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md new file mode 100644 index 000000000..6be07f2e2 --- /dev/null +++ b/actix-multipart/CHANGES.md @@ -0,0 +1,5 @@ +# Changes + +## [0.1.0-alpha.1] - 2019-04-xx + +* Split multipart support to separate crate diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml new file mode 100644 index 000000000..006f7066a --- /dev/null +++ b/actix-multipart/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "actix-multipart" +version = "0.1.0-alpha.1" +authors = ["Nikolay Kim "] +description = "Multipart support for actix web framework." +readme = "README.md" +keywords = ["http", "web", "framework", "async", "futures"] +homepage = "https://actix.rs" +repository = "https://github.com/actix/actix-web.git" +documentation = "https://docs.rs/actix-multipart/" +license = "MIT/Apache-2.0" +exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] +workspace = ".." +edition = "2018" + +[lib] +name = "actix_multipart" +path = "src/lib.rs" + +[dependencies] +actix-web = "1.0.0-alpha.3" +actix-service = "0.3.4" +bytes = "0.4" +derive_more = "0.14" +httparse = "1.3" +futures = "0.1.25" +log = "0.4" +mime = "0.3" +time = "0.1" +twoway = "0.2" + +[dev-dependencies] +actix-rt = "0.2.2" +actix-http = "0.1.0-alpha.3" \ No newline at end of file diff --git a/actix-multipart/README.md b/actix-multipart/README.md new file mode 100644 index 000000000..2739ff3d5 --- /dev/null +++ b/actix-multipart/README.md @@ -0,0 +1 @@ +# Multipart support for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-multipart)](https://crates.io/crates/actix-multipart) [![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) diff --git a/actix-multipart/src/error.rs b/actix-multipart/src/error.rs new file mode 100644 index 000000000..1b872187d --- /dev/null +++ b/actix-multipart/src/error.rs @@ -0,0 +1,46 @@ +//! Error and Result module +use actix_web::error::{ParseError, PayloadError}; +use actix_web::http::StatusCode; +use actix_web::{HttpResponse, ResponseError}; +use derive_more::{Display, From}; + +/// A set of errors that can occur during parsing multipart streams +#[derive(Debug, Display, From)] +pub enum MultipartError { + /// Content-Type header is not found + #[display(fmt = "No Content-type header found")] + NoContentType, + /// Can not parse Content-Type header + #[display(fmt = "Can not parse Content-Type header")] + ParseContentType, + /// Multipart boundary is not found + #[display(fmt = "Multipart boundary is not found")] + Boundary, + /// Multipart stream is incomplete + #[display(fmt = "Multipart stream is incomplete")] + Incomplete, + /// Error during field parsing + #[display(fmt = "{}", _0)] + Parse(ParseError), + /// Payload error + #[display(fmt = "{}", _0)] + Payload(PayloadError), +} + +/// Return `BadRequest` for `MultipartError` +impl ResponseError for MultipartError { + fn error_response(&self) -> HttpResponse { + HttpResponse::new(StatusCode::BAD_REQUEST) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_multipart_error() { + let resp: HttpResponse = MultipartError::Boundary.error_response(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } +} diff --git a/actix-multipart/src/extractor.rs b/actix-multipart/src/extractor.rs new file mode 100644 index 000000000..18c26c6fb --- /dev/null +++ b/actix-multipart/src/extractor.rs @@ -0,0 +1,57 @@ +//! Multipart payload support +use bytes::Bytes; +use futures::Stream; + +use actix_web::dev::ServiceFromRequest; +use actix_web::error::{Error, PayloadError}; +use actix_web::FromRequest; +use actix_web::HttpMessage; + +use crate::server::Multipart; + +/// Get request's payload as multipart stream +/// +/// Content-type: multipart/form-data; +/// +/// ## Server example +/// +/// ```rust +/// # use futures::{Future, Stream}; +/// # use futures::future::{ok, result, Either}; +/// use actix_web::{web, HttpResponse, Error}; +/// use actix_multipart as mp; +/// +/// fn index(payload: mp::Multipart) -> impl Future { +/// payload.from_err() // <- get multipart stream for current request +/// .and_then(|item| match item { // <- iterate over multipart items +/// mp::Item::Field(field) => { +/// // Field in turn is stream of *Bytes* object +/// Either::A(field.from_err() +/// .fold((), |_, chunk| { +/// println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk)); +/// Ok::<_, Error>(()) +/// })) +/// }, +/// mp::Item::Nested(mp) => { +/// // Or item could be nested Multipart stream +/// Either::B(ok(())) +/// } +/// }) +/// .fold((), |_, _| Ok::<_, Error>(())) +/// .map(|_| HttpResponse::Ok().into()) +/// } +/// # fn main() {} +/// ``` +impl

FromRequest

for Multipart +where + P: Stream + 'static, +{ + type Error = Error; + type Future = Result; + + #[inline] + fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { + let pl = req.take_payload(); + Ok(Multipart::new(req.headers(), pl)) + } +} diff --git a/actix-multipart/src/lib.rs b/actix-multipart/src/lib.rs new file mode 100644 index 000000000..602c27931 --- /dev/null +++ b/actix-multipart/src/lib.rs @@ -0,0 +1,6 @@ +mod error; +mod extractor; +mod server; + +pub use self::error::MultipartError; +pub use self::server::{Field, Item, Multipart}; diff --git a/src/types/multipart.rs b/actix-multipart/src/server.rs similarity index 70% rename from src/types/multipart.rs rename to actix-multipart/src/server.rs index 65a64d5e1..c1536af60 100644 --- a/src/types/multipart.rs +++ b/actix-multipart/src/server.rs @@ -4,26 +4,22 @@ use std::marker::PhantomData; use std::rc::Rc; use std::{cmp, fmt}; -use bytes::Bytes; +use bytes::{Bytes, BytesMut}; use futures::task::{current as current_task, Task}; use futures::{Async, Poll, Stream}; use httparse; use mime; -use crate::error::{Error, MultipartError, ParseError, PayloadError}; -use crate::extract::FromRequest; -use crate::http::header::{ +use actix_web::error::{ParseError, PayloadError}; +use actix_web::http::header::{ self, ContentDisposition, HeaderMap, HeaderName, HeaderValue, }; -use crate::http::HttpTryFrom; -use crate::service::ServiceFromRequest; -use crate::HttpMessage; +use actix_web::http::HttpTryFrom; + +use crate::error::MultipartError; const MAX_HEADERS: usize = 32; -type PayloadBuffer = - actix_http::h1::PayloadBuffer>>; - /// The server-side implementation of `multipart/form-data` requests. /// /// This will parse the incoming stream into `MultipartItem` instances via its @@ -37,59 +33,13 @@ pub struct Multipart { } /// Multipart item -pub enum MultipartItem { +pub enum Item { /// Multipart field - Field(MultipartField), + Field(Field), /// Nested multipart stream Nested(Multipart), } -/// Get request's payload as multipart stream -/// -/// Content-type: multipart/form-data; -/// -/// ## Server example -/// -/// ```rust -/// # use futures::{Future, Stream}; -/// # use futures::future::{ok, result, Either}; -/// use actix_web::{web, HttpResponse, Error}; -/// -/// fn index(payload: web::Multipart) -> impl Future { -/// payload.from_err() // <- get multipart stream for current request -/// .and_then(|item| match item { // <- iterate over multipart items -/// web::MultipartItem::Field(field) => { -/// // Field in turn is stream of *Bytes* object -/// Either::A(field.from_err() -/// .fold((), |_, chunk| { -/// println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk)); -/// Ok::<_, Error>(()) -/// })) -/// }, -/// web::MultipartItem::Nested(mp) => { -/// // Or item could be nested Multipart stream -/// Either::B(ok(())) -/// } -/// }) -/// .fold((), |_, _| Ok::<_, Error>(())) -/// .map(|_| HttpResponse::Ok().into()) -/// } -/// # fn main() {} -/// ``` -impl

FromRequest

for Multipart -where - P: Stream + 'static, -{ - type Error = Error; - type Future = Result; - - #[inline] - fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - let pl = req.take_payload(); - Ok(Multipart::new(req.headers(), pl)) - } -} - enum InnerMultipartItem { None, Field(Rc>), @@ -163,14 +113,18 @@ impl Multipart { } impl Stream for Multipart { - type Item = MultipartItem; + type Item = Item; type Error = MultipartError; fn poll(&mut self) -> Poll, Self::Error> { if let Some(err) = self.error.take() { Err(err) } else if self.safety.current() { - self.inner.as_mut().unwrap().borrow_mut().poll(&self.safety) + let mut inner = self.inner.as_mut().unwrap().borrow_mut(); + if let Some(payload) = inner.payload.get_mut(&self.safety) { + payload.poll_stream()?; + } + inner.poll(&self.safety) } else { Ok(Async::NotReady) } @@ -178,11 +132,18 @@ impl Stream for Multipart { } impl InnerMultipart { - fn read_headers(payload: &mut PayloadBuffer) -> Poll { - match payload.read_until(b"\r\n\r\n")? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(None) => Err(MultipartError::Incomplete), - Async::Ready(Some(bytes)) => { + fn read_headers( + payload: &mut PayloadBuffer, + ) -> Result, MultipartError> { + match payload.read_until(b"\r\n\r\n") { + None => { + if payload.eof { + Err(MultipartError::Incomplete) + } else { + Ok(None) + } + } + Some(bytes) => { let mut hdrs = [httparse::EMPTY_HEADER; MAX_HEADERS]; match httparse::parse_headers(&bytes, &mut hdrs) { Ok(httparse::Status::Complete((_, hdrs))) => { @@ -199,7 +160,7 @@ impl InnerMultipart { return Err(ParseError::Header.into()); } } - Ok(Async::Ready(headers)) + Ok(Some(headers)) } Ok(httparse::Status::Partial) => Err(ParseError::Header.into()), Err(err) => Err(ParseError::from(err).into()), @@ -211,23 +172,28 @@ impl InnerMultipart { fn read_boundary( payload: &mut PayloadBuffer, boundary: &str, - ) -> Poll { + ) -> Result, MultipartError> { // TODO: need to read epilogue - match payload.readline()? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(None) => Err(MultipartError::Incomplete), - Async::Ready(Some(chunk)) => { + match payload.readline() { + None => { + if payload.eof { + Err(MultipartError::Incomplete) + } else { + Ok(None) + } + } + Some(chunk) => { if chunk.len() == boundary.len() + 4 && &chunk[..2] == b"--" && &chunk[2..boundary.len() + 2] == boundary.as_bytes() { - Ok(Async::Ready(false)) + Ok(Some(false)) } else if chunk.len() == boundary.len() + 6 && &chunk[..2] == b"--" && &chunk[2..boundary.len() + 2] == boundary.as_bytes() && &chunk[boundary.len() + 2..boundary.len() + 4] == b"--" { - Ok(Async::Ready(true)) + Ok(Some(true)) } else { Err(MultipartError::Boundary) } @@ -238,11 +204,11 @@ impl InnerMultipart { fn skip_until_boundary( payload: &mut PayloadBuffer, boundary: &str, - ) -> Poll { + ) -> Result, MultipartError> { let mut eof = false; loop { - match payload.readline()? { - Async::Ready(Some(chunk)) => { + match payload.readline() { + Some(chunk) => { if chunk.is_empty() { //ValueError("Could not find starting boundary %r" //% (self._boundary)) @@ -267,14 +233,19 @@ impl InnerMultipart { } } } - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(None) => return Err(MultipartError::Incomplete), + None => { + return if payload.eof { + Err(MultipartError::Incomplete) + } else { + Ok(None) + }; + } } } - Ok(Async::Ready(eof)) + Ok(Some(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 { @@ -317,7 +288,7 @@ impl InnerMultipart { payload, &self.boundary, )? { - Async::Ready(eof) => { + Some(eof) => { if eof { self.state = InnerState::Eof; return Ok(Async::Ready(None)); @@ -325,14 +296,14 @@ impl InnerMultipart { self.state = InnerState::Headers; } } - Async::NotReady => return Ok(Async::NotReady), + None => return Ok(Async::NotReady), } } // read boundary InnerState::Boundary => { match InnerMultipart::read_boundary(payload, &self.boundary)? { - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(eof) => { + None => return Ok(Async::NotReady), + Some(eof) => { if eof { self.state = InnerState::Eof; return Ok(Async::Ready(None)); @@ -347,8 +318,7 @@ impl InnerMultipart { // read field headers for next field if self.state == InnerState::Headers { - if let Async::Ready(headers) = InnerMultipart::read_headers(payload)? - { + if let Some(headers) = InnerMultipart::read_headers(payload)? { self.state = InnerState::Boundary; headers } else { @@ -389,7 +359,7 @@ impl InnerMultipart { self.item = InnerMultipartItem::Multipart(Rc::clone(&inner)); - Ok(Async::Ready(Some(MultipartItem::Nested(Multipart { + Ok(Async::Ready(Some(Item::Nested(Multipart { safety: safety.clone(), error: None, inner: Some(inner), @@ -402,9 +372,12 @@ impl InnerMultipart { )?)); self.item = InnerMultipartItem::Field(Rc::clone(&field)); - Ok(Async::Ready(Some(MultipartItem::Field( - MultipartField::new(safety.clone(), headers, mt, field), - )))) + Ok(Async::Ready(Some(Item::Field(Field::new( + safety.clone(), + headers, + mt, + field, + ))))) } } } @@ -418,21 +391,21 @@ impl Drop for InnerMultipart { } /// A single field in a multipart stream -pub struct MultipartField { +pub struct Field { ct: mime::Mime, headers: HeaderMap, inner: Rc>, safety: Safety, } -impl MultipartField { +impl Field { fn new( safety: Safety, headers: HeaderMap, ct: mime::Mime, inner: Rc>, ) -> Self { - MultipartField { + Field { ct, headers, inner, @@ -463,22 +436,28 @@ impl MultipartField { } } -impl Stream for MultipartField { +impl Stream for Field { type Item = Bytes; type Error = MultipartError; fn poll(&mut self) -> Poll, Self::Error> { if self.safety.current() { - self.inner.borrow_mut().poll(&self.safety) + let mut inner = self.inner.borrow_mut(); + if let Some(payload) = inner.payload.as_ref().unwrap().get_mut(&self.safety) + { + payload.poll_stream()?; + } + + inner.poll(&self.safety) } else { Ok(Async::NotReady) } } } -impl fmt::Debug for MultipartField { +impl fmt::Debug for Field { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "\nMultipartField: {}", self.ct)?; + writeln!(f, "\nField: {}", self.ct)?; writeln!(f, " boundary: {}", self.inner.borrow().boundary)?; writeln!(f, " headers:")?; for (key, val) in self.headers.iter() { @@ -532,10 +511,8 @@ impl InnerField { if *size == 0 { Ok(Async::Ready(None)) } else { - match payload.readany() { - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(None)) => Err(MultipartError::Incomplete), - Ok(Async::Ready(Some(mut chunk))) => { + match payload.read_max(*size) { + Some(mut chunk) => { let len = cmp::min(chunk.len() as u64, *size); *size -= len; let ch = chunk.split_to(len as usize); @@ -544,7 +521,13 @@ impl InnerField { } Ok(Async::Ready(Some(ch))) } - Err(err) => Err(err.into()), + None => { + if payload.eof && (*size != 0) { + Err(MultipartError::Incomplete) + } else { + Ok(Async::NotReady) + } + } } } } @@ -555,16 +538,26 @@ impl InnerField { payload: &mut PayloadBuffer, boundary: &str, ) -> Poll, MultipartError> { - match payload.read_until(b"\r")? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(None) => Err(MultipartError::Incomplete), - Async::Ready(Some(mut chunk)) => { + match payload.read_until(b"\r") { + None => { + if payload.eof { + Err(MultipartError::Incomplete) + } else { + Ok(Async::NotReady) + } + } + Some(mut chunk) => { if chunk.len() == 1 { payload.unprocessed(chunk); - match payload.read_exact(boundary.len() + 4)? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(None) => Err(MultipartError::Incomplete), - Async::Ready(Some(mut chunk)) => { + match payload.read_exact(boundary.len() + 4) { + None => { + if payload.eof { + Err(MultipartError::Incomplete) + } else { + Ok(Async::NotReady) + } + } + Some(mut chunk) => { if &chunk[..2] == b"\r\n" && &chunk[2..4] == b"--" && &chunk[4..] == boundary.as_bytes() @@ -606,10 +599,9 @@ impl InnerField { Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)), Async::Ready(None) => { self.eof = true; - match payload.readline()? { - Async::NotReady => Async::NotReady, - Async::Ready(None) => Async::Ready(None), - Async::Ready(Some(line)) => { + match payload.readline() { + None => Async::Ready(None), + Some(line) => { if line.as_ref() != b"\r\n" { log::warn!("multipart field did not read all the data or it is malformed"); } @@ -711,14 +703,86 @@ impl Drop for Safety { } } +/// Payload buffer +struct PayloadBuffer { + eof: bool, + buf: BytesMut, + stream: Box>, +} + +impl PayloadBuffer { + /// Create new `PayloadBuffer` instance + fn new(stream: S) -> Self + where + S: Stream + 'static, + { + PayloadBuffer { + eof: false, + buf: BytesMut::new(), + stream: Box::new(stream), + } + } + + fn poll_stream(&mut self) -> Result<(), PayloadError> { + loop { + match self.stream.poll()? { + Async::Ready(Some(data)) => self.buf.extend_from_slice(&data), + Async::Ready(None) => { + self.eof = true; + return Ok(()); + } + Async::NotReady => return Ok(()), + } + } + } + + /// Read exact number of bytes + #[inline] + fn read_exact(&mut self, size: usize) -> Option { + if size <= self.buf.len() { + Some(self.buf.split_to(size).freeze()) + } else { + None + } + } + + fn read_max(&mut self, size: u64) -> Option { + if !self.buf.is_empty() { + let size = std::cmp::min(self.buf.len() as u64, size) as usize; + Some(self.buf.split_to(size).freeze()) + } else { + None + } + } + + /// Read until specified ending + pub fn read_until(&mut self, line: &[u8]) -> Option { + twoway::find_bytes(&self.buf, line) + .map(|idx| self.buf.split_to(idx + line.len()).freeze()) + } + + /// Read bytes until new line delimiter + pub fn readline(&mut self) -> Option { + self.read_until(b"\n") + } + + /// Put unprocessed data back to the buffer + pub fn unprocessed(&mut self, data: Bytes) { + let buf = BytesMut::from(data); + let buf = std::mem::replace(&mut self.buf, buf); + self.buf.extend_from_slice(&buf); + } +} + #[cfg(test)] mod tests { + use actix_http::h1::{Payload, PayloadWriter}; use bytes::Bytes; use futures::unsync::mpsc; use super::*; - use crate::http::header::{DispositionParam, DispositionType}; - use crate::test::run_on; + use actix_web::http::header::{DispositionParam, DispositionType}; + use actix_web::test::run_on; #[test] fn test_boundary() { @@ -799,9 +863,9 @@ mod tests { ); let mut multipart = Multipart::new(&headers, payload); - match multipart.poll() { - Ok(Async::Ready(Some(item))) => match item { - MultipartItem::Field(mut field) => { + match multipart.poll().unwrap() { + Async::Ready(Some(item)) => match item { + Item::Field(mut field) => { { let cd = field.content_disposition().unwrap(); assert_eq!(cd.disposition, DispositionType::FormData); @@ -813,12 +877,12 @@ mod tests { assert_eq!(field.content_type().type_(), mime::TEXT); assert_eq!(field.content_type().subtype(), mime::PLAIN); - match field.poll() { - Ok(Async::Ready(Some(chunk))) => assert_eq!(chunk, "test"), + match field.poll().unwrap() { + Async::Ready(Some(chunk)) => assert_eq!(chunk, "test"), _ => unreachable!(), } - match field.poll() { - Ok(Async::Ready(None)) => (), + match field.poll().unwrap() { + Async::Ready(None) => (), _ => unreachable!(), } } @@ -827,9 +891,9 @@ mod tests { _ => unreachable!(), } - match multipart.poll() { - Ok(Async::Ready(Some(item))) => match item { - MultipartItem::Field(mut field) => { + match multipart.poll().unwrap() { + Async::Ready(Some(item)) => match item { + Item::Field(mut field) => { assert_eq!(field.content_type().type_(), mime::TEXT); assert_eq!(field.content_type().subtype(), mime::PLAIN); @@ -847,10 +911,110 @@ mod tests { _ => unreachable!(), } - match multipart.poll() { - Ok(Async::Ready(None)) => (), + match multipart.poll().unwrap() { + Async::Ready(None) => (), _ => unreachable!(), } }); } + + #[test] + fn test_basic() { + run_on(|| { + let (_, payload) = Payload::create(false); + let mut payload = PayloadBuffer::new(payload); + + assert_eq!(payload.buf.len(), 0); + payload.poll_stream().unwrap(); + assert_eq!(None, payload.read_max(1)); + }) + } + + #[test] + fn test_eof() { + run_on(|| { + let (mut sender, payload) = Payload::create(false); + let mut payload = PayloadBuffer::new(payload); + + assert_eq!(None, payload.read_max(4)); + sender.feed_data(Bytes::from("data")); + sender.feed_eof(); + payload.poll_stream().unwrap(); + + assert_eq!(Some(Bytes::from("data")), payload.read_max(4)); + assert_eq!(payload.buf.len(), 0); + assert_eq!(None, payload.read_max(1)); + assert!(payload.eof); + }) + } + + #[test] + fn test_err() { + run_on(|| { + let (mut sender, payload) = Payload::create(false); + let mut payload = PayloadBuffer::new(payload); + assert_eq!(None, payload.read_max(1)); + sender.set_error(PayloadError::Incomplete(None)); + payload.poll_stream().err().unwrap(); + }) + } + + #[test] + fn test_readmax() { + run_on(|| { + let (mut sender, payload) = Payload::create(false); + let mut payload = PayloadBuffer::new(payload); + + sender.feed_data(Bytes::from("line1")); + sender.feed_data(Bytes::from("line2")); + payload.poll_stream().unwrap(); + assert_eq!(payload.buf.len(), 10); + + assert_eq!(Some(Bytes::from("line1")), payload.read_max(5)); + assert_eq!(payload.buf.len(), 5); + + assert_eq!(Some(Bytes::from("line2")), payload.read_max(5)); + assert_eq!(payload.buf.len(), 0); + }) + } + + #[test] + fn test_readexactly() { + run_on(|| { + let (mut sender, payload) = Payload::create(false); + let mut payload = PayloadBuffer::new(payload); + + assert_eq!(None, payload.read_exact(2)); + + sender.feed_data(Bytes::from("line1")); + sender.feed_data(Bytes::from("line2")); + payload.poll_stream().unwrap(); + + assert_eq!(Some(Bytes::from_static(b"li")), payload.read_exact(2)); + assert_eq!(payload.buf.len(), 8); + + assert_eq!(Some(Bytes::from_static(b"ne1l")), payload.read_exact(4)); + assert_eq!(payload.buf.len(), 4); + }) + } + + #[test] + fn test_readuntil() { + run_on(|| { + let (mut sender, payload) = Payload::create(false); + let mut payload = PayloadBuffer::new(payload); + + assert_eq!(None, payload.read_until(b"ne")); + + sender.feed_data(Bytes::from("line1")); + sender.feed_data(Bytes::from("line2")); + payload.poll_stream().unwrap(); + + assert_eq!(Some(Bytes::from("line")), payload.read_until(b"ne")); + assert_eq!(payload.buf.len(), 6); + + assert_eq!(Some(Bytes::from("1line2")), payload.read_until(b"2")); + assert_eq!(payload.buf.len(), 0); + }) + } } diff --git a/actix-session/CHANGES.md b/actix-session/CHANGES.md index 3cd156092..85e1123a9 100644 --- a/actix-session/CHANGES.md +++ b/actix-session/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.0-alpha.3] - 2019-04-02 + +* Update actix-web + ## [0.1.0-alpha.2] - 2019-03-29 * Update actix-web diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index e39dc7140..956906fad 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-session" -version = "0.1.0-alpha.2" +version = "0.1.0-alpha.3" authors = ["Nikolay Kim "] description = "Session for actix web framework." readme = "README.md" @@ -24,7 +24,7 @@ default = ["cookie-session"] cookie-session = ["actix-web/secure-cookies"] [dependencies] -actix-web = "1.0.0-alpha.2" +actix-web = "1.0.0-alpha.3" actix-service = "0.3.4" bytes = "0.4" derive_more = "0.14" diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index 9e4fe78b2..f7b4ec03a 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -333,6 +333,7 @@ mod tests { let request = test::TestRequest::get().to_request(); let response = test::block_on(app.call(request)).unwrap(); assert!(response + .response() .cookies() .find(|c| c.name() == "actix-session") .is_some()); @@ -352,6 +353,7 @@ mod tests { let request = test::TestRequest::get().to_request(); let response = test::block_on(app.call(request)).unwrap(); assert!(response + .response() .cookies() .find(|c| c.name() == "actix-session") .is_some()); diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index 819773c6a..0cd1b9ed8 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -190,7 +190,7 @@ mod tests { #[test] fn session() { - let mut req = test::TestRequest::default().to_service(); + let mut req = test::TestRequest::default().to_srv_request(); Session::set_session( vec![("key".to_string(), "\"value\"".to_string())].into_iter(), diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 9b1427980..34592aafc 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.0-alpha.3] - 2019-04-02 + +* Update actix-http and actix-web + ## [0.1.0-alpha.2] - 2019-03-29 * Update actix-http and actix-web diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 759d6fc31..598d39459 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "1.0.0-alpha.2" +version = "1.0.0-alpha.3" authors = ["Nikolay Kim "] description = "Actix actors support for actix web framework." readme = "README.md" @@ -19,12 +19,12 @@ path = "src/lib.rs" [dependencies] actix = "0.8.0-alpha.2" -actix-web = "1.0.0-alpha.2" -actix-http = "0.1.0-alpha.2" +actix-web = "1.0.0-alpha.3" +actix-http = "0.1.0-alpha.3" actix-codec = "0.1.2" bytes = "0.4" futures = "0.1.25" [dev-dependencies] env_logger = "0.6" -actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] } +actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 436011888..642222560 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -20,7 +20,7 @@ pub use actix_http::ws::{ use actix_web::dev::HttpResponseBuilder; use actix_web::error::{Error, ErrorInternalServerError, PayloadError}; use actix_web::http::{header, Method, StatusCode}; -use actix_web::{HttpMessage, HttpRequest, HttpResponse}; +use actix_web::{HttpRequest, HttpResponse}; use bytes::{Bytes, BytesMut}; use futures::sync::oneshot::Sender; use futures::{Async, Future, Poll, Stream}; @@ -64,7 +64,7 @@ pub fn handshake(req: &HttpRequest) -> Result` for `ClientRequest`. - * Export `MessageBody` type * `ClientResponse::json()` - Loads and parse `application/json` encoded body @@ -14,12 +11,12 @@ ### Changed -* Use non-consuming builder pattern for `ClientRequest`. - * `ClientRequest::json()` accepts reference instead of object. * `ClientResponse::body()` does not consume response object. +* Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()` + ## [0.1.0-alpha.2] - 2019-03-29 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index fdaf0a55b..81d91e19f 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -38,7 +38,7 @@ flate2-rust = ["actix-http/flate2-rust"] [dependencies] actix-codec = "0.1.1" actix-service = "0.3.4" -actix-http = "0.1.0-alpa.2" +actix-http = "0.1.0-alpa.3" base64 = "0.10.1" bytes = "0.4" derive_more = "0.14" @@ -55,9 +55,9 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.2" -actix-web = { version = "1.0.0-alpha.2", features=["ssl"] } -actix-http = { version = "0.1.0-alpa.2", features=["ssl"] } -actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] } +actix-web = { version = "1.0.0-alpha.3", features=["ssl"] } +actix-http = { version = "0.1.0-alpa.3", features=["ssl"] } +actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } actix-utils = "0.3.4" actix-server = { version = "0.4.1", features=["ssl"] } brotli2 = { version="0.3.2" } diff --git a/awc/src/error.rs b/awc/src/error.rs index bbfd9b971..20654bdf4 100644 --- a/awc/src/error.rs +++ b/awc/src/error.rs @@ -54,9 +54,6 @@ impl From for WsClientError { /// A set of errors that can occur during parsing json payloads #[derive(Debug, Display, From)] pub enum JsonPayloadError { - /// Payload size is bigger than allowed. (default: 32kB) - #[display(fmt = "Json payload size is bigger than allowed.")] - Overflow, /// Content type error #[display(fmt = "Content type error")] ContentType, diff --git a/awc/src/lib.rs b/awc/src/lib.rs index db994431b..5f9adb463 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -105,7 +105,7 @@ impl Client { let mut req = ClientRequest::new(method, url, self.0.clone()); for (key, value) in &self.0.headers { - req.set_header_if_none(key.clone(), value.clone()); + req = req.set_header_if_none(key.clone(), value.clone()); } req } @@ -120,7 +120,7 @@ impl Client { { let mut req = self.request(head.method.clone(), url); for (key, value) in &head.headers { - req.set_header_if_none(key.clone(), value.clone()); + req = req.set_header_if_none(key.clone(), value.clone()); } req } diff --git a/awc/src/request.rs b/awc/src/request.rs index 0b89581a2..b96b39e2f 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -17,8 +17,8 @@ use actix_http::cookie::{Cookie, CookieJar}; use actix_http::encoding::Decoder; use actix_http::http::header::{self, ContentEncoding, Header, IntoHeaderValue}; use actix_http::http::{ - uri, ConnectionType, Error as HttpError, HeaderName, HeaderValue, HttpTryFrom, - Method, Uri, Version, + uri, ConnectionType, Error as HttpError, HeaderMap, HeaderName, HeaderValue, + HttpTryFrom, Method, Uri, Version, }; use actix_http::{Error, Payload, RequestHead}; @@ -58,7 +58,7 @@ const HTTPS_ENCODING: &str = "gzip, deflate"; /// } /// ``` pub struct ClientRequest { - pub(crate) head: Option, + pub(crate) head: RequestHead, err: Option, cookies: Option, default_headers: bool, @@ -73,40 +73,36 @@ impl ClientRequest { where Uri: HttpTryFrom, { - let mut req = ClientRequest { + ClientRequest { config, - head: Some(RequestHead::default()), + head: RequestHead::default(), err: None, cookies: None, timeout: None, default_headers: true, response_decompress: true, - }; - req.method(method).uri(uri); - req + } + .method(method) + .uri(uri) } /// Set HTTP URI of request. #[inline] - pub fn uri(&mut self, uri: U) -> &mut Self + pub fn uri(mut self, uri: U) -> Self where Uri: HttpTryFrom, { - if let Some(head) = parts(&mut self.head, &self.err) { - match Uri::try_from(uri) { - Ok(uri) => head.uri = uri, - Err(e) => self.err = Some(e.into()), - } + match Uri::try_from(uri) { + Ok(uri) => self.head.uri = uri, + Err(e) => self.err = Some(e.into()), } self } /// Set HTTP method of this request. #[inline] - pub fn method(&mut self, method: Method) -> &mut Self { - if let Some(head) = parts(&mut self.head, &self.err) { - head.method = method; - } + pub fn method(mut self, method: Method) -> Self { + self.head.method = method; self } @@ -115,13 +111,23 @@ impl ClientRequest { /// /// By default requests's HTTP version depends on network stream #[inline] - pub fn version(&mut self, version: Version) -> &mut Self { - if let Some(head) = parts(&mut self.head, &self.err) { - head.version = version; - } + pub fn version(mut self, version: Version) -> Self { + self.head.version = version; self } + #[inline] + /// Returns request's headers. + pub fn headers(&self) -> &HeaderMap { + &self.head.headers + } + + #[inline] + /// Returns request's mutable headers. + pub fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.head.headers + } + /// Set a header. /// /// ```rust @@ -135,14 +141,12 @@ impl ClientRequest { /// # })); /// } /// ``` - pub fn set(&mut self, hdr: H) -> &mut Self { - if let Some(head) = parts(&mut self.head, &self.err) { - match hdr.try_into() { - Ok(value) => { - head.headers.insert(H::name(), value); - } - Err(e) => self.err = Some(e.into()), + pub fn set(mut self, hdr: H) -> Self { + match hdr.try_into() { + Ok(value) => { + self.head.headers.insert(H::name(), value); } + Err(e) => self.err = Some(e.into()), } self } @@ -165,106 +169,96 @@ impl ClientRequest { /// # })); /// } /// ``` - pub fn header(&mut self, key: K, value: V) -> &mut Self + pub fn header(mut self, key: K, value: V) -> Self where HeaderName: HttpTryFrom, V: IntoHeaderValue, { - if let Some(head) = parts(&mut self.head, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - head.headers.append(key, value); - } - Err(e) => self.err = Some(e.into()), - }, + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + let _ = self.head.headers.append(key, value); + } Err(e) => self.err = Some(e.into()), - } + }, + Err(e) => self.err = Some(e.into()), } self } /// Insert a header, replaces existing header. - pub fn set_header(&mut self, key: K, value: V) -> &mut Self + pub fn set_header(mut self, key: K, value: V) -> Self where HeaderName: HttpTryFrom, V: IntoHeaderValue, { - if let Some(head) = parts(&mut self.head, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - head.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - }, + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + let _ = self.head.headers.insert(key, value); + } Err(e) => self.err = Some(e.into()), - } + }, + Err(e) => self.err = Some(e.into()), } self } /// Insert a header only if it is not yet set. - pub fn set_header_if_none(&mut self, key: K, value: V) -> &mut Self + pub fn set_header_if_none(mut self, key: K, value: V) -> Self where HeaderName: HttpTryFrom, V: IntoHeaderValue, { - if let Some(head) = parts(&mut self.head, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => { - if !head.headers.contains_key(&key) { - match value.try_into() { - Ok(value) => { - head.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), + match HeaderName::try_from(key) { + Ok(key) => { + if !self.head.headers.contains_key(&key) { + match value.try_into() { + Ok(value) => { + let _ = self.head.headers.insert(key, value); } + Err(e) => self.err = Some(e.into()), } } - Err(e) => self.err = Some(e.into()), } + Err(e) => self.err = Some(e.into()), } self } - /// Close connection instead of returning it back to connections pool. + /// Force close connection instead of returning it back to connections pool. /// This setting affect only http/1 connections. #[inline] - pub fn close_connection(&mut self) -> &mut Self { - if let Some(head) = parts(&mut self.head, &self.err) { - head.set_connection_type(ConnectionType::Close); - } + pub fn force_close(mut self) -> Self { + self.head.set_connection_type(ConnectionType::Close); self } /// Set request's content type #[inline] - pub fn content_type(&mut self, value: V) -> &mut Self + pub fn content_type(mut self, value: V) -> Self where HeaderValue: HttpTryFrom, { - if let Some(head) = parts(&mut self.head, &self.err) { - match HeaderValue::try_from(value) { - Ok(value) => { - let _ = head.headers.insert(header::CONTENT_TYPE, value); - } - Err(e) => self.err = Some(e.into()), + match HeaderValue::try_from(value) { + Ok(value) => { + let _ = self.head.headers.insert(header::CONTENT_TYPE, value); } + Err(e) => self.err = Some(e.into()), } self } /// Set content length #[inline] - pub fn content_length(&mut self, len: u64) -> &mut Self { + pub fn content_length(self, len: u64) -> Self { let mut wrt = BytesMut::new().writer(); let _ = write!(wrt, "{}", len); self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze()) } /// Set HTTP basic authorization header - pub fn basic_auth(&mut self, username: U, password: Option<&str>) -> &mut Self + pub fn basic_auth(self, username: U, password: Option<&str>) -> Self where U: fmt::Display, { @@ -279,7 +273,7 @@ impl ClientRequest { } /// Set HTTP bearer authentication header - pub fn bearer_auth(&mut self, token: T) -> &mut Self + pub fn bearer_auth(self, token: T) -> Self where T: fmt::Display, { @@ -311,7 +305,7 @@ impl ClientRequest { /// })); /// } /// ``` - pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { + pub fn cookie<'c>(mut self, cookie: Cookie<'c>) -> Self { if self.cookies.is_none() { let mut jar = CookieJar::new(); jar.add(cookie.into_owned()); @@ -324,13 +318,13 @@ impl ClientRequest { /// Do not add default request headers. /// By default `Date` and `User-Agent` headers are set. - pub fn no_default_headers(&mut self) -> &mut Self { + pub fn no_default_headers(mut self) -> Self { self.default_headers = false; self } /// Disable automatic decompress of response's body - pub fn no_decompress(&mut self) -> &mut Self { + pub fn no_decompress(mut self) -> Self { self.response_decompress = false; self } @@ -339,38 +333,38 @@ impl ClientRequest { /// /// Request timeout is the total time before a response must be received. /// Default value is 5 seconds. - pub fn timeout(&mut self, timeout: Duration) -> &mut Self { + pub fn timeout(mut self, timeout: Duration) -> Self { self.timeout = Some(timeout); 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 + pub fn if_true(mut self, value: bool, f: F) -> Self where F: FnOnce(&mut ClientRequest), { if value { - f(self); + f(&mut self); } self } /// This method calls provided closure with builder reference if /// value is `Some`. - pub fn if_some(&mut self, value: Option, f: F) -> &mut Self + pub fn if_some(mut self, value: Option, f: F) -> Self where F: FnOnce(T, &mut ClientRequest), { if let Some(val) = value { - f(val, self); + f(val, &mut self); } self } /// Complete request construction and send body. pub fn send_body( - &mut self, + mut self, body: B, ) -> impl Future< Item = ClientResponse>, @@ -383,10 +377,8 @@ impl ClientRequest { return Either::A(err(e.into())); } - let mut head = self.head.take().expect("cannot reuse response builder"); - // validate uri - let uri = &head.uri; + let uri = &self.head.uri; if uri.host().is_none() { return Either::A(err(InvalidUrl::MissingHost.into())); } else if uri.scheme_part().is_none() { @@ -403,18 +395,18 @@ impl ClientRequest { // set default headers if self.default_headers { // set request host header - if let Some(host) = head.uri.host() { - if !head.headers.contains_key(header::HOST) { + if let Some(host) = self.head.uri.host() { + if !self.head.headers.contains_key(header::HOST) { let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); - let _ = match head.uri.port_u16() { + let _ = match self.head.uri.port_u16() { None | Some(80) | Some(443) => write!(wrt, "{}", host), Some(port) => write!(wrt, "{}:{}", host, port), }; match wrt.get_mut().take().freeze().try_into() { Ok(value) => { - head.headers.insert(header::HOST, value); + self.head.headers.insert(header::HOST, value); } Err(e) => return Either::A(err(HttpError::from(e).into())), } @@ -422,32 +414,11 @@ impl ClientRequest { } // user agent - self.set_header_if_none( - header::USER_AGENT, - concat!("awc/", env!("CARGO_PKG_VERSION")), - ); - } - - // enable br only for https - let https = head - .uri - .scheme_part() - .map(|s| s == &uri::Scheme::HTTPS) - .unwrap_or(true); - - #[cfg(any( - feature = "brotli", - feature = "flate2-zlib", - feature = "flate2-rust" - ))] - { - if https { - self.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING); - } else { - #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] - { - self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate"); - } + if !self.head.headers.contains_key(&header::USER_AGENT) { + self.head.headers.insert( + header::USER_AGENT, + HeaderValue::from_static(concat!("awc/", env!("CARGO_PKG_VERSION"))), + ); } } @@ -459,14 +430,43 @@ impl ClientRequest { let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); let _ = write!(&mut cookie, "; {}={}", name, value); } - head.headers.insert( + self.head.headers.insert( header::COOKIE, HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), ); } - let config = self.config.as_ref(); - let response_decompress = self.response_decompress; + let slf = self; + + // enable br only for https + #[cfg(any( + feature = "brotli", + feature = "flate2-zlib", + feature = "flate2-rust" + ))] + let slf = { + let https = slf + .head + .uri + .scheme_part() + .map(|s| s == &uri::Scheme::HTTPS) + .unwrap_or(true); + + if https { + slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING) + } else { + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] + { + slf.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate") + } + #[cfg(not(any(feature = "flate2-zlib", feature = "flate2-rust")))] + slf + } + }; + + let head = slf.head; + let config = slf.config.as_ref(); + let response_decompress = slf.response_decompress; let fut = config .connector @@ -483,7 +483,7 @@ impl ClientRequest { }); // set request timeout - if let Some(timeout) = self.timeout.or_else(|| config.timeout.clone()) { + if let Some(timeout) = slf.timeout.or_else(|| config.timeout.clone()) { Either::B(Either::A(Timeout::new(fut, timeout).map_err(|e| { if let Some(e) = e.into_inner() { e @@ -498,7 +498,7 @@ impl ClientRequest { /// Set a JSON body and generate `ClientRequest` pub fn send_json( - &mut self, + self, value: &T, ) -> impl Future< Item = ClientResponse>, @@ -510,16 +510,16 @@ impl ClientRequest { }; // set content-type - self.set_header_if_none(header::CONTENT_TYPE, "application/json"); + let slf = self.set_header_if_none(header::CONTENT_TYPE, "application/json"); - Either::B(self.send_body(Body::Bytes(Bytes::from(body)))) + Either::B(slf.send_body(Body::Bytes(Bytes::from(body)))) } /// Set a urlencoded body and generate `ClientRequest` /// /// `ClientRequestBuilder` can not be used after this call. pub fn send_form( - &mut self, + self, value: &T, ) -> impl Future< Item = ClientResponse>, @@ -531,17 +531,17 @@ impl ClientRequest { }; // set content-type - self.set_header_if_none( + let slf = self.set_header_if_none( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ); - Either::B(self.send_body(Body::Bytes(Bytes::from(body)))) + Either::B(slf.send_body(Body::Bytes(Bytes::from(body)))) } /// Set an streaming body and generate `ClientRequest`. pub fn send_stream( - &mut self, + self, stream: S, ) -> impl Future< Item = ClientResponse>, @@ -556,7 +556,7 @@ impl ClientRequest { /// Set an empty body and generate `ClientRequest`. pub fn send( - &mut self, + self, ) -> impl Future< Item = ClientResponse>, Error = SendRequestError, @@ -565,48 +565,21 @@ impl ClientRequest { } } -impl std::ops::Deref for ClientRequest { - type Target = RequestHead; - - fn deref(&self) -> &RequestHead { - self.head.as_ref().expect("cannot reuse response builder") - } -} - -impl std::ops::DerefMut for ClientRequest { - fn deref_mut(&mut self) -> &mut RequestHead { - self.head.as_mut().expect("cannot reuse response builder") - } -} - impl fmt::Debug for ClientRequest { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let head = self.head.as_ref().expect("cannot reuse response builder"); - writeln!( f, "\nClientRequest {:?} {}:{}", - head.version, head.method, head.uri + self.head.version, self.head.method, self.head.uri )?; writeln!(f, " headers:")?; - for (key, val) in head.headers.iter() { + for (key, val) in self.head.headers.iter() { writeln!(f, " {:?}: {:?}", key, val)?; } Ok(()) } } -#[inline] -fn parts<'a>( - parts: &'a mut Option, - err: &Option, -) -> Option<&'a mut RequestHead> { - if err.is_some() { - return None; - } - parts.as_mut() -} - #[cfg(test)] mod tests { use super::*; @@ -615,8 +588,7 @@ mod tests { #[test] fn test_debug() { test::run_on(|| { - let mut request = Client::new().get("/"); - request.header("x-test", "111"); + let request = Client::new().get("/").header("x-test", "111"); let repr = format!("{:?}", request); assert!(repr.contains("ClientRequest")); assert!(repr.contains("x-test")); @@ -633,8 +605,6 @@ mod tests { assert_eq!( req.head - .as_ref() - .unwrap() .headers .get(header::CONTENT_TYPE) .unwrap() @@ -648,16 +618,14 @@ mod tests { #[test] fn test_client_header_override() { test::run_on(|| { - let mut req = Client::build() + let req = Client::build() .header(header::CONTENT_TYPE, "111") .finish() - .get("/"); - req.set_header(header::CONTENT_TYPE, "222"); + .get("/") + .set_header(header::CONTENT_TYPE, "222"); assert_eq!( req.head - .as_ref() - .unwrap() .headers .get(header::CONTENT_TYPE) .unwrap() @@ -671,12 +639,11 @@ mod tests { #[test] fn client_basic_auth() { test::run_on(|| { - let mut req = Client::new().get("/"); - req.basic_auth("username", Some("password")); + let req = Client::new() + .get("/") + .basic_auth("username", Some("password")); assert_eq!( req.head - .as_ref() - .unwrap() .headers .get(header::AUTHORIZATION) .unwrap() @@ -685,12 +652,9 @@ mod tests { "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" ); - let mut req = Client::new().get("/"); - req.basic_auth("username", None); + let req = Client::new().get("/").basic_auth("username", None); assert_eq!( req.head - .as_ref() - .unwrap() .headers .get(header::AUTHORIZATION) .unwrap() @@ -704,12 +668,9 @@ mod tests { #[test] fn client_bearer_auth() { test::run_on(|| { - let mut req = Client::new().get("/"); - req.bearer_auth("someS3cr3tAutht0k3n"); + let req = Client::new().get("/").bearer_auth("someS3cr3tAutht0k3n"); assert_eq!( req.head - .as_ref() - .unwrap() .headers .get(header::AUTHORIZATION) .unwrap() diff --git a/awc/src/response.rs b/awc/src/response.rs index b91735208..73194d673 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -1,8 +1,9 @@ use std::cell::{Ref, RefMut}; use std::fmt; +use std::marker::PhantomData; use bytes::{Bytes, BytesMut}; -use futures::{Future, Poll, Stream}; +use futures::{Async, Future, Poll, Stream}; use actix_http::cookie::Cookie; use actix_http::error::{CookieParseError, PayloadError}; @@ -82,7 +83,7 @@ impl ClientResponse { } #[inline] - /// Returns Request's headers. + /// Returns request's headers. pub fn headers(&self) -> &HeaderMap { &self.head().headers } @@ -103,7 +104,7 @@ impl ClientResponse { impl ClientResponse where - S: Stream + 'static, + S: Stream, { /// Loads http response's body. pub fn body(&mut self) -> MessageBody { @@ -147,16 +148,14 @@ impl fmt::Debug for ClientResponse { /// Future that resolves to a complete http message body. pub struct MessageBody { - limit: usize, length: Option, - stream: Option>, err: Option, - fut: Option>>, + fut: Option>, } impl MessageBody where - S: Stream + 'static, + S: Stream, { /// Create `MessageBody` for request. pub fn new(res: &mut ClientResponse) -> MessageBody { @@ -174,24 +173,22 @@ where } MessageBody { - limit: 262_144, length: len, - stream: Some(res.take_payload()), - fut: None, err: None, + fut: Some(ReadBody::new(res.take_payload(), 262_144)), } } /// Change max size of payload. By default max size is 256Kb pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; + if let Some(ref mut fut) = self.fut { + fut.limit = limit; + } self } fn err(e: PayloadError) -> Self { MessageBody { - stream: None, - limit: 262_144, fut: None, err: Some(e), length: None, @@ -201,44 +198,23 @@ where impl Future for MessageBody where - S: Stream + 'static, + S: Stream, { type Item = Bytes; type Error = PayloadError; fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut { - return fut.poll(); - } - if let Some(err) = self.err.take() { return Err(err); } if let Some(len) = self.length.take() { - if len > self.limit { + if len > self.fut.as_ref().unwrap().limit { return Err(PayloadError::Overflow); } } - // future - let limit = self.limit; - self.fut = Some(Box::new( - self.stream - .take() - .expect("Can not be used second time") - .from_err() - .fold(BytesMut::with_capacity(8192), 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.poll() + self.fut.as_mut().unwrap().poll() } } @@ -249,16 +225,15 @@ where /// * content type is not `application/json` /// * content length is greater than 64k pub struct JsonBody { - limit: usize, length: Option, - stream: Payload, err: Option, - fut: Option>>, + fut: Option>, + _t: PhantomData, } impl JsonBody where - S: Stream + 'static, + S: Stream, U: DeserializeOwned, { /// Create `JsonBody` for request. @@ -271,11 +246,10 @@ where }; if !json { return JsonBody { - limit: 65536, length: None, - stream: Payload::None, fut: None, err: Some(JsonPayloadError::ContentType), + _t: PhantomData, }; } @@ -289,58 +263,84 @@ where } JsonBody { - limit: 65536, length: len, - stream: req.take_payload(), - fut: None, err: None, + fut: Some(ReadBody::new(req.take_payload(), 65536)), + _t: PhantomData, } } /// Change max size of payload. By default max size is 64Kb pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; + if let Some(ref mut fut) = self.fut { + fut.limit = limit; + } self } } impl Future for JsonBody where - T: Stream + 'static, + T: Stream, U: DeserializeOwned + 'static, { type Item = U; type Error = JsonPayloadError; fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut { - return fut.poll(); - } - if let Some(err) = self.err.take() { return Err(err); } - let limit = self.limit; if let Some(len) = self.length.take() { - if len > limit { - return Err(JsonPayloadError::Overflow); + if len > self.fut.as_ref().unwrap().limit { + return Err(JsonPayloadError::Payload(PayloadError::Overflow)); } } - let fut = std::mem::replace(&mut self.stream, Payload::None) - .from_err() - .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(JsonPayloadError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) + let body = futures::try_ready!(self.fut.as_mut().unwrap().poll()); + Ok(Async::Ready(serde_json::from_slice::(&body)?)) + } +} + +struct ReadBody { + stream: Payload, + buf: BytesMut, + limit: usize, +} + +impl ReadBody { + fn new(stream: Payload, limit: usize) -> Self { + Self { + stream, + buf: BytesMut::with_capacity(std::cmp::min(limit, 32768)), + limit, + } + } +} + +impl Future for ReadBody +where + S: Stream, +{ + type Item = Bytes; + type Error = PayloadError; + + fn poll(&mut self) -> Poll { + loop { + return match self.stream.poll()? { + Async::Ready(Some(chunk)) => { + if (self.buf.len() + chunk.len()) > self.limit { + Err(PayloadError::Overflow) + } else { + self.buf.extend_from_slice(&chunk); + continue; + } } - }) - .and_then(|body| Ok(serde_json::from_slice::(&body)?)); - self.fut = Some(Box::new(fut)); - self.poll() + Async::Ready(None) => Ok(Async::Ready(self.buf.take().freeze())), + Async::NotReady => Ok(Async::NotReady), + }; + } } } @@ -391,8 +391,8 @@ mod tests { fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool { match err { - JsonPayloadError::Overflow => match other { - JsonPayloadError::Overflow => true, + JsonPayloadError::Payload(PayloadError::Overflow) => match other { + JsonPayloadError::Payload(PayloadError::Overflow) => true, _ => false, }, JsonPayloadError::ContentType => match other { @@ -430,7 +430,10 @@ mod tests { .finish(); let json = block_on(JsonBody::<_, MyObject>::new(&mut req).limit(100)); - assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow)); + assert!(json_eq( + json.err().unwrap(), + JsonPayloadError::Payload(PayloadError::Overflow) + )); let mut req = TestResponse::default() .header( diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 51791d67a..a2882708a 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -44,15 +44,15 @@ fn test_simple() { )) }); - let request = srv.get().header("x-test", "111").send(); - let response = srv.block_on(request).unwrap(); + let request = srv.get("/").header("x-test", "111").send(); + let mut response = srv.block_on(request).unwrap(); assert!(response.status().is_success()); // read response let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - let response = srv.block_on(srv.post().send()).unwrap(); + let mut response = srv.block_on(srv.post("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -114,7 +114,7 @@ fn test_timeout_override() { // let mut srv = // test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); -// let request = srv.get().header("Connection", "close").finish().unwrap(); +// let request = srv.get("/").header("Connection", "close").finish().unwrap(); // let response = srv.execute(request.send()).unwrap(); // assert!(response.status().is_success()); // } @@ -128,7 +128,7 @@ fn test_timeout_override() { // }) // }); -// let request = srv.get().uri(srv.url("/?qp=5").as_str()).finish().unwrap(); +// let request = srv.get("/").uri(srv.url("/?qp=5").as_str()).finish().unwrap(); // let response = srv.execute(request.send()).unwrap(); // assert!(response.status().is_success()); @@ -139,7 +139,7 @@ fn test_timeout_override() { // let mut srv = // test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); -// let request = srv.get().disable_decompress().finish().unwrap(); +// let request = srv.get("/").disable_decompress().finish().unwrap(); // let response = srv.execute(request.send()).unwrap(); // assert!(response.status().is_success()); @@ -177,7 +177,7 @@ fn test_client_gzip_encoding() { }); // client request - let response = srv.block_on(srv.post().send()).unwrap(); + let mut response = srv.block_on(srv.post("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -200,7 +200,7 @@ fn test_client_gzip_encoding_large() { }); // client request - let response = srv.block_on(srv.post().send()).unwrap(); + let mut response = srv.block_on(srv.post("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -229,7 +229,7 @@ fn test_client_gzip_encoding_large_random() { }); // client request - let response = srv.block_on(srv.post().send_body(data.clone())).unwrap(); + let mut response = srv.block_on(srv.post("/").send_body(data.clone())).unwrap(); assert!(response.status().is_success()); // read response @@ -253,7 +253,7 @@ fn test_client_brotli_encoding() { }); // client request - let response = srv.block_on(srv.post().send_body(STR)).unwrap(); + let mut response = srv.block_on(srv.post("/").send_body(STR)).unwrap(); assert!(response.status().is_success()); // read response @@ -375,7 +375,7 @@ fn test_client_brotli_encoding() { // let body = once(Ok(Bytes::from_static(STR.as_ref()))); -// let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap(); +// let request = srv.get("/").body(Body::Streaming(Box::new(body))).unwrap(); // let response = srv.execute(request.send()).unwrap(); // assert!(response.status().is_success()); @@ -395,7 +395,7 @@ fn test_client_brotli_encoding() { // }) // }); -// let request = srv.get().finish().unwrap(); +// let request = srv.get("/").finish().unwrap(); // let response = srv.execute(request.send()).unwrap(); // assert!(response.status().is_success()); @@ -459,7 +459,7 @@ fn test_client_cookie_handling() { )) }); - let request = srv.get().cookie(cookie1.clone()).cookie(cookie2.clone()); + let request = srv.get("/").cookie(cookie1.clone()).cookie(cookie2.clone()); let response = srv.block_on(request.send()).unwrap(); assert!(response.status().is_success()); let c1 = response.cookie("cookie1").expect("Missing cookie1"); @@ -472,7 +472,7 @@ fn test_client_cookie_handling() { // fn test_default_headers() { // let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); -// let request = srv.get().finish().unwrap(); +// let request = srv.get("/").finish().unwrap(); // let repr = format!("{:?}", request); // assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\"")); // assert!(repr.contains(concat!( @@ -482,7 +482,7 @@ fn test_client_cookie_handling() { // ))); // let request_override = srv -// .get() +// .get("/") // .header("User-Agent", "test") // .header("Accept-Encoding", "over_test") // .finish() @@ -551,7 +551,7 @@ fn client_basic_auth() { }); // set authorization header to Basic - let request = srv.get().basic_auth("username", Some("password")); + let request = srv.get("/").basic_auth("username", Some("password")); let response = srv.block_on(request.send()).unwrap(); assert!(response.status().is_success()); } @@ -579,7 +579,7 @@ fn client_bearer_auth() { }); // set authorization header to Bearer - let request = srv.get().bearer_auth("someS3cr3tAutht0k3n"); + let request = srv.get("/").bearer_auth("someS3cr3tAutht0k3n"); let response = srv.block_on(request.send()).unwrap(); assert!(response.status().is_success()); } diff --git a/src/app.rs b/src/app.rs index 9535dac21..802569458 100644 --- a/src/app.rs +++ b/src/app.rs @@ -15,7 +15,7 @@ use bytes::Bytes; use futures::{IntoFuture, Stream}; use crate::app_service::{AppChain, AppEntry, AppInit, AppRouting, AppRoutingFactory}; -use crate::config::{AppConfig, AppConfigInner}; +use crate::config::{AppConfig, AppConfigInner, RouterConfig}; use crate::data::{Data, DataFactory}; use crate::dev::{Payload, PayloadStream, ResourceDef}; use crate::error::{Error, PayloadError}; @@ -112,9 +112,9 @@ where self } - /// Registers middleware, in the form of a middleware component (type), - /// that runs during inbound and/or outbound processing in the request - /// lifecycle (request -> response), modifying request/response as + /// Registers middleware, in the form of a middleware component (type), + /// that runs during inbound and/or outbound processing in the request + /// lifecycle (request -> response), modifying request/response as /// necessary, across all requests managed by the *Application*. /// /// Use middleware when you need to read or modify *every* request or response in some way. @@ -257,6 +257,55 @@ where } } + /// Run external configuration as part of the application building + /// process + /// + /// This function is useful for moving parts of configuration to a + /// different module or even library. For example, + /// some of the resource's configuration could be moved to different module. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{web, middleware, App, HttpResponse}; + /// + /// // this function could be located in different module + /// fn config

(cfg: &mut web::RouterConfig

) { + /// cfg.service(web::resource("/test") + /// .route(web::get().to(|| HttpResponse::Ok())) + /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) + /// ); + /// } + /// + /// fn main() { + /// let app = App::new() + /// .wrap(middleware::Logger::default()) + /// .configure(config) // <- register resources + /// .route("/index.html", web::get().to(|| HttpResponse::Ok())); + /// } + /// ``` + pub fn configure(mut self, f: F) -> AppRouter> + where + F: Fn(&mut RouterConfig), + { + let mut cfg = RouterConfig::new(); + f(&mut cfg); + self.data.extend(cfg.data); + + let fref = Rc::new(RefCell::new(None)); + + AppRouter { + chain: self.chain, + default: None, + endpoint: AppEntry::new(fref.clone()), + factory_ref: fref, + data: self.data, + config: self.config, + services: cfg.services, + external: cfg.external, + _t: PhantomData, + } + } + /// Configure route for a specific path. /// /// This is a simplified version of the `App::service()` method. @@ -382,6 +431,45 @@ where InitError = (), >, { + /// Run external configuration as part of the application building + /// process + /// + /// This function is useful for moving parts of configuration to a + /// different module or even library. For example, + /// some of the resource's configuration could be moved to different module. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{web, middleware, App, HttpResponse}; + /// + /// // this function could be located in different module + /// fn config

(cfg: &mut web::RouterConfig

) { + /// cfg.service(web::resource("/test") + /// .route(web::get().to(|| HttpResponse::Ok())) + /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) + /// ); + /// } + /// + /// fn main() { + /// let app = App::new() + /// .wrap(middleware::Logger::default()) + /// .configure(config) // <- register resources + /// .route("/index.html", web::get().to(|| HttpResponse::Ok())); + /// } + /// ``` + pub fn configure(mut self, f: F) -> Self + where + F: Fn(&mut RouterConfig

), + { + let mut cfg = RouterConfig::new(); + f(&mut cfg); + self.data.extend(cfg.data); + self.services.extend(cfg.services); + self.external.extend(cfg.external); + + self + } + /// Configure route for a specific path. /// /// This is a simplified version of the `App::service()` method. @@ -427,9 +515,9 @@ where self } - /// Registers middleware, in the form of a middleware component (type), - /// that runs during inbound and/or outbound processing in the request - /// lifecycle (request -> response), modifying request/response as + /// Registers middleware, in the form of a middleware component (type), + /// that runs during inbound and/or outbound processing in the request + /// lifecycle (request -> response), modifying request/response as /// necessary, across all requests managed by the *Route*. /// /// Use middleware when you need to read or modify *every* request or response in some way. diff --git a/src/config.rs b/src/config.rs index ceb58feb7..1e552291f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,11 +5,18 @@ use std::rc::Rc; use actix_http::Extensions; use actix_router::ResourceDef; use actix_service::{boxed, IntoNewService, NewService}; +use futures::IntoFuture; +use crate::data::{Data, DataFactory}; use crate::error::Error; use crate::guard::Guard; +use crate::resource::Resource; use crate::rmap::ResourceMap; -use crate::service::{ServiceRequest, ServiceResponse}; +use crate::route::Route; +use crate::service::{ + HttpServiceFactory, ServiceFactory, ServiceFactoryWrapper, ServiceRequest, + ServiceResponse, +}; type Guards = Vec>; type HttpNewService

= @@ -157,3 +164,140 @@ impl Default for AppConfigInner { } } } + +/// Router config. It is used for external configuration. +/// Part of application configuration could be offloaded +/// to set of external methods. This could help with +/// modularization of big application configuration. +pub struct RouterConfig { + pub(crate) services: Vec>>, + pub(crate) data: Vec>, + pub(crate) external: Vec, +} + +impl RouterConfig

{ + pub(crate) fn new() -> Self { + Self { + services: Vec::new(), + data: Vec::new(), + external: Vec::new(), + } + } + + /// Set application data. Applicatin data could be accessed + /// by using `Data` extractor where `T` is data type. + /// + /// This is same as `App::data()` method. + pub fn data(&mut self, data: S) -> &mut Self { + self.data.push(Box::new(Data::new(data))); + self + } + + /// Set application data factory. This function is + /// similar to `.data()` but it accepts data factory. Data object get + /// constructed asynchronously during application initialization. + /// + /// This is same as `App::data_dactory()` method. + pub fn data_factory(&mut self, data: F) -> &mut Self + where + F: Fn() -> R + 'static, + R: IntoFuture + 'static, + R::Error: std::fmt::Debug, + { + self.data.push(Box::new(data)); + self + } + + /// Configure route for a specific path. + /// + /// This is same as `App::route()` method. + pub fn route(&mut self, path: &str, mut route: Route

) -> &mut Self { + self.service( + Resource::new(path) + .add_guards(route.take_guards()) + .route(route), + ) + } + + /// Register http service. + /// + /// This is same as `App::service()` method. + pub fn service(&mut self, factory: F) -> &mut Self + where + F: HttpServiceFactory

+ 'static, + { + self.services + .push(Box::new(ServiceFactoryWrapper::new(factory))); + self + } + + /// Register an external resource. + /// + /// External resources are useful for URL generation purposes only + /// and are never considered for matching at request time. Calls to + /// `HttpRequest::url_for()` will work as expected. + /// + /// This is same as `App::external_service()` method. + pub fn external_resource(&mut self, name: N, url: U) -> &mut Self + where + N: AsRef, + U: AsRef, + { + let mut rdef = ResourceDef::new(url.as_ref()); + *rdef.name_mut() = name.as_ref().to_string(); + self.external.push(rdef); + self + } +} + +#[cfg(test)] +mod tests { + use actix_service::Service; + + use super::*; + use crate::http::StatusCode; + use crate::test::{block_on, init_service, TestRequest}; + use crate::{web, App, HttpResponse}; + + #[test] + fn test_data() { + let cfg = |cfg: &mut RouterConfig<_>| { + cfg.data(10usize); + }; + + let mut srv = + init_service(App::new().configure(cfg).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), + )); + let req = TestRequest::default().to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + } + + #[test] + fn test_data_factory() { + let cfg = |cfg: &mut RouterConfig<_>| { + cfg.data_factory(|| Ok::<_, ()>(10usize)); + }; + + let mut srv = + init_service(App::new().configure(cfg).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), + )); + let req = TestRequest::default().to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let cfg2 = |cfg: &mut RouterConfig<_>| { + cfg.data_factory(|| Ok::<_, ()>(10u32)); + }; + let mut srv = init_service( + App::new() + .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())) + .configure(cfg2), + ); + let req = TestRequest::default().to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + } +} diff --git a/src/data.rs b/src/data.rs index a53015c23..a79a303bc 100644 --- a/src/data.rs +++ b/src/data.rs @@ -92,7 +92,7 @@ impl FromRequest

for Data { #[inline] fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - if let Some(st) = req.config().extensions().get::>() { + if let Some(st) = req.request().config().extensions().get::>() { Ok(st.clone()) } else { Err(ErrorInternalServerError( diff --git a/src/error.rs b/src/error.rs index 78dc2fb6a..74b890f0a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -121,36 +121,6 @@ impl ResponseError for ReadlinesError { } } -/// A set of errors that can occur during parsing multipart streams -#[derive(Debug, Display, From)] -pub enum MultipartError { - /// Content-Type header is not found - #[display(fmt = "No Content-type header found")] - NoContentType, - /// Can not parse Content-Type header - #[display(fmt = "Can not parse Content-Type header")] - ParseContentType, - /// Multipart boundary is not found - #[display(fmt = "Multipart boundary is not found")] - Boundary, - /// Multipart stream is incomplete - #[display(fmt = "Multipart stream is incomplete")] - Incomplete, - /// Error during field parsing - #[display(fmt = "{}", _0)] - Parse(ParseError), - /// Payload error - #[display(fmt = "{}", _0)] - Payload(PayloadError), -} - -/// Return `BadRequest` for `MultipartError` -impl ResponseError for MultipartError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) - } -} - #[cfg(test)] mod tests { use super::*; @@ -180,10 +150,4 @@ mod tests { let resp: HttpResponse = ReadlinesError::EncodingError.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } - - #[test] - fn test_multipart_error() { - let resp: HttpResponse = MultipartError::Boundary.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - } } diff --git a/src/guard.rs b/src/guard.rs index 44e4891e6..0990e876a 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -73,6 +73,15 @@ where } } +impl Guard for F +where + F: Fn(&RequestHead) -> bool, +{ + fn check(&self, head: &RequestHead) -> bool { + (self)(head) + } +} + /// Return guard that matches if any of supplied guards. /// /// ```rust @@ -300,13 +309,13 @@ mod tests { .to_http_request(); let pred = Header("transfer-encoding", "chunked"); - assert!(pred.check(&req)); + assert!(pred.check(req.head())); let pred = Header("transfer-encoding", "other"); - assert!(!pred.check(&req)); + assert!(!pred.check(req.head())); let pred = Header("content-type", "other"); - assert!(!pred.check(&req)); + assert!(!pred.check(req.head())); } // #[test] @@ -332,50 +341,50 @@ mod tests { .method(Method::POST) .to_http_request(); - assert!(Get().check(&req)); - assert!(!Get().check(&req2)); - assert!(Post().check(&req2)); - assert!(!Post().check(&req)); + assert!(Get().check(req.head())); + assert!(!Get().check(req2.head())); + assert!(Post().check(req2.head())); + assert!(!Post().check(req.head())); let r = TestRequest::default().method(Method::PUT).to_http_request(); - assert!(Put().check(&r)); - assert!(!Put().check(&req)); + assert!(Put().check(r.head())); + assert!(!Put().check(req.head())); let r = TestRequest::default() .method(Method::DELETE) .to_http_request(); - assert!(Delete().check(&r)); - assert!(!Delete().check(&req)); + assert!(Delete().check(r.head())); + assert!(!Delete().check(req.head())); let r = TestRequest::default() .method(Method::HEAD) .to_http_request(); - assert!(Head().check(&r)); - assert!(!Head().check(&req)); + assert!(Head().check(r.head())); + assert!(!Head().check(req.head())); let r = TestRequest::default() .method(Method::OPTIONS) .to_http_request(); - assert!(Options().check(&r)); - assert!(!Options().check(&req)); + assert!(Options().check(r.head())); + assert!(!Options().check(req.head())); let r = TestRequest::default() .method(Method::CONNECT) .to_http_request(); - assert!(Connect().check(&r)); - assert!(!Connect().check(&req)); + assert!(Connect().check(r.head())); + assert!(!Connect().check(req.head())); let r = TestRequest::default() .method(Method::PATCH) .to_http_request(); - assert!(Patch().check(&r)); - assert!(!Patch().check(&req)); + assert!(Patch().check(r.head())); + assert!(!Patch().check(req.head())); let r = TestRequest::default() .method(Method::TRACE) .to_http_request(); - assert!(Trace().check(&r)); - assert!(!Trace().check(&req)); + assert!(Trace().check(r.head())); + assert!(!Trace().check(req.head())); } #[test] @@ -384,13 +393,13 @@ mod tests { .method(Method::TRACE) .to_http_request(); - assert!(Not(Get()).check(&r)); - assert!(!Not(Trace()).check(&r)); + assert!(Not(Get()).check(r.head())); + assert!(!Not(Trace()).check(r.head())); - assert!(All(Trace()).and(Trace()).check(&r)); - assert!(!All(Get()).and(Trace()).check(&r)); + assert!(All(Trace()).and(Trace()).check(r.head())); + assert!(!All(Get()).and(Trace()).check(r.head())); - assert!(Any(Get()).or(Trace()).check(&r)); - assert!(!Any(Get()).or(Get()).check(&r)); + assert!(Any(Get()).or(Trace()).check(r.head())); + assert!(!Any(Get()).or(Get()).check(r.head())); } } diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index d797e1250..f74754402 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -113,7 +113,7 @@ where fn call(&mut self, req: ServiceRequest

) -> Self::Future { // negotiate content-encoding - let encoding = if let Some(val) = req.headers.get(ACCEPT_ENCODING) { + let encoding = if let Some(val) = req.headers().get(ACCEPT_ENCODING) { if let Ok(enc) = val.to_str() { AcceptEncoding::parse(enc, self.encoding) } else { @@ -157,7 +157,7 @@ where fn poll(&mut self) -> Poll { let resp = futures::try_ready!(self.fut.poll()); - let enc = if let Some(enc) = resp.head().extensions().get::() { + let enc = if let Some(enc) = resp.response().extensions().get::() { enc.0 } else { self.encoding diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 8924eb0ab..920b480bb 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -51,7 +51,7 @@ use crate::error::{ResponseError, Result}; use crate::http::header::{self, HeaderName, HeaderValue}; use crate::http::{self, HttpTryFrom, Method, StatusCode, Uri}; use crate::service::{ServiceRequest, ServiceResponse}; -use crate::{HttpMessage, HttpResponse}; +use crate::HttpResponse; /// A set of errors that can occur during processing CORS #[derive(Debug, Display)] @@ -702,9 +702,9 @@ where if self.inner.preflight && Method::OPTIONS == *req.method() { if let Err(e) = self .inner - .validate_origin(&req) - .and_then(|_| self.inner.validate_allowed_method(&req)) - .and_then(|_| self.inner.validate_allowed_headers(&req)) + .validate_origin(req.head()) + .and_then(|_| self.inner.validate_allowed_method(req.head())) + .and_then(|_| self.inner.validate_allowed_headers(req.head())) { return Either::A(ok(req.error_response(e))); } @@ -739,7 +739,7 @@ where let _ = resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers); }) .if_some( - self.inner.access_control_allow_origin(&req), + self.inner.access_control_allow_origin(req.head()), |origin, resp| { let _ = resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin); }, @@ -762,7 +762,7 @@ where Either::A(ok(req.into_response(res))) } else if req.headers().contains_key(header::ORIGIN) { // Only check requests with a origin header. - if let Err(e) = self.inner.validate_origin(&req) { + if let Err(e) = self.inner.validate_origin(req.head()) { return Either::A(ok(req.error_response(e))); } @@ -771,7 +771,7 @@ where Either::B(Either::B(Box::new(self.service.call(req).and_then( move |mut res| { if let Some(origin) = - inner.access_control_allow_origin(&res.request()) + inner.access_control_allow_origin(res.request().head()) { res.headers_mut() .insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone()); @@ -869,8 +869,8 @@ mod tests { .method(Method::OPTIONS) .to_srv_request(); - assert!(cors.inner.validate_allowed_method(&req).is_err()); - assert!(cors.inner.validate_allowed_headers(&req).is_err()); + assert!(cors.inner.validate_allowed_method(req.head()).is_err()); + assert!(cors.inner.validate_allowed_headers(req.head()).is_err()); let resp = test::call_success(&mut cors, req); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); @@ -879,8 +879,8 @@ mod tests { .method(Method::OPTIONS) .to_srv_request(); - assert!(cors.inner.validate_allowed_method(&req).is_err()); - assert!(cors.inner.validate_allowed_headers(&req).is_err()); + assert!(cors.inner.validate_allowed_method(req.head()).is_err()); + assert!(cors.inner.validate_allowed_headers(req.head()).is_err()); let req = TestRequest::with_header("Origin", "https://www.example.com") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") @@ -961,9 +961,9 @@ mod tests { let req = TestRequest::with_header("Origin", "https://www.unknown.com") .method(Method::GET) .to_srv_request(); - cors.inner.validate_origin(&req).unwrap(); - cors.inner.validate_allowed_method(&req).unwrap(); - cors.inner.validate_allowed_headers(&req).unwrap(); + cors.inner.validate_origin(req.head()).unwrap(); + cors.inner.validate_allowed_method(req.head()).unwrap(); + cors.inner.validate_allowed_headers(req.head()).unwrap(); } #[test] diff --git a/src/middleware/decompress.rs b/src/middleware/decompress.rs index 84d357375..13735143a 100644 --- a/src/middleware/decompress.rs +++ b/src/middleware/decompress.rs @@ -10,7 +10,6 @@ use futures::{Async, Poll, Stream}; use crate::dev::Payload; use crate::error::{Error, PayloadError}; use crate::service::ServiceRequest; -use crate::HttpMessage; /// `Middleware` for decompressing request's payload. /// `Decompress` middleware must be added with `App::chain()` method. diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index 34979e167..7a2c9f376 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -148,7 +148,7 @@ impl

FromRequest

for Identity { #[inline] fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - Ok(Identity(req.clone())) + Ok(Identity(req.request().clone())) } } @@ -507,7 +507,7 @@ mod tests { let resp = test::call_success(&mut srv, TestRequest::with_uri("/login").to_request()); assert_eq!(resp.status(), StatusCode::OK); - let c = resp.cookies().next().unwrap().to_owned(); + let c = resp.response().cookies().next().unwrap().to_owned(); let resp = test::call_success( &mut srv, diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index a120fe7b3..3039b850f 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -15,7 +15,7 @@ use time; use crate::dev::{BodySize, MessageBody, ResponseBody}; use crate::error::{Error, Result}; use crate::service::{ServiceRequest, ServiceResponse}; -use crate::{HttpMessage, HttpResponse}; +use crate::HttpResponse; /// `Middleware` for logging request and response info to the terminal. /// @@ -203,7 +203,7 @@ where if let Some(ref mut format) = self.format { for unit in &mut format.0 { - unit.render_response(&res); + unit.render_response(res.response()); } } diff --git a/src/request.rs b/src/request.rs index c524d4978..b5ba74122 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,6 +1,5 @@ use std::cell::{Ref, RefMut}; use std::fmt; -use std::ops::Deref; use std::rc::Rc; use actix_http::http::{HeaderMap, Method, Uri, Version}; @@ -66,6 +65,12 @@ impl HttpRequest { self.head().version } + #[inline] + /// Returns request's headers. + pub fn headers(&self) -> &HeaderMap { + &self.head().headers + } + /// The target path of this Request. #[inline] pub fn path(&self) -> &str { @@ -111,6 +116,18 @@ impl HttpRequest { } } + /// Request extensions + #[inline] + pub fn extensions(&self) -> Ref { + self.head().extensions() + } + + /// Mutable reference to a the request's extensions + #[inline] + pub fn extensions_mut(&self) -> RefMut { + self.head().extensions_mut() + } + /// Generate url for named resource /// /// ```rust @@ -154,15 +171,7 @@ impl HttpRequest { /// Get *ConnectionInfo* for the current request. #[inline] pub fn connection_info(&self) -> Ref { - ConnectionInfo::get(&*self, &*self.config()) - } -} - -impl Deref for HttpRequest { - type Target = RequestHead; - - fn deref(&self) -> &RequestHead { - self.head() + ConnectionInfo::get(self.head(), &*self.config()) } } @@ -219,7 +228,7 @@ impl

FromRequest

for HttpRequest { #[inline] fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - Ok(req.clone()) + Ok(req.request().clone()) } } diff --git a/src/responder.rs b/src/responder.rs index 50467883c..3e0676289 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -313,7 +313,7 @@ pub(crate) mod tests { let req = TestRequest::with_uri("/some").to_request(); let resp = TestRequest::block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); - match resp.body() { + match resp.response().body() { ResponseBody::Body(Body::Bytes(ref b)) => { let bytes: Bytes = b.clone().into(); assert_eq!(bytes, Bytes::from_static(b"some")); diff --git a/src/scope.rs b/src/scope.rs index 0dfaaf066..7ad2d95eb 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -200,12 +200,12 @@ where self } - /// Registers middleware, in the form of a middleware component (type), - /// that runs during inbound processing in the request - /// lifecycle (request -> response), modifying request as + /// Registers middleware, in the form of a middleware component (type), + /// that runs during inbound processing in the request + /// lifecycle (request -> response), modifying request as /// necessary, across all requests managed by the *Scope*. Scope-level /// middleware is more limited in what it can modify, relative to Route or - /// Application level middleware, in that Scope-level middleware can not modify + /// Application level middleware, in that Scope-level middleware can not modify /// ServiceResponse. /// /// Use middleware when you need to read or modify *every* request in some way. @@ -243,7 +243,7 @@ where } /// Registers middleware, in the form of a closure, that runs during inbound - /// processing in the request lifecycle (request -> response), modifying + /// processing in the request lifecycle (request -> response), modifying /// request as necessary, across all requests managed by the *Scope*. /// Scope-level middleware is more limited in what it can modify, relative /// to Route or Application level middleware, in that Scope-level middleware @@ -693,7 +693,7 @@ mod tests { let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); - match resp.body() { + match resp.response().body() { ResponseBody::Body(Body::Bytes(ref b)) => { let bytes: Bytes = b.clone().into(); assert_eq!(bytes, Bytes::from_static(b"project: project1")); @@ -799,7 +799,7 @@ mod tests { let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::CREATED); - match resp.body() { + match resp.response().body() { ResponseBody::Body(Body::Bytes(ref b)) => { let bytes: Bytes = b.clone().into(); assert_eq!(bytes, Bytes::from_static(b"project: project_1")); @@ -826,7 +826,7 @@ mod tests { let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::CREATED); - match resp.body() { + match resp.response().body() { ResponseBody::Body(Body::Bytes(ref b)) => { let bytes: Bytes = b.clone().into(); assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); diff --git a/src/service.rs b/src/service.rs index c260f25b2..13aae8692 100644 --- a/src/service.rs +++ b/src/service.rs @@ -4,7 +4,7 @@ use std::marker::PhantomData; use std::rc::Rc; use actix_http::body::{Body, MessageBody, ResponseBody}; -use actix_http::http::{HeaderMap, Method, Uri, Version}; +use actix_http::http::{HeaderMap, Method, StatusCode, Uri, Version}; use actix_http::{ Error, Extensions, HttpMessage, Payload, PayloadStream, Request, RequestHead, Response, ResponseHead, @@ -123,7 +123,13 @@ impl

ServiceRequest

{ } #[inline] - /// Returns mutable Request's headers. + /// Returns request's headers. + pub fn headers(&self) -> &HeaderMap { + &self.head().headers + } + + #[inline] + /// Returns mutable request's headers. pub fn headers_mut(&mut self) -> &mut HeaderMap { &mut self.head_mut().headers } @@ -202,20 +208,6 @@ impl

HttpMessage for ServiceRequest

{ } } -impl

std::ops::Deref for ServiceRequest

{ - type Target = RequestHead; - - fn deref(&self) -> &RequestHead { - self.req.head() - } -} - -impl

std::ops::DerefMut for ServiceRequest

{ - fn deref_mut(&mut self) -> &mut RequestHead { - self.head_mut() - } -} - impl

fmt::Debug for ServiceRequest

{ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!( @@ -255,11 +247,19 @@ impl

ServiceFromRequest

{ } #[inline] + /// Get reference to inner HttpRequest + pub fn request(&self) -> &HttpRequest { + &self.req + } + + #[inline] + /// Convert this request into a HttpRequest pub fn into_request(self) -> HttpRequest { self.req } #[inline] + /// Get match information for this request pub fn match_info_mut(&mut self) -> &mut Path { &mut self.req.path } @@ -281,14 +281,6 @@ impl

ServiceFromRequest

{ } } -impl

std::ops::Deref for ServiceFromRequest

{ - type Target = HttpRequest; - - fn deref(&self) -> &HttpRequest { - &self.req - } -} - impl

HttpMessage for ServiceFromRequest

{ type Stream = P; @@ -366,6 +358,24 @@ impl ServiceResponse { &mut self.response } + /// Get the response status code + #[inline] + pub fn status(&self) -> StatusCode { + self.response.status() + } + + #[inline] + /// Returns response's headers. + pub fn headers(&self) -> &HeaderMap { + self.response.headers() + } + + #[inline] + /// Returns mutable response's headers. + pub fn headers_mut(&mut self) -> &mut HeaderMap { + self.response.headers_mut() + } + /// Execute closure and in case of error convert it to response. pub fn checked_expr(mut self, f: F) -> Self where @@ -402,20 +412,6 @@ impl ServiceResponse { } } -impl std::ops::Deref for ServiceResponse { - type Target = Response; - - fn deref(&self) -> &Response { - self.response() - } -} - -impl std::ops::DerefMut for ServiceResponse { - fn deref_mut(&mut self) -> &mut Response { - self.response_mut() - } -} - impl Into> for ServiceResponse { fn into(self) -> Response { self.response diff --git a/src/types/form.rs b/src/types/form.rs index cd4d09bb5..812a08e52 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -80,7 +80,7 @@ where #[inline] fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - let req2 = req.clone(); + let req2 = req.request().clone(); let (limit, err) = req .route_data::() .map(|c| (c.limit, c.ehandler.clone())) diff --git a/src/types/json.rs b/src/types/json.rs index 9e13d994e..c8ed5afd3 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -174,7 +174,7 @@ where #[inline] fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - let req2 = req.clone(); + let req2 = req.request().clone(); let (limit, err) = req .route_data::() .map(|c| (c.limit, c.ehandler.clone())) diff --git a/src/types/mod.rs b/src/types/mod.rs index 9a0a08801..30ee73091 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -2,7 +2,6 @@ pub(crate) mod form; pub(crate) mod json; -mod multipart; mod path; pub(crate) mod payload; mod query; @@ -10,7 +9,6 @@ pub(crate) mod readlines; pub use self::form::{Form, FormConfig}; pub use self::json::{Json, JsonConfig}; -pub use self::multipart::{Multipart, MultipartField, MultipartItem}; pub use self::path::Path; pub use self::payload::{Payload, PayloadConfig}; pub use self::query::Query; diff --git a/src/types/path.rs b/src/types/path.rs index 4e6784794..fbd106630 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -170,7 +170,7 @@ where #[inline] fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - Self::extract(req).map_err(ErrorNotFound) + Self::extract(req.request()).map_err(ErrorNotFound) } } diff --git a/src/types/query.rs b/src/types/query.rs index f0eb6a7ae..85dab0610 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -119,7 +119,7 @@ where #[inline] fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - serde_urlencoded::from_str::(req.query_string()) + serde_urlencoded::from_str::(req.request().query_string()) .map(|val| Ok(Query(val))) .unwrap_or_else(|e| Err(e.into())) } diff --git a/src/web.rs b/src/web.rs index 65b3cfc70..94c98c22a 100644 --- a/src/web.rs +++ b/src/web.rs @@ -13,6 +13,7 @@ use crate::responder::Responder; use crate::route::Route; use crate::scope::Scope; +pub use crate::config::RouterConfig; pub use crate::data::{Data, RouteData}; pub use crate::request::HttpRequest; pub use crate::types::*; diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md index cac5a2afe..14a8ce628 100644 --- a/test-server/CHANGES.md +++ b/test-server/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## [0.1.0-alpha.3] - 2019-04-02 + +* Request functions accept path #743 + + ## [0.1.0-alpha.2] - 2019-03-29 * Added TestServerRuntime::load_body() method diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 838f2d8d2..f85e2b156 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "0.1.0-alpha.2" +version = "0.1.0-alpha.3" authors = ["Nikolay Kim "] description = "Actix http test server" readme = "README.md" @@ -35,7 +35,7 @@ actix-rt = "0.2.1" actix-service = "0.3.4" actix-server = "0.4.0" actix-utils = "0.3.4" -awc = "0.1.0-alpha.2" +awc = "0.1.0-alpha.3" base64 = "0.10" bytes = "0.4" @@ -53,3 +53,7 @@ time = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" openssl = { version="0.10", optional = true } + +[dev-dependencies] +actix-web = "1.0.0-alpa.3" +actix-http = "0.1.0-alpa.3" diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index b64ff433e..98bef99be 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -36,7 +36,7 @@ use net2::TcpBuilder; /// ) /// ); /// -/// let req = srv.get(); +/// let req = srv.get("/"); /// let response = srv.block_on(req.send()).unwrap(); /// assert!(response.status().is_success()); /// } @@ -195,7 +195,7 @@ impl TestServerRuntime { pub fn load_body( &mut self, - response: ClientResponse, + mut response: ClientResponse, ) -> Result where S: Stream + 'static, diff --git a/tests/test_server.rs b/tests/test_server.rs index fc590ff0b..3c5d09066 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -54,7 +54,7 @@ fn test_body() { ) }); - let mut response = srv.block_on(srv.get().send()).unwrap(); + let mut response = srv.block_on(srv.get("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -73,7 +73,7 @@ fn test_body_gzip() { ) }); - let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap(); + let mut response = srv.block_on(srv.get("/").no_decompress().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -111,7 +111,7 @@ fn test_body_encoding_override() { }); // Builder - let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap(); + let mut response = srv.block_on(srv.get("/").no_decompress().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -161,7 +161,7 @@ fn test_body_gzip_large() { ) }); - let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap(); + let mut response = srv.block_on(srv.get("/").no_decompress().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -195,7 +195,7 @@ fn test_body_gzip_large_random() { ) }); - let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap(); + let mut response = srv.block_on(srv.get("/").no_decompress().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -224,7 +224,7 @@ fn test_body_chunked_implicit() { ) }); - let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap(); + let mut response = srv.block_on(srv.get("/").no_decompress().send()).unwrap(); assert!(response.status().is_success()); assert_eq!( response.headers().get(TRANSFER_ENCODING).unwrap(), @@ -258,7 +258,7 @@ fn test_body_br_streaming() { let mut response = srv .block_on( - srv.get() + srv.get("/") .header(ACCEPT_ENCODING, "br") .no_decompress() .send(), @@ -284,7 +284,7 @@ fn test_head_binary() { ))) }); - let mut response = srv.block_on(srv.head().send()).unwrap(); + let mut response = srv.block_on(srv.head("/").send()).unwrap(); assert!(response.status().is_success()); { @@ -310,7 +310,7 @@ fn test_no_chunking() { )))) }); - let mut response = srv.block_on(srv.get().send()).unwrap(); + let mut response = srv.block_on(srv.get("/").send()).unwrap(); assert!(response.status().is_success()); assert!(!response.headers().contains_key(TRANSFER_ENCODING)); @@ -333,7 +333,7 @@ fn test_body_deflate() { }); // client request - let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap(); + let mut response = srv.block_on(srv.get("/").no_decompress().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -362,7 +362,7 @@ fn test_body_brotli() { // client request let mut response = srv .block_on( - srv.get() + srv.get("/") .header(ACCEPT_ENCODING, "br") .no_decompress() .send(), @@ -398,7 +398,7 @@ fn test_encoding() { let enc = e.finish().unwrap(); let request = srv - .post() + .post("/") .header(CONTENT_ENCODING, "gzip") .send_body(enc.clone()); let mut response = srv.block_on(request).unwrap(); @@ -427,7 +427,7 @@ fn test_gzip_encoding() { let enc = e.finish().unwrap(); let request = srv - .post() + .post("/") .header(CONTENT_ENCODING, "gzip") .send_body(enc.clone()); let mut response = srv.block_on(request).unwrap(); @@ -457,7 +457,7 @@ fn test_gzip_encoding_large() { let enc = e.finish().unwrap(); let request = srv - .post() + .post("/") .header(CONTENT_ENCODING, "gzip") .send_body(enc.clone()); let mut response = srv.block_on(request).unwrap(); @@ -491,7 +491,7 @@ fn test_reading_gzip_encoding_large_random() { let enc = e.finish().unwrap(); let request = srv - .post() + .post("/") .header(CONTENT_ENCODING, "gzip") .send_body(enc.clone()); let mut response = srv.block_on(request).unwrap(); @@ -521,7 +521,7 @@ fn test_reading_deflate_encoding() { // client request let request = srv - .post() + .post("/") .header(CONTENT_ENCODING, "deflate") .send_body(enc.clone()); let mut response = srv.block_on(request).unwrap(); @@ -551,7 +551,7 @@ fn test_reading_deflate_encoding_large() { // client request let request = srv - .post() + .post("/") .header(CONTENT_ENCODING, "deflate") .send_body(enc.clone()); let mut response = srv.block_on(request).unwrap(); @@ -585,7 +585,7 @@ fn test_reading_deflate_encoding_large_random() { // client request let request = srv - .post() + .post("/") .header(CONTENT_ENCODING, "deflate") .send_body(enc.clone()); let mut response = srv.block_on(request).unwrap(); @@ -615,7 +615,7 @@ fn test_brotli_encoding() { // client request let request = srv - .post() + .post("/") .header(CONTENT_ENCODING, "br") .send_body(enc.clone()); let mut response = srv.block_on(request).unwrap(); @@ -645,7 +645,7 @@ fn test_brotli_encoding_large() { // client request let request = srv - .post() + .post("/") .header(CONTENT_ENCODING, "br") .send_body(enc.clone()); let mut response = srv.block_on(request).unwrap(); @@ -912,7 +912,7 @@ fn test_reading_deflate_encoding_large_random_ssl() { // .finish(); // let second_cookie = http::Cookie::new("second", "second_value"); -// let request = srv.get().finish().unwrap(); +// let request = srv.get("/").finish().unwrap(); // let response = srv.execute(request.send()).unwrap(); // assert!(response.status().is_success());