From f56072954bb16e21308617a3bc0cff5f38ef10fb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Apr 2019 03:20:20 -0700 Subject: [PATCH 001/122] remove PayloadBuffer --- actix-http/CHANGES.md | 4 + actix-http/src/h1/mod.rs | 2 +- actix-http/src/h1/payload.rs | 398 +---------------------------------- 3 files changed, 6 insertions(+), 398 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 79995b77f..eef0bdaf8 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,9 @@ # Changes +### Deleted + +* Removed PayloadBuffer + ## [0.1.0-alpha.3] - 2019-04-02 ### Added diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs index 472d73477..3bf69b38e 100644 --- a/actix-http/src/h1/mod.rs +++ b/actix-http/src/h1/mod.rs @@ -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; pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; #[derive(Debug)] diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 73d05c4bb..187962259 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}; @@ -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() From e738361e09b7533ab77f5269400b6429622e6a67 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Apr 2019 12:28:58 -0700 Subject: [PATCH 002/122] move multipart support to separate crate --- CHANGES.md | 7 + Cargo.toml | 2 +- actix-http/CHANGES.md | 2 + actix-http/src/h1/mod.rs | 2 +- actix-http/src/h1/payload.rs | 4 +- actix-multipart/CHANGES.md | 5 + actix-multipart/Cargo.toml | 34 ++ actix-multipart/README.md | 1 + actix-multipart/src/error.rs | 46 ++ actix-multipart/src/extractor.rs | 57 +++ actix-multipart/src/lib.rs | 6 + .../src/server.rs | 420 ++++++++++++------ src/types/mod.rs | 2 - 13 files changed, 454 insertions(+), 134 deletions(-) create mode 100644 actix-multipart/CHANGES.md create mode 100644 actix-multipart/Cargo.toml create mode 100644 actix-multipart/README.md create mode 100644 actix-multipart/src/error.rs create mode 100644 actix-multipart/src/extractor.rs create mode 100644 actix-multipart/src/lib.rs rename src/types/multipart.rs => actix-multipart/src/server.rs (70%) diff --git a/CHANGES.md b/CHANGES.md index d6ff547d5..fc690ee50 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [1.0.0-alpha.3] - 2019-04-xx + +### Changed + +* Move multipart support to actix-multipart crate + + ## [1.0.0-alpha.3] - 2019-04-02 ### Changed diff --git a/Cargo.toml b/Cargo.toml index 0e2fb32a9..507be4bb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ members = [ "actix-http", "actix-files", "actix-session", + "actix-multipart", "actix-web-actors", "actix-web-codegen", "test-server", @@ -83,7 +84,6 @@ 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" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index eef0bdaf8..3ae481db4 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,7 @@ # Changes +## [0.1.0-alpha.4] - 2019-04-xx + ### Deleted * Removed PayloadBuffer diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs index 3bf69b38e..a05f2800c 100644 --- a/actix-http/src/h1/mod.rs +++ b/actix-http/src/h1/mod.rs @@ -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; +pub use self::payload::{Payload, PayloadWriter}; pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; #[derive(Debug)] diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 187962259..bef87f7dc 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -14,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, @@ -106,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); 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..2a65840a1 --- /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-session)](https://crates.io/crates/actix-session) [![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/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; From dfa0abf5a5e158f44bea043b765cdf7625bfa236 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Apr 2019 12:44:47 -0700 Subject: [PATCH 003/122] Export IntoHeaderValue --- actix-http/CHANGES.md | 4 ++++ actix-http/src/header/mod.rs | 22 ++++++++++++++++++++-- src/error.rs | 36 ------------------------------------ 3 files changed, 24 insertions(+), 38 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 3ae481db4..742ff8d3e 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,6 +2,10 @@ ## [0.1.0-alpha.4] - 2019-04-xx +### Changed + +* Export IntoHeaderValue + ### Deleted * Removed PayloadBuffer 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/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); - } } From 237bfba1ed2e7fd1e739093c24ab75fa01a8fac6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Apr 2019 15:09:31 -0700 Subject: [PATCH 004/122] add App::configure() - allow to offload app configuration to different methods --- CHANGES.md | 6 +- actix-multipart/README.md | 2 +- src/app.rs | 90 ++++++++++++++++++++++- src/config.rs | 146 +++++++++++++++++++++++++++++++++++++- src/web.rs | 1 + 5 files changed, 241 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fc690ee50..4aaef9511 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,10 @@ # Changes -## [1.0.0-alpha.3] - 2019-04-xx +## [1.0.0-alpha.4] - 2019-04-xx + +### Added + +* `App::configure()` allow to offload app configuration to different methods ### Changed diff --git a/actix-multipart/README.md b/actix-multipart/README.md index 2a65840a1..2739ff3d5 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -1 +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-session)](https://crates.io/crates/actix-session) [![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) +# 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/src/app.rs b/src/app.rs index fd91d0728..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}; @@ -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. 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/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::*; From cef3dc3586ba64787ca2e4cc395fbd3d7c4478cd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Apr 2019 15:25:52 -0700 Subject: [PATCH 005/122] added app_data() method --- CHANGES.md | 5 +++++ src/service.rs | 22 +++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 4aaef9511..7a6e9b9bf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,11 @@ * `App::configure()` allow to offload app configuration to different methods +* Added `ServiceRequest::app_data()`, returns `Data` + +* Added `ServiceFromRequest::app_data()`, returns `Data` + + ### Changed * Move multipart support to actix-multipart crate diff --git a/src/service.rs b/src/service.rs index 13aae8692..0f11b89e1 100644 --- a/src/service.rs +++ b/src/service.rs @@ -13,7 +13,7 @@ use actix_router::{Path, Resource, Url}; use futures::future::{ok, FutureResult, IntoFuture}; use crate::config::{AppConfig, ServiceConfig}; -use crate::data::RouteData; +use crate::data::{Data, RouteData}; use crate::request::HttpRequest; use crate::rmap::ResourceMap; @@ -173,6 +173,16 @@ impl

ServiceRequest

{ pub fn app_config(&self) -> &AppConfig { self.req.config() } + + /// Get an application data stored with `App::data()` method during + /// application configuration. + pub fn app_data(&self) -> Option> { + if let Some(st) = self.req.config().extensions().get::>() { + Some(st.clone()) + } else { + None + } + } } impl

Resource for ServiceRequest

{ @@ -270,6 +280,16 @@ impl

ServiceFromRequest

{ ServiceResponse::new(self.req, err.into().into()) } + /// Get an application data stored with `App::data()` method during + /// application configuration. + pub fn app_data(&self) -> Option> { + if let Some(st) = self.req.config().extensions().get::>() { + Some(st.clone()) + } else { + None + } + } + /// Load route data. Route data could be set during /// route configuration with `Route::data()` method. pub fn route_data(&self) -> Option<&RouteData> { From 7d6085ddbd32e6104c2f2940c851af83301d8bbd Mon Sep 17 00:00:00 2001 From: Haze Date: Wed, 3 Apr 2019 20:41:42 -0400 Subject: [PATCH 006/122] Add %U (URLPath) for logger (#752) * Add %R (Route) for logger * Requested Updates (Route => URLPath, %R => %U) --- CHANGES.md | 3 ++- src/middleware/logger.rs | 41 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7a6e9b9bf..b7e0d7423 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,11 +6,12 @@ * `App::configure()` allow to offload app configuration to different methods +* Added `URLPath` option for logger + * Added `ServiceRequest::app_data()`, returns `Data` * Added `ServiceFromRequest::app_data()`, returns `Data` - ### Changed * Move multipart support to actix-multipart crate diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index bdcc00f28..3039b850f 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -65,6 +65,8 @@ use crate::HttpResponse; /// /// `%D` Time taken to serve the request, in milliseconds /// +/// `%U` Request URL +/// /// `%{FOO}i` request.headers['FOO'] /// /// `%{FOO}o` response.headers['FOO'] @@ -272,7 +274,7 @@ impl Format { /// Returns `None` if the format string syntax is incorrect. pub fn new(s: &str) -> Format { log::trace!("Access log format: {}", s); - let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([ioe])|[atPrsbTD]?)").unwrap(); + let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([ioe])|[atPrUsbTD]?)").unwrap(); let mut idx = 0; let mut results = Vec::new(); @@ -300,6 +302,7 @@ impl Format { "r" => FormatText::RequestLine, "s" => FormatText::ResponseStatus, "b" => FormatText::ResponseSize, + "U" => FormatText::UrlPath, "T" => FormatText::Time, "D" => FormatText::TimeMillis, _ => FormatText::Str(m.as_str().to_owned()), @@ -328,6 +331,7 @@ pub enum FormatText { Time, TimeMillis, RemoteAddr, + UrlPath, RequestHeader(String), ResponseHeader(String), EnvironHeader(String), @@ -413,6 +417,12 @@ impl FormatText { )) }; } + FormatText::UrlPath => { + *self = FormatText::Str(format!( + "{}", + req.path() + )) + } FormatText::RequestTime => { *self = FormatText::Str(format!( "{:?}", @@ -473,6 +483,35 @@ mod tests { let _res = block_on(srv.call(req)); } + #[test] + fn test_url_path() { + let mut format = Format::new("%T %U"); + let req = TestRequest::with_header( + header::USER_AGENT, + header::HeaderValue::from_static("ACTIX-WEB"), + ).uri("/test/route/yeah").to_srv_request(); + + let now = time::now(); + for unit in &mut format.0 { + unit.render_request(now, &req); + } + + let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + for unit in &mut format.0 { + unit.render_response(&resp); + } + + let render = |fmt: &mut Formatter| { + for unit in &format.0 { + unit.render(fmt, 1024, now)?; + } + Ok(()) + }; + let s = format!("{}", FormatDisplay(&render)); + println!("{}", s); + assert!(s.contains("/test/route/yeah")); + } + #[test] fn test_default_format() { let mut format = Format::default(); From 954fe21751991ea6e60e0f6b30c64864a837c0cc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Apr 2019 19:07:25 -0700 Subject: [PATCH 007/122] set response error body --- actix-http/src/response.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 88eb7dccb..f82945526 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -54,7 +54,8 @@ impl Response { /// Constructs an error response #[inline] pub fn from_error(error: Error) -> Response { - let mut resp = error.as_response_error().error_response(); + let resp = error.as_response_error().error_response(); + let mut resp = resp.set_body(Body::from(format!("{}", error))); resp.error = Some(error); resp } From 1e2bd68e83247ac2e76f0f1ff7357f6c128ba8b3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Apr 2019 19:55:19 -0700 Subject: [PATCH 008/122] Render error and return as response body --- actix-http/CHANGES.md | 2 ++ actix-http/src/error.rs | 9 --------- actix-http/src/response.rs | 23 +++++++++++++++++++---- actix-http/tests/test_server.rs | 4 ++-- src/middleware/logger.rs | 11 ++++------- 5 files changed, 27 insertions(+), 22 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 742ff8d3e..049f5328c 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -6,6 +6,8 @@ * Export IntoHeaderValue +* Render error and return as response body + ### Deleted * Removed PayloadBuffer diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 45bf067dc..6098421a8 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -18,8 +18,6 @@ use tokio_timer::Error as TimerError; // re-export for convinience pub use crate::cookie::ParseError as CookieParseError; - -use crate::body::Body; use crate::response::Response; /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) @@ -49,13 +47,6 @@ impl Error { pub fn as_response_error(&self) -> &ResponseError { self.cause.as_ref() } - - /// Converts error to a response instance and set error message as response body - pub fn response_with_message(self) -> Response { - let message = format!("{}", self); - let resp: Response = self.into(); - resp.set_body(Body::from(message)) - } } /// Error that can be converted to `Response` diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index f82945526..ff0ce48de 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -1,7 +1,7 @@ //! Http response use std::cell::{Ref, RefMut}; use std::io::Write; -use std::{fmt, str}; +use std::{fmt, io, str}; use bytes::{BufMut, Bytes, BytesMut}; use futures::future::{ok, FutureResult, IntoFuture}; @@ -54,10 +54,13 @@ impl Response { /// Constructs an error response #[inline] pub fn from_error(error: Error) -> Response { - let resp = error.as_response_error().error_response(); - let mut resp = resp.set_body(Body::from(format!("{}", error))); + let mut resp = error.as_response_error().error_response(); + let mut buf = BytesMut::new(); + let _ = write!(Writer(&mut buf), "{}", error); + resp.headers_mut() + .insert(header::CONTENT_TYPE, HeaderValue::from_static("text/plain")); resp.error = Some(error); - resp + resp.set_body(Body::from(buf)) } /// Convert response to response with body @@ -300,6 +303,18 @@ impl<'a> Iterator for CookieIter<'a> { } } +pub struct Writer<'a>(pub &'a mut BytesMut); + +impl<'a> io::Write for Writer<'a> { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.extend_from_slice(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + /// An HTTP response builder /// /// This type can be used to construct an instance of `Response` through a diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 85cab929c..d777d3d1a 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -867,7 +867,7 @@ fn test_h1_response_http_error_handling() { // read response let bytes = srv.load_body(response).unwrap(); - assert!(bytes.is_empty()); + assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); } #[cfg(feature = "ssl")] @@ -900,7 +900,7 @@ fn test_h2_response_http_error_handling() { // read response let bytes = srv.load_body(response).unwrap(); - assert!(bytes.is_empty()); + assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); } #[test] diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 3039b850f..f4b7517de 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -417,12 +417,7 @@ impl FormatText { )) }; } - FormatText::UrlPath => { - *self = FormatText::Str(format!( - "{}", - req.path() - )) - } + FormatText::UrlPath => *self = FormatText::Str(format!("{}", req.path())), FormatText::RequestTime => { *self = FormatText::Str(format!( "{:?}", @@ -489,7 +484,9 @@ mod tests { let req = TestRequest::with_header( header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB"), - ).uri("/test/route/yeah").to_srv_request(); + ) + .uri("/test/route/yeah") + .to_srv_request(); let now = time::now(); for unit in &mut format.0 { From dc7c3d37a175cdf8ff946daeb75c9cb80074a6ab Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Apr 2019 21:45:30 -0700 Subject: [PATCH 009/122] upgrade router --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 507be4bb4..86a168093 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ rust-tls = ["rustls", "actix-server/rust-tls"] actix-codec = "0.1.1" actix-service = "0.3.4" actix-utils = "0.3.4" -actix-router = "0.1.0" +actix-router = "0.1.1" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-alpha.1" actix-http = { version = "0.1.0-alpha.3", features=["fail"] } From bc834f6a035a05b2a4f56b0fbde96e91621bf398 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Apr 2019 10:59:34 -0700 Subject: [PATCH 010/122] remove some static contraints --- actix-http/Cargo.toml | 2 +- actix-http/src/builder.rs | 7 +++-- actix-http/src/client/connection.rs | 2 +- actix-http/src/h1/dispatcher.rs | 8 +++--- actix-http/src/h1/service.rs | 5 +--- actix-http/src/h2/dispatcher.rs | 34 ++++++++++++----------- actix-http/src/h2/service.rs | 33 ++++++++++++---------- actix-http/src/service/service.rs | 43 ++++++++++++++++------------- awc/Cargo.toml | 4 +-- awc/src/response.rs | 2 +- awc/src/ws.rs | 2 +- src/middleware/compress.rs | 10 +------ src/middleware/cors.rs | 8 ++---- src/middleware/identity.rs | 4 ++- test-server/Cargo.toml | 4 +-- 15 files changed, 84 insertions(+), 84 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 9d4e15216..0aa264e2e 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -51,7 +51,7 @@ secure-cookies = ["ring"] actix-service = "0.3.4" actix-codec = "0.1.2" actix-connect = "0.1.0" -actix-utils = "0.3.4" +actix-utils = "0.3.5" actix-server-config = "0.1.0" actix-threadpool = "0.1.0" diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 2f7466a90..74ba1aed1 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use std::marker::PhantomData; use actix_server_config::ServerConfig as SrvConfig; -use actix_service::{IntoNewService, NewService}; +use actix_service::{IntoNewService, NewService, Service}; use crate::body::MessageBody; use crate::config::{KeepAlive, ServiceConfig}; @@ -27,8 +27,7 @@ pub struct HttpServiceBuilder { impl HttpServiceBuilder where S: NewService, - S::Error: Debug + 'static, - S::Service: 'static, + S::Error: Debug, { /// Create instance of `ServiceConfigBuilder` pub fn new() -> HttpServiceBuilder { @@ -115,6 +114,7 @@ where B: MessageBody + 'static, F: IntoNewService, S::Response: Into>, + ::Future: 'static, { let cfg = ServiceConfig::new( self.keep_alive, @@ -130,6 +130,7 @@ where B: MessageBody + 'static, F: IntoNewService, S::Response: Into>, + ::Future: 'static, { let cfg = ServiceConfig::new( self.keep_alive, diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 4522dbbd3..c5d720efd 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -69,7 +69,7 @@ where } } -impl IoConnection { +impl IoConnection { pub(crate) fn new( io: ConnectionType, created: time::Instant, diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 96db08122..0f9b495b3 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -37,14 +37,14 @@ bitflags! { } /// Dispatcher for HTTP/1.1 protocol -pub struct Dispatcher + 'static, B: MessageBody> +pub struct Dispatcher, B: MessageBody> where S::Error: Debug, { inner: Option>, } -struct InnerDispatcher + 'static, B: MessageBody> +struct InnerDispatcher, B: MessageBody> where S::Error: Debug, { @@ -86,7 +86,7 @@ impl, B: MessageBody> State { impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service, S::Error: Debug, S::Response: Into>, B: MessageBody, @@ -144,7 +144,7 @@ where impl InnerDispatcher where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service, S::Error: Debug, S::Response: Into>, B: MessageBody, diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index f3301b9b2..d7ab50626 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -30,7 +30,6 @@ where S: NewService, S::Error: Debug, S::Response: Into>, - S::Service: 'static, B: MessageBody, { /// Create new `HttpService` instance with default config. @@ -63,7 +62,6 @@ where S: NewService, S::Error: Debug, S::Response: Into>, - S::Service: 'static, B: MessageBody, { type Request = Io; @@ -93,7 +91,6 @@ impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite, S: NewService, - S::Service: 'static, S::Error: Debug, S::Response: Into>, B: MessageBody, @@ -111,7 +108,7 @@ where } /// `Service` implementation for HTTP1 transport -pub struct H1ServiceHandler { +pub struct H1ServiceHandler { srv: CloneableService, cfg: ServiceConfig, _t: PhantomData<(T, P, B)>, diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 9b43be669..0ef40fc08 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -31,7 +31,7 @@ const CHUNK_SIZE: usize = 16_384; /// Dispatcher for HTTP/2 protocol pub struct Dispatcher< T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service, B: MessageBody, > { service: CloneableService, @@ -45,8 +45,9 @@ pub struct Dispatcher< impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service, S::Error: fmt::Debug, + S::Future: 'static, S::Response: Into>, B: MessageBody + 'static, { @@ -86,8 +87,9 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service, S::Error: fmt::Debug, + S::Future: 'static, S::Response: Into>, B: MessageBody + 'static, { @@ -115,7 +117,7 @@ where head.method = parts.method; head.version = parts.version; head.headers = parts.headers; - tokio_current_thread::spawn(ServiceResponse:: { + tokio_current_thread::spawn(ServiceResponse:: { state: ServiceResponseState::ServiceCall( self.service.call(req), Some(res), @@ -130,22 +132,22 @@ where } } -struct ServiceResponse { - state: ServiceResponseState, +struct ServiceResponse { + state: ServiceResponseState, config: ServiceConfig, buffer: Option, } -enum ServiceResponseState { - ServiceCall(S::Future, Option>), +enum ServiceResponseState { + ServiceCall(F, Option>), SendPayload(SendStream, ResponseBody), } -impl ServiceResponse +impl ServiceResponse where - S: Service + 'static, - S::Error: fmt::Debug, - S::Response: Into>, + F: Future, + F::Error: fmt::Debug, + F::Item: Into>, B: MessageBody + 'static, { fn prepare_response( @@ -209,11 +211,11 @@ where } } -impl Future for ServiceResponse +impl Future for ServiceResponse where - S: Service + 'static, - S::Error: fmt::Debug, - S::Response: Into>, + F: Future, + F::Error: fmt::Debug, + F::Item: Into>, B: MessageBody + 'static, { type Item = (); diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 9d9a19e24..16ccd79a5 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -32,9 +32,9 @@ pub struct H2Service { impl H2Service where S: NewService, - S::Service: 'static, - S::Error: Debug + 'static, + S::Error: Debug, S::Response: Into>, + ::Future: 'static, B: MessageBody + 'static, { /// Create new `HttpService` instance. @@ -65,9 +65,9 @@ impl NewService for H2Service where T: AsyncRead + AsyncWrite, S: NewService, - S::Service: 'static, S::Error: Debug, S::Response: Into>, + ::Future: 'static, B: MessageBody + 'static, { type Request = Io; @@ -97,9 +97,9 @@ impl Future for H2ServiceResponse where T: AsyncRead + AsyncWrite, S: NewService, - S::Service: 'static, - S::Response: Into>, S::Error: Debug, + S::Response: Into>, + ::Future: 'static, B: MessageBody + 'static, { type Item = H2ServiceHandler; @@ -115,7 +115,7 @@ where } /// `Service` implementation for http/2 transport -pub struct H2ServiceHandler { +pub struct H2ServiceHandler { srv: CloneableService, cfg: ServiceConfig, _t: PhantomData<(T, P, B)>, @@ -123,8 +123,9 @@ pub struct H2ServiceHandler { impl H2ServiceHandler where - S: Service + 'static, + S: Service, S::Error: Debug, + S::Future: 'static, S::Response: Into>, B: MessageBody + 'static, { @@ -140,8 +141,9 @@ where impl Service for H2ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service, S::Error: Debug, + S::Future: 'static, S::Response: Into>, B: MessageBody + 'static, { @@ -168,11 +170,10 @@ where } } -enum State< - T: AsyncRead + AsyncWrite, - S: Service + 'static, - B: MessageBody, -> { +enum State, B: MessageBody> +where + S::Future: 'static, +{ Incoming(Dispatcher), Handshake( Option>, @@ -184,8 +185,9 @@ enum State< pub struct H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service, S::Error: Debug, + S::Future: 'static, S::Response: Into>, B: MessageBody + 'static, { @@ -195,8 +197,9 @@ where impl Future for H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service, S::Error: Debug, + S::Future: 'static, S::Response: Into>, B: MessageBody, { diff --git a/actix-http/src/service/service.rs b/actix-http/src/service/service.rs index 50a1a6bdf..f97cc0483 100644 --- a/actix-http/src/service/service.rs +++ b/actix-http/src/service/service.rs @@ -29,9 +29,9 @@ pub struct HttpService { impl HttpService where S: NewService, - S::Service: 'static, - S::Error: Debug + 'static, + S::Error: Debug, S::Response: Into>, + ::Future: 'static, B: MessageBody + 'static, { /// Create builder for `HttpService` instance. @@ -43,9 +43,9 @@ where impl HttpService where S: NewService, - S::Service: 'static, - S::Error: Debug + 'static, + S::Error: Debug, S::Response: Into>, + ::Future: 'static, B: MessageBody + 'static, { /// Create new `HttpService` instance. @@ -74,11 +74,11 @@ where impl NewService for HttpService where - T: AsyncRead + AsyncWrite + 'static, + T: AsyncRead + AsyncWrite, S: NewService, - S::Service: 'static, S::Error: Debug, S::Response: Into>, + ::Future: 'static, B: MessageBody + 'static, { type Request = ServerIo; @@ -108,9 +108,9 @@ impl Future for HttpServiceResponse where T: AsyncRead + AsyncWrite, S: NewService, - S::Service: 'static, - S::Response: Into>, S::Error: Debug, + S::Response: Into>, + ::Future: 'static, B: MessageBody + 'static, { type Item = HttpServiceHandler; @@ -126,7 +126,7 @@ where } /// `Service` implementation for http transport -pub struct HttpServiceHandler { +pub struct HttpServiceHandler { srv: CloneableService, cfg: ServiceConfig, _t: PhantomData<(T, P, B)>, @@ -134,8 +134,9 @@ pub struct HttpServiceHandler { impl HttpServiceHandler where - S: Service + 'static, + S: Service, S::Error: Debug, + S::Future: 'static, S::Response: Into>, B: MessageBody + 'static, { @@ -150,9 +151,10 @@ where impl Service for HttpServiceHandler where - T: AsyncRead + AsyncWrite + 'static, - S: Service + 'static, + T: AsyncRead + AsyncWrite, + S: Service, S::Error: Debug, + S::Future: 'static, S::Response: Into>, B: MessageBody + 'static, { @@ -203,10 +205,11 @@ where } } -enum State + 'static, B: MessageBody> +enum State, B: MessageBody> where + S::Future: 'static, S::Error: fmt::Debug, - T: AsyncRead + AsyncWrite + 'static, + T: AsyncRead + AsyncWrite, { H1(h1::Dispatcher), H2(Dispatcher, S, B>), @@ -216,9 +219,10 @@ where pub struct HttpServiceHandlerResponse where - T: AsyncRead + AsyncWrite + 'static, - S: Service + 'static, + T: AsyncRead + AsyncWrite, + S: Service, S::Error: Debug, + S::Future: 'static, S::Response: Into>, B: MessageBody + 'static, { @@ -230,8 +234,9 @@ const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; impl Future for HttpServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service, S::Error: Debug, + S::Future: 'static, S::Response: Into>, B: MessageBody, { @@ -331,13 +336,13 @@ impl io::Write for Io { } } -impl AsyncRead for Io { +impl AsyncRead for Io { unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { self.inner.prepare_uninitialized_buffer(buf) } } -impl AsyncWrite for Io { +impl AsyncWrite for Io { fn shutdown(&mut self) -> Poll<(), io::Error> { self.inner.shutdown() } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 81d91e19f..9f4d916e4 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.3" +actix-http = "0.1.0-alpha.3" base64 = "0.10.1" bytes = "0.4" derive_more = "0.14" @@ -56,7 +56,7 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.2" actix-web = { version = "1.0.0-alpha.3", features=["ssl"] } -actix-http = { version = "0.1.0-alpa.3", features=["ssl"] } +actix-http = { version = "0.1.0-alpha.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"] } diff --git a/awc/src/response.rs b/awc/src/response.rs index 73194d673..b6d7bba65 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -282,7 +282,7 @@ where impl Future for JsonBody where T: Stream, - U: DeserializeOwned + 'static, + U: DeserializeOwned, { type Item = U; type Error = JsonPayloadError; diff --git a/awc/src/ws.rs b/awc/src/ws.rs index bbeaa061b..a28518983 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -70,7 +70,7 @@ impl WebsocketsRequest { /// Set supported websocket protocols pub fn protocols(mut self, protos: U) -> Self where - U: IntoIterator + 'static, + U: IntoIterator, V: AsRef, { let mut protos = protos diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index f74754402..ed3943711 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -70,10 +70,8 @@ impl Default for Compress { impl Transform for Compress where - P: 'static, B: MessageBody, S: Service, Response = ServiceResponse>, - S::Future: 'static, { type Request = ServiceRequest

; type Response = ServiceResponse>; @@ -97,10 +95,8 @@ pub struct CompressMiddleware { impl Service for CompressMiddleware where - P: 'static, B: MessageBody, S: Service, Response = ServiceResponse>, - S::Future: 'static, { type Request = ServiceRequest

; type Response = ServiceResponse>; @@ -134,10 +130,8 @@ where #[doc(hidden)] pub struct CompressResponse where - P: 'static, - B: MessageBody, S: Service, - S::Future: 'static, + B: MessageBody, { fut: S::Future, encoding: ContentEncoding, @@ -146,10 +140,8 @@ where impl Future for CompressResponse where - P: 'static, B: MessageBody, S: Service, Response = ServiceResponse>, - S::Future: 'static, { type Item = ServiceResponse>; type Error = S::Error; diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 920b480bb..f003ac95b 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -477,8 +477,9 @@ fn cors<'a>( impl IntoTransform for Cors where - S: Service, Response = ServiceResponse> + 'static, - P: 'static, + S: Service, Response = ServiceResponse>, + S::Future: 'static, + S::Error: 'static, B: 'static, { fn into_transform(self) -> CorsFactory { @@ -541,7 +542,6 @@ where S: Service, Response = ServiceResponse>, S::Future: 'static, S::Error: 'static, - P: 'static, B: 'static, { type Request = ServiceRequest

; @@ -683,7 +683,6 @@ where S: Service, Response = ServiceResponse>, S::Future: 'static, S::Error: 'static, - P: 'static, B: 'static, { type Request = ServiceRequest

; @@ -826,7 +825,6 @@ mod tests { + 'static, S::Future: 'static, S::Error: 'static, - P: 'static, B: 'static, { block_on( diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index 7a2c9f376..3df2f0e3b 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -202,10 +202,11 @@ impl IdentityService { impl Transform for IdentityService where - P: 'static, S: Service, Response = ServiceResponse> + 'static, S::Future: 'static, + S::Error: 'static, T: IdentityPolicy, + P: 'static, B: 'static, { type Request = ServiceRequest

; @@ -235,6 +236,7 @@ where B: 'static, S: Service, Response = ServiceResponse> + 'static, S::Future: 'static, + S::Error: 'static, T: IdentityPolicy, { type Request = ServiceRequest

; diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index f85e2b156..fefcb5184 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -55,5 +55,5 @@ 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" +actix-web = "1.0.0-alpha.3" +actix-http = "0.1.0-alpha.3" From d8bc66a18eaf4fe3dcddd85fc77f91304da4ff26 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Apr 2019 13:17:55 -0700 Subject: [PATCH 011/122] Use thread pool for response body comression --- actix-http/CHANGES.md | 3 + actix-http/src/encoding/encoder.rs | 145 ++++++++++++++++------------- src/handler.rs | 16 ++-- 3 files changed, 91 insertions(+), 73 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 049f5328c..106c57f38 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -8,10 +8,13 @@ * Render error and return as response body +* Use thread pool for response body comression + ### Deleted * Removed PayloadBuffer + ## [0.1.0-alpha.3] - 2019-04-02 ### Added diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index fcac3a427..0e02bc895 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -1,13 +1,13 @@ //! Stream encoder use std::io::{self, Write}; -use bytes::Bytes; -use futures::{Async, Poll}; - +use actix_threadpool::{run, CpuFuture}; #[cfg(feature = "brotli")] use brotli2::write::BrotliEncoder; +use bytes::Bytes; #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] use flate2::write::{GzEncoder, ZlibEncoder}; +use futures::{Async, Future, Poll}; use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::http::header::{ContentEncoding, CONTENT_ENCODING}; @@ -16,9 +16,12 @@ use crate::{Error, ResponseHead}; use super::Writer; +const INPLACE: usize = 2049; + pub struct Encoder { body: EncoderBody, encoder: Option, + fut: Option>, } impl Encoder { @@ -27,73 +30,58 @@ impl Encoder { head: &mut ResponseHead, body: ResponseBody, ) -> ResponseBody> { - let has_ce = head.headers().contains_key(CONTENT_ENCODING); - match body { - ResponseBody::Other(b) => match b { - Body::None => ResponseBody::Other(Body::None), - Body::Empty => ResponseBody::Other(Body::Empty), - Body::Bytes(buf) => { - if !(has_ce - || encoding == ContentEncoding::Identity - || encoding == ContentEncoding::Auto) - { - let mut enc = ContentEncoder::encoder(encoding).unwrap(); + let can_encode = !(head.headers().contains_key(CONTENT_ENCODING) + || head.status == StatusCode::SWITCHING_PROTOCOLS + || encoding == ContentEncoding::Identity + || encoding == ContentEncoding::Auto); - // TODO return error! - let _ = enc.write(buf.as_ref()); - let body = enc.finish().unwrap(); - update_head(encoding, head); - ResponseBody::Other(Body::Bytes(body)) + let body = match body { + ResponseBody::Other(b) => match b { + Body::None => return ResponseBody::Other(Body::None), + Body::Empty => return ResponseBody::Other(Body::Empty), + Body::Bytes(buf) => { + if can_encode { + EncoderBody::Bytes(buf) } else { - ResponseBody::Other(Body::Bytes(buf)) - } - } - Body::Message(stream) => { - if has_ce || head.status == StatusCode::SWITCHING_PROTOCOLS { - ResponseBody::Body(Encoder { - body: EncoderBody::Other(stream), - encoder: None, - }) - } else { - update_head(encoding, head); - head.no_chunking(false); - ResponseBody::Body(Encoder { - body: EncoderBody::Other(stream), - encoder: ContentEncoder::encoder(encoding), - }) + return ResponseBody::Other(Body::Bytes(buf)); } } + Body::Message(stream) => EncoderBody::BoxedStream(stream), }, - ResponseBody::Body(stream) => { - if has_ce || head.status == StatusCode::SWITCHING_PROTOCOLS { - ResponseBody::Body(Encoder { - body: EncoderBody::Body(stream), - encoder: None, - }) - } else { - update_head(encoding, head); - head.no_chunking(false); - ResponseBody::Body(Encoder { - body: EncoderBody::Body(stream), - encoder: ContentEncoder::encoder(encoding), - }) - } - } + ResponseBody::Body(stream) => EncoderBody::Stream(stream), + }; + + if can_encode { + update_head(encoding, head); + head.no_chunking(false); + ResponseBody::Body(Encoder { + body, + fut: None, + encoder: ContentEncoder::encoder(encoding), + }) + } else { + ResponseBody::Body(Encoder { + body, + fut: None, + encoder: None, + }) } } } enum EncoderBody { - Body(B), - Other(Box), + Bytes(Bytes), + Stream(B), + BoxedStream(Box), } impl MessageBody for Encoder { fn length(&self) -> BodySize { if self.encoder.is_none() { match self.body { - EncoderBody::Body(ref b) => b.length(), - EncoderBody::Other(ref b) => b.length(), + EncoderBody::Bytes(ref b) => b.length(), + EncoderBody::Stream(ref b) => b.length(), + EncoderBody::BoxedStream(ref b) => b.length(), } } else { BodySize::Stream @@ -102,20 +90,47 @@ impl MessageBody for Encoder { fn poll_next(&mut self) -> Poll, Error> { loop { + if let Some(ref mut fut) = self.fut { + let mut encoder = futures::try_ready!(fut.poll()); + let chunk = encoder.take(); + self.encoder = Some(encoder); + self.fut.take(); + if !chunk.is_empty() { + return Ok(Async::Ready(Some(chunk))); + } + } + let result = match self.body { - EncoderBody::Body(ref mut b) => b.poll_next()?, - EncoderBody::Other(ref mut b) => b.poll_next()?, + EncoderBody::Bytes(ref mut b) => { + if b.is_empty() { + Async::Ready(None) + } else { + Async::Ready(Some(std::mem::replace(b, Bytes::new()))) + } + } + EncoderBody::Stream(ref mut b) => b.poll_next()?, + EncoderBody::BoxedStream(ref mut b) => b.poll_next()?, }; match result { Async::NotReady => return Ok(Async::NotReady), Async::Ready(Some(chunk)) => { - if let Some(ref mut encoder) = self.encoder { - if encoder.write(&chunk)? { - return Ok(Async::Ready(Some(encoder.take()))); + if let Some(mut encoder) = self.encoder.take() { + if chunk.len() < INPLACE { + encoder.write(&chunk)?; + let chunk = encoder.take(); + self.encoder = Some(encoder); + if !chunk.is_empty() { + return Ok(Async::Ready(Some(chunk))); + } + } else { + self.fut = Some(run(move || { + encoder.write(&chunk)?; + Ok(encoder) + })); + continue; } - } else { - return Ok(Async::Ready(Some(chunk))); } + return Ok(Async::Ready(Some(chunk))); } Async::Ready(None) => { if let Some(encoder) = self.encoder.take() { @@ -203,11 +218,11 @@ impl ContentEncoder { } } - fn write(&mut self, data: &[u8]) -> Result { + fn write(&mut self, data: &[u8]) -> Result<(), io::Error> { match *self { #[cfg(feature = "brotli")] ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) { - Ok(_) => Ok(!encoder.get_ref().buf.is_empty()), + Ok(_) => Ok(()), Err(err) => { trace!("Error decoding br encoding: {}", err); Err(err) @@ -215,7 +230,7 @@ impl ContentEncoder { }, #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) { - Ok(_) => Ok(!encoder.get_ref().buf.is_empty()), + Ok(_) => Ok(()), Err(err) => { trace!("Error decoding gzip encoding: {}", err); Err(err) @@ -223,7 +238,7 @@ impl ContentEncoder { }, #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) { - Ok(_) => Ok(!encoder.get_ref().buf.is_empty()), + Ok(_) => Ok(()), Err(err) => { trace!("Error decoding deflate encoding: {}", err); Err(err) diff --git a/src/handler.rs b/src/handler.rs index 4ff3193c8..a11a5d0b6 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -22,8 +22,8 @@ where impl Factory<(), R> for F where - F: Fn() -> R + Clone + 'static, - R: Responder + 'static, + F: Fn() -> R + Clone, + R: Responder, { fn call(&self, _: ()) -> R { (self)() @@ -55,7 +55,7 @@ where impl NewService for Handler where F: Factory, - R: Responder + 'static, + R: Responder, { type Request = (T, HttpRequest); type Response = ServiceResponse; @@ -76,7 +76,7 @@ where pub struct HandlerService where F: Factory, - R: Responder + 'static, + R: Responder, { hnd: F, _t: PhantomData<(T, R)>, @@ -85,7 +85,7 @@ where impl Service for HandlerService where F: Factory, - R: Responder + 'static, + R: Responder, { type Request = (T, HttpRequest); type Response = ServiceResponse; @@ -355,8 +355,8 @@ impl> Future for ExtractResponse { /// FromRequest trait impl for tuples macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => { impl Factory<($($T,)+), Res> for Func - where Func: Fn($($T,)+) -> Res + Clone + 'static, - Res: Responder + 'static, + where Func: Fn($($T,)+) -> Res + Clone, + Res: Responder, { fn call(&self, param: ($($T,)+)) -> Res { (self)($(param.$n,)+) @@ -365,7 +365,7 @@ macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => { impl AsyncFactory<($($T,)+), Res> for Func where Func: Fn($($T,)+) -> Res + Clone + 'static, - Res: IntoFuture + 'static, + Res: IntoFuture, Res::Item: Into, Res::Error: Into, { From 1f5c0f50f9092f3b19f4c2b4c3f25111e0861e55 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Apr 2019 13:23:38 -0700 Subject: [PATCH 012/122] Add minimal std::error::Error impl for Error --- actix-http/CHANGES.md | 4 ++++ actix-http/src/error.rs | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 106c57f38..25439604f 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,6 +2,10 @@ ## [0.1.0-alpha.4] - 2019-04-xx +### Added + +* Add minimal `std::error::Error` impl for `Error` + ### Changed * Export IntoHeaderValue diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 6098421a8..3e0076b49 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -71,6 +71,20 @@ impl fmt::Debug for Error { } } +impl std::error::Error for Error { + fn description(&self) -> &str { + "actix-http::Error" + } + + fn cause(&self) -> Option<&dyn std::error::Error> { + None + } + + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +} + /// Convert `Error` to a `Response` instance impl From for Response { fn from(err: Error) -> Self { From 9c205f9f1df451a5a6039f5105307117223f57d4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Apr 2019 14:00:56 -0700 Subject: [PATCH 013/122] update tests for content-encoding --- actix-files/src/lib.rs | 20 +++++++++++++++++++- actix-files/src/named.rs | 7 +++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 8404ab319..e2fa06e12 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -979,14 +979,32 @@ mod tests { .to_request(); let res = test::call_success(&mut srv, request); assert_eq!(res.status(), StatusCode::OK); + assert!(!res.headers().contains_key(header::CONTENT_ENCODING)); + } + #[test] + fn test_named_file_content_encoding_gzip() { + let mut srv = test::init_service(App::new().enable_encoding().service( + web::resource("/").to(|| { + NamedFile::open("Cargo.toml") + .unwrap() + .set_content_encoding(header::ContentEncoding::Gzip) + }), + )); + + let request = TestRequest::get() + .uri("/") + .header(header::ACCEPT_ENCODING, "gzip") + .to_request(); + let res = test::call_success(&mut srv, request); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( res.headers() .get(header::CONTENT_ENCODING) .unwrap() .to_str() .unwrap(), - "identity" + "gzip" ); } diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 4ee1a3caa..717b058cf 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -296,10 +296,9 @@ impl Responder for NamedFile { header::CONTENT_DISPOSITION, self.content_disposition.to_string(), ); - // TODO blocking by compressing - // if let Some(current_encoding) = self.encoding { - // resp.content_encoding(current_encoding); - // } + if let Some(current_encoding) = self.encoding { + resp.encoding(current_encoding); + } let reader = ChunkedReadFile { size: self.md.len(), offset: 0, From 309c480782ab028f3e49d16817a1c91a1d45a059 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Apr 2019 15:03:40 -0700 Subject: [PATCH 014/122] encoder sent uncompressed data before compressed --- actix-http/src/encoding/encoder.rs | 12 ++++++-- src/lib.rs | 1 - tests/test_server.rs | 45 +++++++++++++++--------------- 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 0e02bc895..50e9d068b 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -19,6 +19,7 @@ use super::Writer; const INPLACE: usize = 2049; pub struct Encoder { + eof: bool, body: EncoderBody, encoder: Option, fut: Option>, @@ -56,12 +57,14 @@ impl Encoder { head.no_chunking(false); ResponseBody::Body(Encoder { body, + eof: false, fut: None, encoder: ContentEncoder::encoder(encoding), }) } else { ResponseBody::Body(Encoder { body, + eof: false, fut: None, encoder: None, }) @@ -90,6 +93,10 @@ impl MessageBody for Encoder { fn poll_next(&mut self) -> Poll, Error> { loop { + if self.eof { + return Ok(Async::Ready(None)); + } + if let Some(ref mut fut) = self.fut { let mut encoder = futures::try_ready!(fut.poll()); let chunk = encoder.take(); @@ -127,10 +134,10 @@ impl MessageBody for Encoder { encoder.write(&chunk)?; Ok(encoder) })); - continue; } + } else { + return Ok(Async::Ready(Some(chunk))); } - return Ok(Async::Ready(Some(chunk))); } Async::Ready(None) => { if let Some(encoder) = self.encoder.take() { @@ -138,6 +145,7 @@ impl MessageBody for Encoder { if chunk.is_empty() { return Ok(Async::Ready(None)); } else { + self.eof = true; return Ok(Async::Ready(Some(chunk))); } } else { diff --git a/src/lib.rs b/src/lib.rs index ca4968833..d1efd39c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -143,7 +143,6 @@ pub mod dev { }; pub use crate::types::form::UrlEncoded; pub use crate::types::json::JsonBody; - pub use crate::types::payload::HttpMessageBody; pub use crate::types::readlines::Readlines; pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody}; diff --git a/tests/test_server.rs b/tests/test_server.rs index 3c5d09066..7c91d4fed 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -16,7 +16,7 @@ use flate2::Compression; use futures::stream::once; use rand::{distributions::Alphanumeric, Rng}; -use actix_web::{dev::HttpMessageBody, http, test, web, App, HttpResponse, HttpServer}; +use actix_web::{http, test, web, App, HttpResponse, HttpServer}; #[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] use actix_web::middleware::encoding; @@ -58,7 +58,7 @@ fn test_body() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -77,7 +77,7 @@ fn test_body_gzip() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); // decode let mut e = GzDecoder::new(&bytes[..]); @@ -115,7 +115,7 @@ fn test_body_encoding_override() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); // decode let mut e = ZlibDecoder::new(Vec::new()); @@ -134,7 +134,7 @@ fn test_body_encoding_override() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); // decode let mut e = ZlibDecoder::new(Vec::new()); @@ -165,7 +165,7 @@ fn test_body_gzip_large() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); // decode let mut e = GzDecoder::new(&bytes[..]); @@ -199,7 +199,7 @@ fn test_body_gzip_large_random() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); // decode let mut e = GzDecoder::new(&bytes[..]); @@ -232,7 +232,7 @@ fn test_body_chunked_implicit() { ); // read response - let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); // decode let mut e = GzDecoder::new(&bytes[..]); @@ -267,7 +267,7 @@ fn test_body_br_streaming() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); // decode br let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); @@ -293,7 +293,7 @@ fn test_head_binary() { } // read response - let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert!(bytes.is_empty()); } @@ -315,7 +315,7 @@ fn test_no_chunking() { assert!(!response.headers().contains_key(TRANSFER_ENCODING)); // read response - let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -337,9 +337,8 @@ fn test_body_deflate() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); - // decode deflate let mut e = ZlibDecoder::new(Vec::new()); e.write_all(bytes.as_ref()).unwrap(); let dec = e.finish().unwrap(); @@ -371,7 +370,7 @@ fn test_body_brotli() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); // decode brotli let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); @@ -405,7 +404,7 @@ fn test_encoding() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -434,7 +433,7 @@ fn test_gzip_encoding() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -464,7 +463,7 @@ fn test_gzip_encoding_large() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from(data)); } @@ -498,7 +497,7 @@ fn test_reading_gzip_encoding_large_random() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes.len(), data.len()); assert_eq!(bytes, Bytes::from(data)); } @@ -528,7 +527,7 @@ fn test_reading_deflate_encoding() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -558,7 +557,7 @@ fn test_reading_deflate_encoding_large() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from(data)); } @@ -592,7 +591,7 @@ fn test_reading_deflate_encoding_large_random() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes.len(), data.len()); assert_eq!(bytes, Bytes::from(data)); } @@ -622,7 +621,7 @@ fn test_brotli_encoding() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -652,7 +651,7 @@ fn test_brotli_encoding_large() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from(data)); } From a655bdac52165b5bb8c5f3ed1503a7c1fe218fb2 Mon Sep 17 00:00:00 2001 From: nasa Date: Fri, 5 Apr 2019 18:34:24 +0900 Subject: [PATCH 015/122] Fix clippy warning (#755) --- actix-http/src/body.rs | 2 +- actix-http/src/cookie/secure/key.rs | 4 ++-- actix-http/src/cookie/secure/private.rs | 2 +- actix-http/src/cookie/secure/signed.rs | 2 +- awc/src/request.rs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index 85717ba85..0d015b2e9 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -359,7 +359,7 @@ where } fn poll_next(&mut self) -> Poll, Error> { - self.stream.poll().map_err(|e| e.into()) + self.stream.poll().map_err(std::convert::Into::into) } } diff --git a/actix-http/src/cookie/secure/key.rs b/actix-http/src/cookie/secure/key.rs index 3b8a0af78..4e74f6e78 100644 --- a/actix-http/src/cookie/secure/key.rs +++ b/actix-http/src/cookie/secure/key.rs @@ -68,8 +68,8 @@ impl Key { encryption_key.copy_from_slice(&both_keys[SIGNED_KEY_LEN..]); Key { - signing_key: signing_key, - encryption_key: encryption_key, + signing_key, + encryption_key, } } diff --git a/actix-http/src/cookie/secure/private.rs b/actix-http/src/cookie/secure/private.rs index 323687303..e59743767 100644 --- a/actix-http/src/cookie/secure/private.rs +++ b/actix-http/src/cookie/secure/private.rs @@ -37,7 +37,7 @@ impl<'a> PrivateJar<'a> { let mut key_array = [0u8; KEY_LEN]; key_array.copy_from_slice(key.encryption()); PrivateJar { - parent: parent, + parent, key: key_array, } } diff --git a/actix-http/src/cookie/secure/signed.rs b/actix-http/src/cookie/secure/signed.rs index 5a4ffb76c..1b1799cf4 100644 --- a/actix-http/src/cookie/secure/signed.rs +++ b/actix-http/src/cookie/secure/signed.rs @@ -31,7 +31,7 @@ impl<'a> SignedJar<'a> { #[doc(hidden)] pub fn new(parent: &'a mut CookieJar, key: &Key) -> SignedJar<'a> { SignedJar { - parent: parent, + parent, key: SigningKey::new(HMAC_DIGEST, key.signing()), } } diff --git a/awc/src/request.rs b/awc/src/request.rs index b96b39e2f..32ab7d78f 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -414,7 +414,7 @@ impl ClientRequest { } // user agent - if !self.head.headers.contains_key(&header::USER_AGENT) { + 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"))), From 162cd3eecd332d15e2cb519ac80d1e3d9ce7106a Mon Sep 17 00:00:00 2001 From: Darin Date: Fri, 5 Apr 2019 10:37:00 -0400 Subject: [PATCH 016/122] added Connector to actix-web::client namespace (#756) --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index d1efd39c3..a668b83be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -189,5 +189,6 @@ pub mod client { pub use awc::error::{ ConnectError, InvalidUrl, PayloadError, SendRequestError, WsClientError, }; - pub use awc::{test, Client, ClientBuilder, ClientRequest, ClientResponse}; + pub use awc::{test, Client, ClientBuilder, ClientRequest, ClientResponse, + Connector}; } From 0d4a8e1b1c771704a3483fddb80e7e9b250f15a5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Apr 2019 10:35:14 -0700 Subject: [PATCH 017/122] update actix-connect --- actix-http/Cargo.toml | 2 +- actix-http/src/client/connector.rs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 0aa264e2e..315dcd5ca 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -50,7 +50,7 @@ secure-cookies = ["ring"] [dependencies] actix-service = "0.3.4" actix-codec = "0.1.2" -actix-connect = "0.1.0" +actix-connect = "0.1.2" actix-utils = "0.3.5" actix-server-config = "0.1.0" actix-threadpool = "0.1.0" diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 804756ced..05410a4ff 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -3,9 +3,7 @@ use std::marker::PhantomData; use std::time::Duration; use actix_codec::{AsyncRead, AsyncWrite}; -use actix_connect::{ - default_connector, Connect as TcpConnect, Connection as TcpConnection, -}; +use actix_connect::{default_connector, Connect as TcpConnect, TcpConnection}; use actix_service::{apply_fn, Service, ServiceExt}; use actix_utils::timeout::{TimeoutError, TimeoutService}; use http::Uri; From f89321fd01b5d8e1569e410fa59f4d3189ee2f66 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Apr 2019 10:50:11 -0700 Subject: [PATCH 018/122] fix import --- actix-http/src/client/connector.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 05410a4ff..804756ced 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -3,7 +3,9 @@ use std::marker::PhantomData; use std::time::Duration; use actix_codec::{AsyncRead, AsyncWrite}; -use actix_connect::{default_connector, Connect as TcpConnect, TcpConnection}; +use actix_connect::{ + default_connector, Connect as TcpConnect, Connection as TcpConnection, +}; use actix_service::{apply_fn, Service, ServiceExt}; use actix_utils::timeout::{TimeoutError, TimeoutService}; use http::Uri; From b6dacaa23acc4c949e6fba51b3d5fbaecf08a0ec Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Apr 2019 11:29:42 -0700 Subject: [PATCH 019/122] remove SendError and SendResponse services --- actix-http/examples/framed_hello.rs | 28 --- actix-http/src/lib.rs | 2 +- actix-http/src/response.rs | 4 +- actix-http/src/{service => }/service.rs | 0 actix-http/src/service/mod.rs | 5 - actix-http/src/service/senderror.rs | 241 ------------------------ awc/tests/test_ws.rs | 34 ++-- src/lib.rs | 5 +- 8 files changed, 27 insertions(+), 292 deletions(-) delete mode 100644 actix-http/examples/framed_hello.rs rename actix-http/src/{service => }/service.rs (100%) delete mode 100644 actix-http/src/service/mod.rs delete mode 100644 actix-http/src/service/senderror.rs diff --git a/actix-http/examples/framed_hello.rs b/actix-http/examples/framed_hello.rs deleted file mode 100644 index 7d4c13d34..000000000 --- a/actix-http/examples/framed_hello.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::{env, io}; - -use actix_codec::Framed; -use actix_http::{h1, Response, SendResponse, ServiceConfig}; -use actix_server::{Io, Server}; -use actix_service::{fn_service, NewService}; -use actix_utils::framed::IntoFramed; -use actix_utils::stream::TakeItem; -use futures::Future; -use tokio_tcp::TcpStream; - -fn main() -> io::Result<()> { - env::set_var("RUST_LOG", "framed_hello=info"); - env_logger::init(); - - Server::build() - .bind("framed_hello", "127.0.0.1:8080", || { - fn_service(|io: Io| Ok(io.into_parts().0)) - .and_then(IntoFramed::new(|| h1::Codec::new(ServiceConfig::default()))) - .and_then(TakeItem::new().map_err(|_| ())) - .and_then(|(_req, _framed): (_, Framed<_, _>)| { - SendResponse::send(_framed, Response::Ok().body("Hello world!")) - .map_err(|_| ()) - .map(|_| ()) - }) - })? - .run() -} diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 088125ae0..ed3669e85 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -37,7 +37,7 @@ pub use self::message::{Message, RequestHead, ResponseHead}; pub use self::payload::{Payload, PayloadStream}; pub use self::request::Request; pub use self::response::{Response, ResponseBuilder}; -pub use self::service::{HttpService, SendError, SendResponse}; +pub use self::service::HttpService; pub mod http { //! Various HTTP related types diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index ff0ce48de..c3fed133d 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -208,7 +208,7 @@ impl Response { } /// Set a body - pub(crate) fn set_body(self, body: B2) -> Response { + pub fn set_body(self, body: B2) -> Response { Response { head: self.head, body: ResponseBody::Body(body), @@ -217,7 +217,7 @@ impl Response { } /// Drop request's body - pub(crate) fn drop_body(self) -> Response<()> { + pub fn drop_body(self) -> Response<()> { Response { head: self.head, body: ResponseBody::Body(()), diff --git a/actix-http/src/service/service.rs b/actix-http/src/service.rs similarity index 100% rename from actix-http/src/service/service.rs rename to actix-http/src/service.rs diff --git a/actix-http/src/service/mod.rs b/actix-http/src/service/mod.rs deleted file mode 100644 index 25e95bf60..000000000 --- a/actix-http/src/service/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod senderror; -mod service; - -pub use self::senderror::{SendError, SendResponse}; -pub use self::service::HttpService; diff --git a/actix-http/src/service/senderror.rs b/actix-http/src/service/senderror.rs deleted file mode 100644 index 03fe5976a..000000000 --- a/actix-http/src/service/senderror.rs +++ /dev/null @@ -1,241 +0,0 @@ -use std::marker::PhantomData; - -use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_service::{NewService, Service}; -use futures::future::{ok, Either, FutureResult}; -use futures::{Async, Future, Poll, Sink}; - -use crate::body::{BodySize, MessageBody, ResponseBody}; -use crate::error::{Error, ResponseError}; -use crate::h1::{Codec, Message}; -use crate::response::Response; - -pub struct SendError(PhantomData<(T, R, E)>); - -impl Default for SendError -where - T: AsyncRead + AsyncWrite, - E: ResponseError, -{ - fn default() -> Self { - SendError(PhantomData) - } -} - -impl NewService for SendError -where - T: AsyncRead + AsyncWrite, - E: ResponseError, -{ - type Request = Result)>; - type Response = R; - type Error = (E, Framed); - type InitError = (); - type Service = SendError; - type Future = FutureResult; - - fn new_service(&self, _: &()) -> Self::Future { - ok(SendError(PhantomData)) - } -} - -impl Service for SendError -where - T: AsyncRead + AsyncWrite, - E: ResponseError, -{ - type Request = Result)>; - type Response = R; - type Error = (E, Framed); - type Future = Either)>, SendErrorFut>; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, req: Result)>) -> Self::Future { - match req { - Ok(r) => Either::A(ok(r)), - Err((e, framed)) => { - let res = e.error_response().set_body(format!("{}", e)); - let (res, _body) = res.replace_body(()); - Either::B(SendErrorFut { - framed: Some(framed), - res: Some((res, BodySize::Empty).into()), - err: Some(e), - _t: PhantomData, - }) - } - } - } -} - -pub struct SendErrorFut { - res: Option, BodySize)>>, - framed: Option>, - err: Option, - _t: PhantomData, -} - -impl Future for SendErrorFut -where - E: ResponseError, - T: AsyncRead + AsyncWrite, -{ - type Item = R; - type Error = (E, Framed); - - fn poll(&mut self) -> Poll { - if let Some(res) = self.res.take() { - if self.framed.as_mut().unwrap().force_send(res).is_err() { - return Err((self.err.take().unwrap(), self.framed.take().unwrap())); - } - } - match self.framed.as_mut().unwrap().poll_complete() { - Ok(Async::Ready(_)) => { - Err((self.err.take().unwrap(), self.framed.take().unwrap())) - } - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(_) => Err((self.err.take().unwrap(), self.framed.take().unwrap())), - } - } -} - -pub struct SendResponse(PhantomData<(T, B)>); - -impl Default for SendResponse { - fn default() -> Self { - SendResponse(PhantomData) - } -} - -impl SendResponse -where - T: AsyncRead + AsyncWrite, - B: MessageBody, -{ - pub fn send( - framed: Framed, - res: Response, - ) -> impl Future, Error = Error> { - // extract body from response - let (res, body) = res.replace_body(()); - - // write response - SendResponseFut { - res: Some(Message::Item((res, body.length()))), - body: Some(body), - framed: Some(framed), - } - } -} - -impl NewService for SendResponse -where - T: AsyncRead + AsyncWrite, - B: MessageBody, -{ - type Request = (Response, Framed); - type Response = Framed; - type Error = Error; - type InitError = (); - type Service = SendResponse; - type Future = FutureResult; - - fn new_service(&self, _: &()) -> Self::Future { - ok(SendResponse(PhantomData)) - } -} - -impl Service for SendResponse -where - T: AsyncRead + AsyncWrite, - B: MessageBody, -{ - type Request = (Response, Framed); - type Response = Framed; - type Error = Error; - type Future = SendResponseFut; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, (res, framed): (Response, Framed)) -> Self::Future { - let (res, body) = res.replace_body(()); - SendResponseFut { - res: Some(Message::Item((res, body.length()))), - body: Some(body), - framed: Some(framed), - } - } -} - -pub struct SendResponseFut { - res: Option, BodySize)>>, - body: Option>, - framed: Option>, -} - -impl Future for SendResponseFut -where - T: AsyncRead + AsyncWrite, - B: MessageBody, -{ - type Item = Framed; - type Error = Error; - - fn poll(&mut self) -> Poll { - loop { - let mut body_ready = self.body.is_some(); - let framed = self.framed.as_mut().unwrap(); - - // send body - if self.res.is_none() && self.body.is_some() { - while body_ready && self.body.is_some() && !framed.is_write_buf_full() { - match self.body.as_mut().unwrap().poll_next()? { - Async::Ready(item) => { - // body is done - if item.is_none() { - let _ = self.body.take(); - } - framed.force_send(Message::Chunk(item))?; - } - Async::NotReady => body_ready = false, - } - } - } - - // flush write buffer - if !framed.is_write_buf_empty() { - match framed.poll_complete()? { - Async::Ready(_) => { - if body_ready { - continue; - } else { - return Ok(Async::NotReady); - } - } - Async::NotReady => return Ok(Async::NotReady), - } - } - - // send response - if let Some(res) = self.res.take() { - framed.force_send(res)?; - continue; - } - - if self.body.is_some() { - if body_ready { - continue; - } else { - return Ok(Async::NotReady); - } - } else { - break; - } - } - Ok(Async::Ready(self.framed.take().unwrap())) - } -} diff --git a/awc/tests/test_ws.rs b/awc/tests/test_ws.rs index d8942fb17..04a6a110a 100644 --- a/awc/tests/test_ws.rs +++ b/awc/tests/test_ws.rs @@ -11,7 +11,7 @@ use futures::future::{ok, Either}; use futures::{Future, Sink, Stream}; use tokio_tcp::TcpStream; -use actix_http::{h1, ws, ResponseError, SendResponse, ServiceConfig}; +use actix_http::{body::BodySize, h1, ws, ResponseError, ServiceConfig}; fn ws_service(req: ws::Frame) -> impl Future { match req { @@ -46,26 +46,34 @@ fn test_simple() { match ws::verify_handshake(&req) { Err(e) => { // validation failed + let res = e.error_response(); Either::A( - SendResponse::send(framed, e.error_response()) + framed + .send(h1::Message::Item(( + res.drop_body(), + BodySize::Empty, + ))) .map_err(|_| ()) .map(|_| ()), ) } Ok(_) => { + let res = ws::handshake_response(&req).finish(); Either::B( // send handshake response - SendResponse::send( - framed, - ws::handshake_response(&req).finish(), - ) - .map_err(|_| ()) - .and_then(|framed| { - // start websocket service - let framed = framed.into_framed(ws::Codec::new()); - ws::Transport::with(framed, ws_service) - .map_err(|_| ()) - }), + framed + .send(h1::Message::Item(( + res.drop_body(), + BodySize::None, + ))) + .map_err(|_| ()) + .and_then(|framed| { + // start websocket service + let framed = + framed.into_framed(ws::Codec::new()); + ws::Transport::with(framed, ws_service) + .map_err(|_| ()) + }), ) } } diff --git a/src/lib.rs b/src/lib.rs index a668b83be..39c054bc4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -189,6 +189,7 @@ pub mod client { pub use awc::error::{ ConnectError, InvalidUrl, PayloadError, SendRequestError, WsClientError, }; - pub use awc::{test, Client, ClientBuilder, ClientRequest, ClientResponse, - Connector}; + pub use awc::{ + test, Client, ClientBuilder, ClientRequest, ClientResponse, Connector, + }; } From 18593d847613bdcfeae51fe93115a7aea5430648 Mon Sep 17 00:00:00 2001 From: Darin Date: Fri, 5 Apr 2019 14:34:27 -0400 Subject: [PATCH 020/122] updated Connector docs and renamed service() to finish() (#757) * added Connector to actix-web::client namespace * updated Connector, renaming service() to finish() and adding docs * added doc for finish method on Connector --- actix-http/src/client/connector.rs | 20 ++++++++++++++++---- awc/src/builder.rs | 2 +- awc/src/lib.rs | 2 +- src/lib.rs | 5 ++--- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 804756ced..8a0968f52 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -21,8 +21,18 @@ use openssl::ssl::SslConnector; #[cfg(not(feature = "ssl"))] type SslConnector = (); -/// Http client connector builde instance. -/// `Connector` type uses builder-like pattern for connector service construction. +/// Manages http client network connectivity +/// The `Connector` type uses a builder-like combinator pattern for service +/// construction that finishes by calling the `.finish()` method. +/// +/// ```rust +/// use actix-web::client::Connector; +/// use time::Duration; +/// +/// let connector = Connector::new() +/// .timeout(Duration::from_secs(5)) +/// .finish(); +/// ``` pub struct Connector { connector: T, timeout: Duration, @@ -163,8 +173,10 @@ where self } - /// Finish configuration process and create connector service. - pub fn service( + /// Finish configuration process and create connector service. + /// The Connector builder always concludes by calling `finish()` last in + /// its combinator chain. + pub fn finish( self, ) -> impl Service + Clone { diff --git a/awc/src/builder.rs b/awc/src/builder.rs index dcea55952..ddefed439 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -31,7 +31,7 @@ impl ClientBuilder { headers: HeaderMap::new(), timeout: Some(Duration::from_secs(5)), connector: RefCell::new(Box::new(ConnectorWrapper( - Connector::new().service(), + Connector::new().finish(), ))), }, } diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 5f9adb463..bd08c3c31 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -78,7 +78,7 @@ impl Default for Client { fn default() -> Self { Client(Rc::new(ClientConfig { connector: RefCell::new(Box::new(ConnectorWrapper( - Connector::new().service(), + Connector::new().finish(), ))), headers: HeaderMap::new(), timeout: Some(Duration::from_secs(5)), diff --git a/src/lib.rs b/src/lib.rs index 39c054bc4..a668b83be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -189,7 +189,6 @@ pub mod client { pub use awc::error::{ ConnectError, InvalidUrl, PayloadError, SendRequestError, WsClientError, }; - pub use awc::{ - test, Client, ClientBuilder, ClientRequest, ClientResponse, Connector, - }; + pub use awc::{test, Client, ClientBuilder, ClientRequest, ClientResponse, + Connector}; } From 02fcaca3da0eeaf4a1dad783c1fde487cd637fc7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Apr 2019 11:36:26 -0700 Subject: [PATCH 021/122] add backward compatibility --- actix-http/src/client/connector.rs | 13 +++++++++++-- src/lib.rs | 5 +++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 8a0968f52..f476ad5fb 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -173,8 +173,8 @@ where self } - /// Finish configuration process and create connector service. - /// The Connector builder always concludes by calling `finish()` last in + /// Finish configuration process and create connector service. + /// The Connector builder always concludes by calling `finish()` last in /// its combinator chain. pub fn finish( self, @@ -265,6 +265,15 @@ where } } } + + #[doc(hidden)] + #[deprecated(since = "0.1.0-alpha4", note = "please use `.finish()` method")] + pub fn service( + self, + ) -> impl Service + Clone + { + self.finish() + } } #[cfg(not(feature = "ssl"))] diff --git a/src/lib.rs b/src/lib.rs index a668b83be..39c054bc4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -189,6 +189,7 @@ pub mod client { pub use awc::error::{ ConnectError, InvalidUrl, PayloadError, SendRequestError, WsClientError, }; - pub use awc::{test, Client, ClientBuilder, ClientRequest, ClientResponse, - Connector}; + pub use awc::{ + test, Client, ClientBuilder, ClientRequest, ClientResponse, Connector, + }; } From fbedaec661094d283dde01dc176de8a7909e9695 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Apr 2019 16:46:44 -0700 Subject: [PATCH 022/122] add expect: 100-continue support #141 --- actix-http/src/builder.rs | 39 ++++-- actix-http/src/client/connector.rs | 10 +- actix-http/src/error.rs | 19 ++- actix-http/src/h1/codec.rs | 54 +++++---- actix-http/src/h1/decoder.rs | 18 +++ actix-http/src/h1/dispatcher.rs | 124 +++++++++++++++---- actix-http/src/h1/expect.rs | 36 ++++++ actix-http/src/h1/mod.rs | 2 + actix-http/src/h1/service.rs | 150 ++++++++++++++++++----- actix-http/src/h2/dispatcher.rs | 8 +- actix-http/src/h2/service.rs | 17 +-- actix-http/src/message.rs | 14 ++- actix-http/src/response.rs | 12 ++ actix-http/src/service.rs | 186 ++++++++++++++++++++++------- actix-http/tests/test_client.rs | 2 + src/server.rs | 11 +- test-server/src/lib.rs | 4 +- tests/test_httpserver.rs | 4 +- 18 files changed, 554 insertions(+), 156 deletions(-) create mode 100644 actix-http/src/h1/expect.rs diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 74ba1aed1..2a8a8360f 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -1,4 +1,4 @@ -use std::fmt::Debug; +use std::fmt; use std::marker::PhantomData; use actix_server_config::ServerConfig as SrvConfig; @@ -6,39 +6,52 @@ use actix_service::{IntoNewService, NewService, Service}; use crate::body::MessageBody; use crate::config::{KeepAlive, ServiceConfig}; +use crate::error::Error; +use crate::h1::{ExpectHandler, H1Service}; +use crate::h2::H2Service; use crate::request::Request; use crate::response::Response; - -use crate::h1::H1Service; -use crate::h2::H2Service; use crate::service::HttpService; /// A http service builder /// /// This type can be used to construct an instance of `http service` through a /// builder-like pattern. -pub struct HttpServiceBuilder { +pub struct HttpServiceBuilder { keep_alive: KeepAlive, client_timeout: u64, client_disconnect: u64, + expect: X, _t: PhantomData<(T, S)>, } -impl HttpServiceBuilder +impl HttpServiceBuilder where S: NewService, - S::Error: Debug, + S::Error: Into, + S::InitError: fmt::Debug, { /// Create instance of `ServiceConfigBuilder` - pub fn new() -> HttpServiceBuilder { + pub fn new() -> Self { HttpServiceBuilder { keep_alive: KeepAlive::Timeout(5), client_timeout: 5000, client_disconnect: 0, + expect: ExpectHandler, _t: PhantomData, } } +} +impl HttpServiceBuilder +where + S: NewService, + S::Error: Into, + S::InitError: fmt::Debug, + X: NewService, + X::Error: Into, + X::InitError: fmt::Debug, +{ /// Set server keep-alive setting. /// /// By default keep alive is set to a 5 seconds. @@ -94,10 +107,12 @@ where // } /// Finish service configuration and create *http service* for HTTP/1 protocol. - pub fn h1(self, service: F) -> H1Service + pub fn h1(self, service: F) -> H1Service where B: MessageBody + 'static, F: IntoNewService, + S::Error: Into, + S::InitError: fmt::Debug, S::Response: Into>, { let cfg = ServiceConfig::new( @@ -105,7 +120,7 @@ where self.client_timeout, self.client_disconnect, ); - H1Service::with_config(cfg, service.into_new_service()) + H1Service::with_config(cfg, service.into_new_service()).expect(self.expect) } /// Finish service configuration and create *http service* for HTTP/2 protocol. @@ -113,6 +128,8 @@ where where B: MessageBody + 'static, F: IntoNewService, + S::Error: Into, + S::InitError: fmt::Debug, S::Response: Into>, ::Future: 'static, { @@ -129,6 +146,8 @@ where where B: MessageBody + 'static, F: IntoNewService, + S::Error: Into, + S::InitError: fmt::Debug, S::Response: Into>, ::Future: 'static, { diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index f476ad5fb..1c9a3aab0 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -25,13 +25,13 @@ type SslConnector = (); /// The `Connector` type uses a builder-like combinator pattern for service /// construction that finishes by calling the `.finish()` method. /// -/// ```rust -/// use actix-web::client::Connector; -/// use time::Duration; +/// ```rust,ignore +/// use std::time::Duration; +/// use actix_http::client::Connector; /// /// let connector = Connector::new() -/// .timeout(Duration::from_secs(5)) -/// .finish(); +/// .timeout(Duration::from_secs(5)) +/// .finish(); /// ``` pub struct Connector { connector: T, diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 3e0076b49..fc37d3243 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -71,6 +71,12 @@ impl fmt::Debug for Error { } } +impl From<()> for Error { + fn from(_: ()) -> Self { + Error::from(UnitError) + } +} + impl std::error::Error for Error { fn description(&self) -> &str { "actix-http::Error" @@ -111,6 +117,13 @@ impl ResponseError for TimeoutError { } } +#[derive(Debug, Display)] +#[display(fmt = "UnknownError")] +struct UnitError; + +/// `InternalServerError` for `JsonError` +impl ResponseError for UnitError {} + /// `InternalServerError` for `JsonError` impl ResponseError for JsonError {} @@ -120,6 +133,10 @@ impl ResponseError for FormError {} /// `InternalServerError` for `TimerError` impl ResponseError for TimerError {} +#[cfg(feature = "ssl")] +/// `InternalServerError` for `SslError` +impl ResponseError for openssl::ssl::Error {} + /// Return `BAD_REQUEST` for `de::value::Error` impl ResponseError for DeError { fn error_response(&self) -> Response { @@ -331,7 +348,7 @@ impl ResponseError for crate::cookie::ParseError { /// A set of errors that can occur during dispatching http requests pub enum DispatchError { /// Service error - Service, + Service(Error), /// An `io::Error` that occurred while trying to read or write to a network /// stream. diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index 6e891e7cd..64731ac94 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -154,33 +154,37 @@ impl Encoder for Codec { ) -> Result<(), Self::Error> { match item { Message::Item((mut res, length)) => { - // set response version - res.head_mut().version = self.version; - - // connection status - self.ctype = if let Some(ct) = res.head().ctype() { - if ct == ConnectionType::KeepAlive { - self.ctype - } else { - ct - } + if res.head().status == StatusCode::CONTINUE { + dst.extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n"); } else { - self.ctype - }; + // set response version + res.head_mut().version = self.version; - // encode message - let len = dst.len(); - self.encoder.encode( - dst, - &mut res, - self.flags.contains(Flags::HEAD), - self.flags.contains(Flags::STREAM), - self.version, - length, - self.ctype, - &self.config, - )?; - self.headers_size = (dst.len() - len) as u32; + // connection status + self.ctype = if let Some(ct) = res.head().ctype() { + if ct == ConnectionType::KeepAlive { + self.ctype + } else { + ct + } + } else { + self.ctype + }; + + // encode message + let len = dst.len(); + self.encoder.encode( + dst, + &mut res, + self.flags.contains(Flags::HEAD), + self.flags.contains(Flags::STREAM), + self.version, + length, + self.ctype, + &self.config, + )?; + self.headers_size = (dst.len() - len) as u32; + } } Message::Chunk(Some(bytes)) => { self.encoder.encode_chunk(bytes.as_ref(), dst)?; diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index dfd9fe25c..10652d627 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -51,6 +51,8 @@ pub(crate) enum PayloadLength { pub(crate) trait MessageType: Sized { fn set_connection_type(&mut self, ctype: Option); + fn set_expect(&mut self); + fn headers_mut(&mut self) -> &mut HeaderMap; fn decode(src: &mut BytesMut) -> Result, ParseError>; @@ -62,6 +64,7 @@ pub(crate) trait MessageType: Sized { ) -> Result { let mut ka = None; let mut has_upgrade = false; + let mut expect = false; let mut chunked = false; let mut content_length = None; @@ -126,6 +129,12 @@ pub(crate) trait MessageType: Sized { } } } + header::EXPECT => { + let bytes = value.as_bytes(); + if bytes.len() >= 4 && &bytes[0..4] == b"100-" { + expect = true; + } + } _ => (), } @@ -136,6 +145,9 @@ pub(crate) trait MessageType: Sized { } } self.set_connection_type(ka); + if expect { + self.set_expect() + } // https://tools.ietf.org/html/rfc7230#section-3.3.3 if chunked { @@ -163,6 +175,10 @@ impl MessageType for Request { } } + fn set_expect(&mut self) { + self.head_mut().set_expect(); + } + fn headers_mut(&mut self) -> &mut HeaderMap { &mut self.head_mut().headers } @@ -235,6 +251,8 @@ impl MessageType for ResponseHead { } } + fn set_expect(&mut self) {} + fn headers_mut(&mut self) -> &mut HeaderMap { &mut self.headers } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 0f9b495b3..e2306fde7 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -1,5 +1,4 @@ use std::collections::VecDeque; -use std::fmt::Debug; use std::mem; use std::time::Instant; @@ -13,8 +12,9 @@ use tokio_timer::Delay; use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::config::ServiceConfig; -use crate::error::DispatchError; +use crate::error::{DispatchError, Error}; use crate::error::{ParseError, PayloadError}; +use crate::http::StatusCode; use crate::request::Request; use crate::response::Response; @@ -37,24 +37,33 @@ bitflags! { } /// Dispatcher for HTTP/1.1 protocol -pub struct Dispatcher, B: MessageBody> +pub struct Dispatcher where - S::Error: Debug, + S: Service, + S::Error: Into, + B: MessageBody, + X: Service, + X::Error: Into, { - inner: Option>, + inner: Option>, } -struct InnerDispatcher, B: MessageBody> +struct InnerDispatcher where - S::Error: Debug, + S: Service, + S::Error: Into, + B: MessageBody, + X: Service, + X::Error: Into, { service: CloneableService, + expect: CloneableService, flags: Flags, framed: Framed, error: Option, config: ServiceConfig, - state: State, + state: State, payload: Option, messages: VecDeque, @@ -67,13 +76,24 @@ enum DispatcherMessage { Error(Response<()>), } -enum State, B: MessageBody> { +enum State +where + S: Service, + X: Service, + B: MessageBody, +{ None, + ExpectCall(X::Future), ServiceCall(S::Future), SendPayload(ResponseBody), } -impl, B: MessageBody> State { +impl State +where + S: Service, + X: Service, + B: MessageBody, +{ fn is_empty(&self) -> bool { if let State::None = self { true @@ -83,21 +103,29 @@ impl, B: MessageBody> State { } } -impl Dispatcher +impl Dispatcher where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Debug, + S::Error: Into, S::Response: Into>, B: MessageBody, + X: Service, + X::Error: Into, { /// Create http/1 dispatcher. - pub fn new(stream: T, config: ServiceConfig, service: CloneableService) -> Self { + pub fn new( + stream: T, + config: ServiceConfig, + service: CloneableService, + expect: CloneableService, + ) -> Self { Dispatcher::with_timeout( Framed::new(stream, Codec::new(config.clone())), config, None, service, + expect, ) } @@ -107,6 +135,7 @@ where config: ServiceConfig, timeout: Option, service: CloneableService, + expect: CloneableService, ) -> Self { let keepalive = config.keep_alive_enabled(); let flags = if keepalive { @@ -132,6 +161,7 @@ where error: None, messages: VecDeque::new(), service, + expect, flags, config, ka_expire, @@ -141,13 +171,15 @@ where } } -impl InnerDispatcher +impl InnerDispatcher where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Debug, + S::Error: Into, S::Response: Into>, B: MessageBody, + X: Service, + X::Error: Into, { fn can_read(&self) -> bool { if self.flags.contains(Flags::DISCONNECTED) { @@ -195,7 +227,7 @@ where &mut self, message: Response<()>, body: ResponseBody, - ) -> Result, DispatchError> { + ) -> Result, DispatchError> { self.framed .force_send(Message::Item((message, body.length()))) .map_err(|err| { @@ -213,6 +245,15 @@ where } } + fn send_continue(&mut self) -> Result<(), DispatchError> { + self.framed + .force_send(Message::Item(( + Response::empty(StatusCode::CONTINUE), + BodySize::Empty, + ))) + .map_err(|err| DispatchError::Io(err)) + } + fn poll_response(&mut self) -> Result<(), DispatchError> { let mut retry = self.can_read(); loop { @@ -227,6 +268,22 @@ where } None => None, }, + State::ExpectCall(mut fut) => match fut.poll() { + Ok(Async::Ready(req)) => { + self.send_continue()?; + Some(State::ServiceCall(self.service.call(req))) + } + Ok(Async::NotReady) => { + self.state = State::ExpectCall(fut); + None + } + Err(e) => { + let e = e.into(); + let res: Response = e.into(); + let (res, body) = res.replace_body(()); + Some(self.send_response(res, body.into_body())?) + } + }, State::ServiceCall(mut fut) => match fut.poll() { Ok(Async::Ready(res)) => { let (res, body) = res.into().replace_body(()); @@ -289,7 +346,28 @@ where Ok(()) } - fn handle_request(&mut self, req: Request) -> Result, DispatchError> { + fn handle_request(&mut self, req: Request) -> Result, DispatchError> { + // Handle `EXPECT: 100-Continue` header + let req = if req.head().expect() { + let mut task = self.expect.call(req); + match task.poll() { + Ok(Async::Ready(req)) => { + self.send_continue()?; + req + } + Ok(Async::NotReady) => return Ok(State::ExpectCall(task)), + Err(e) => { + let e = e.into(); + let res: Response = e.into(); + let (res, body) = res.replace_body(()); + return self.send_response(res, body.into_body()); + } + } + } else { + req + }; + + // Call service let mut task = self.service.call(req); match task.poll() { Ok(Async::Ready(res)) => { @@ -329,10 +407,6 @@ where req = req1; self.payload = Some(ps); } - //MessageType::Stream => { - // self.unhandled = Some(req); - // return Ok(updated); - //} _ => (), } @@ -482,13 +556,15 @@ where } } -impl Future for Dispatcher +impl Future for Dispatcher where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Debug, + S::Error: Into, S::Response: Into>, B: MessageBody, + X: Service, + X::Error: Into, { type Item = (); type Error = DispatchError; @@ -558,6 +634,7 @@ mod tests { use super::*; use crate::error::Error; + use crate::h1::ExpectHandler; struct Buffer { buf: Bytes, @@ -620,6 +697,7 @@ mod tests { CloneableService::new( (|_| ok::<_, Error>(Response::Ok().finish())).into_service(), ), + CloneableService::new(ExpectHandler), ); assert!(h1.poll().is_ok()); assert!(h1.poll().is_ok()); diff --git a/actix-http/src/h1/expect.rs b/actix-http/src/h1/expect.rs new file mode 100644 index 000000000..86fcb2cc3 --- /dev/null +++ b/actix-http/src/h1/expect.rs @@ -0,0 +1,36 @@ +use actix_service::{NewService, Service}; +use futures::future::{ok, FutureResult}; +use futures::{Async, Poll}; + +use crate::error::Error; +use crate::request::Request; + +pub struct ExpectHandler; + +impl NewService for ExpectHandler { + type Request = Request; + type Response = Request; + type Error = Error; + type Service = ExpectHandler; + type InitError = Error; + type Future = FutureResult; + + fn new_service(&self, _: &()) -> Self::Future { + ok(ExpectHandler) + } +} + +impl Service for ExpectHandler { + type Request = Request; + type Response = Request; + type Error = Error; + type Future = FutureResult; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, req: Request) -> Self::Future { + ok(req) + } +} diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs index a05f2800c..dd29547e5 100644 --- a/actix-http/src/h1/mod.rs +++ b/actix-http/src/h1/mod.rs @@ -6,12 +6,14 @@ mod codec; mod decoder; mod dispatcher; mod encoder; +mod expect; mod payload; mod service; pub use self::client::{ClientCodec, ClientPayloadCodec}; pub use self::codec::Codec; pub use self::dispatcher::Dispatcher; +pub use self::expect::ExpectHandler; pub use self::payload::{Payload, PayloadWriter}; pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index d7ab50626..c3d21b4db 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -1,4 +1,4 @@ -use std::fmt::Debug; +use std::fmt; use std::marker::PhantomData; use actix_codec::{AsyncRead, AsyncWrite, Framed}; @@ -10,25 +10,27 @@ use futures::{try_ready, Async, Future, IntoFuture, Poll, Stream}; use crate::body::MessageBody; use crate::config::{KeepAlive, ServiceConfig}; -use crate::error::{DispatchError, ParseError}; +use crate::error::{DispatchError, Error, ParseError}; use crate::request::Request; use crate::response::Response; use super::codec::Codec; use super::dispatcher::Dispatcher; -use super::Message; +use super::{ExpectHandler, Message}; /// `NewService` implementation for HTTP1 transport -pub struct H1Service { +pub struct H1Service { srv: S, cfg: ServiceConfig, + expect: X, _t: PhantomData<(T, P, B)>, } impl H1Service where S: NewService, - S::Error: Debug, + S::Error: Into, + S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, { @@ -39,6 +41,7 @@ where H1Service { cfg, srv: service.into_new_service(), + expect: ExpectHandler, _t: PhantomData, } } @@ -51,29 +54,59 @@ where H1Service { cfg, srv: service.into_new_service(), + expect: ExpectHandler, _t: PhantomData, } } } -impl NewService for H1Service +impl H1Service +where + S: NewService, + S::Error: Into, + S::Response: Into>, + S::InitError: fmt::Debug, + B: MessageBody, +{ + pub fn expect(self, expect: U) -> H1Service + where + U: NewService, + U::Error: Into, + U::InitError: fmt::Debug, + { + H1Service { + expect, + cfg: self.cfg, + srv: self.srv, + _t: PhantomData, + } + } +} + +impl NewService for H1Service where T: AsyncRead + AsyncWrite, S: NewService, - S::Error: Debug, + S::Error: Into, S::Response: Into>, + S::InitError: fmt::Debug, B: MessageBody, + X: NewService, + X::Error: Into, + X::InitError: fmt::Debug, { type Request = Io; type Response = (); type Error = DispatchError; - type InitError = S::InitError; - type Service = H1ServiceHandler; - type Future = H1ServiceResponse; + type InitError = (); + type Service = H1ServiceHandler; + type Future = H1ServiceResponse; fn new_service(&self, cfg: &SrvConfig) -> Self::Future { H1ServiceResponse { fut: self.srv.new_service(cfg).into_future(), + fut_ex: Some(self.expect.new_service(&())), + expect: None, cfg: Some(self.cfg.clone()), _t: PhantomData, } @@ -81,77 +114,136 @@ where } #[doc(hidden)] -pub struct H1ServiceResponse, B> { - fut: ::Future, +pub struct H1ServiceResponse +where + S: NewService, + S::Error: Into, + S::InitError: fmt::Debug, + X: NewService, + X::Error: Into, + X::InitError: fmt::Debug, +{ + fut: S::Future, + fut_ex: Option, + expect: Option, cfg: Option, _t: PhantomData<(T, P, B)>, } -impl Future for H1ServiceResponse +impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite, S: NewService, - S::Error: Debug, + S::Error: Into, S::Response: Into>, + S::InitError: fmt::Debug, B: MessageBody, + X: NewService, + X::Error: Into, + X::InitError: fmt::Debug, { - type Item = H1ServiceHandler; - type Error = S::InitError; + type Item = H1ServiceHandler; + type Error = (); fn poll(&mut self) -> Poll { - let service = try_ready!(self.fut.poll()); + if let Some(ref mut fut) = self.fut_ex { + let expect = try_ready!(fut + .poll() + .map_err(|e| log::error!("Init http service error: {:?}", e))); + self.expect = Some(expect); + self.fut_ex.take(); + } + + let service = try_ready!(self + .fut + .poll() + .map_err(|e| log::error!("Init http service error: {:?}", e))); Ok(Async::Ready(H1ServiceHandler::new( self.cfg.take().unwrap(), service, + self.expect.take().unwrap(), ))) } } /// `Service` implementation for HTTP1 transport -pub struct H1ServiceHandler { +pub struct H1ServiceHandler { srv: CloneableService, + expect: CloneableService, cfg: ServiceConfig, _t: PhantomData<(T, P, B)>, } -impl H1ServiceHandler +impl H1ServiceHandler where S: Service, - S::Error: Debug, + S::Error: Into, S::Response: Into>, B: MessageBody, + X: Service, + X::Error: Into, { - fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { + fn new(cfg: ServiceConfig, srv: S, expect: X) -> H1ServiceHandler { H1ServiceHandler { srv: CloneableService::new(srv), + expect: CloneableService::new(expect), cfg, _t: PhantomData, } } } -impl Service for H1ServiceHandler +impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Debug, + S::Error: Into, S::Response: Into>, B: MessageBody, + X: Service, + X::Error: Into, { type Request = Io; type Response = (); type Error = DispatchError; - type Future = Dispatcher; + type Future = Dispatcher; fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.srv.poll_ready().map_err(|e| { - log::error!("Http service readiness error: {:?}", e); - DispatchError::Service - }) + let ready = self + .expect + .poll_ready() + .map_err(|e| { + let e = e.into(); + log::error!("Http service readiness error: {:?}", e); + DispatchError::Service(e) + })? + .is_ready(); + + let ready = self + .srv + .poll_ready() + .map_err(|e| { + let e = e.into(); + log::error!("Http service readiness error: {:?}", e); + DispatchError::Service(e) + })? + .is_ready() + && ready; + + if ready { + Ok(Async::Ready(())) + } else { + Ok(Async::NotReady) + } } fn call(&mut self, req: Self::Request) -> Self::Future { - Dispatcher::new(req.into_parts().0, self.cfg.clone(), self.srv.clone()) + Dispatcher::new( + req.into_parts().0, + self.cfg.clone(), + self.srv.clone(), + self.expect.clone(), + ) } } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 0ef40fc08..e00996048 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -46,7 +46,7 @@ impl Dispatcher where T: AsyncRead + AsyncWrite, S: Service, - S::Error: fmt::Debug, + S::Error: Into, S::Future: 'static, S::Response: Into>, B: MessageBody + 'static, @@ -88,7 +88,7 @@ impl Future for Dispatcher where T: AsyncRead + AsyncWrite, S: Service, - S::Error: fmt::Debug, + S::Error: Into, S::Future: 'static, S::Response: Into>, B: MessageBody + 'static, @@ -146,7 +146,7 @@ enum ServiceResponseState { impl ServiceResponse where F: Future, - F::Error: fmt::Debug, + F::Error: Into, F::Item: Into>, B: MessageBody + 'static, { @@ -214,7 +214,7 @@ where impl Future for ServiceResponse where F: Future, - F::Error: fmt::Debug, + F::Error: Into, F::Item: Into>, B: MessageBody + 'static, { diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 16ccd79a5..8ab244b50 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -32,7 +32,7 @@ pub struct H2Service { impl H2Service where S: NewService, - S::Error: Debug, + S::Error: Into, S::Response: Into>, ::Future: 'static, B: MessageBody + 'static, @@ -65,7 +65,7 @@ impl NewService for H2Service where T: AsyncRead + AsyncWrite, S: NewService, - S::Error: Debug, + S::Error: Into, S::Response: Into>, ::Future: 'static, B: MessageBody + 'static, @@ -97,7 +97,7 @@ impl Future for H2ServiceResponse where T: AsyncRead + AsyncWrite, S: NewService, - S::Error: Debug, + S::Error: Into, S::Response: Into>, ::Future: 'static, B: MessageBody + 'static, @@ -124,7 +124,7 @@ pub struct H2ServiceHandler { impl H2ServiceHandler where S: Service, - S::Error: Debug, + S::Error: Into, S::Future: 'static, S::Response: Into>, B: MessageBody + 'static, @@ -142,7 +142,7 @@ impl Service for H2ServiceHandler where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Debug, + S::Error: Into, S::Future: 'static, S::Response: Into>, B: MessageBody + 'static, @@ -154,8 +154,9 @@ where fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.srv.poll_ready().map_err(|e| { + let e = e.into(); error!("Service readiness error: {:?}", e); - DispatchError::Service + DispatchError::Service(e) }) } @@ -186,7 +187,7 @@ pub struct H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Debug, + S::Error: Into, S::Future: 'static, S::Response: Into>, B: MessageBody + 'static, @@ -198,7 +199,7 @@ impl Future for H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Debug, + S::Error: Into, S::Future: 'static, S::Response: Into>, B: MessageBody, diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 3466f66df..2fdb28e40 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -23,7 +23,8 @@ bitflags! { const CLOSE = 0b0000_0001; const KEEP_ALIVE = 0b0000_0010; const UPGRADE = 0b0000_0100; - const NO_CHUNKING = 0b0000_1000; + const EXPECT = 0b0000_1000; + const NO_CHUNKING = 0b0001_0000; } } @@ -145,6 +146,17 @@ impl RequestHead { self.flags.remove(Flags::NO_CHUNKING); } } + + #[inline] + /// Request contains `EXPECT` header + pub fn expect(&self) -> bool { + self.flags.contains(Flags::EXPECT) + } + + #[inline] + pub(crate) fn set_expect(&mut self) { + self.flags.insert(Flags::EXPECT); + } } #[derive(Debug)] diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index c3fed133d..0c8c2eef6 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -51,6 +51,18 @@ impl Response { } } + #[inline] + pub(crate) fn empty(status: StatusCode) -> Response<()> { + let mut head: Message = Message::new(); + head.status = status; + + Response { + head, + body: ResponseBody::Body(()), + error: None, + } + } + /// Constructs an error response #[inline] pub fn from_error(error: Error) -> Response { diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index f97cc0483..f259e3021 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -1,4 +1,3 @@ -use std::fmt::Debug; use std::marker::PhantomData; use std::{fmt, io}; @@ -9,27 +8,28 @@ use actix_utils::cloneable::CloneableService; use bytes::{Buf, BufMut, Bytes, BytesMut}; use futures::{try_ready, Async, Future, IntoFuture, Poll}; use h2::server::{self, Handshake}; -use log::error; use crate::body::MessageBody; use crate::builder::HttpServiceBuilder; use crate::config::{KeepAlive, ServiceConfig}; -use crate::error::DispatchError; +use crate::error::{DispatchError, Error}; use crate::request::Request; use crate::response::Response; use crate::{h1, h2::Dispatcher}; /// `NewService` HTTP1.1/HTTP2 transport implementation -pub struct HttpService { +pub struct HttpService { srv: S, cfg: ServiceConfig, + expect: X, _t: PhantomData<(T, P, B)>, } impl HttpService where S: NewService, - S::Error: Debug, + S::Error: Into, + S::InitError: fmt::Debug, S::Response: Into>, ::Future: 'static, B: MessageBody + 'static, @@ -43,7 +43,8 @@ where impl HttpService where S: NewService, - S::Error: Debug, + S::Error: Into, + S::InitError: fmt::Debug, S::Response: Into>, ::Future: 'static, B: MessageBody + 'static, @@ -55,6 +56,7 @@ where HttpService { cfg, srv: service.into_new_service(), + expect: h1::ExpectHandler, _t: PhantomData, } } @@ -67,30 +69,65 @@ where HttpService { cfg, srv: service.into_new_service(), + expect: h1::ExpectHandler, _t: PhantomData, } } } -impl NewService for HttpService +impl HttpService +where + S: NewService, + S::Error: Into, + S::InitError: fmt::Debug, + S::Response: Into>, + B: MessageBody, +{ + /// Provide service for `EXPECT: 100-Continue` support. + /// + /// Service get called with request that contains `EXPECT` header. + /// Service must return request in case of success, in that case + /// request will be forwarded to main service. + pub fn expect(self, expect: U) -> HttpService + where + U: NewService, + U::Error: Into, + U::InitError: fmt::Debug, + { + HttpService { + expect, + cfg: self.cfg, + srv: self.srv, + _t: PhantomData, + } + } +} + +impl NewService for HttpService where T: AsyncRead + AsyncWrite, S: NewService, - S::Error: Debug, + S::Error: Into, + S::InitError: fmt::Debug, S::Response: Into>, ::Future: 'static, B: MessageBody + 'static, + X: NewService, + X::Error: Into, + X::InitError: fmt::Debug, { type Request = ServerIo; type Response = (); type Error = DispatchError; - type InitError = S::InitError; - type Service = HttpServiceHandler; - type Future = HttpServiceResponse; + type InitError = (); + type Service = HttpServiceHandler; + type Future = HttpServiceResponse; fn new_service(&self, cfg: &SrvConfig) -> Self::Future { HttpServiceResponse { fut: self.srv.new_service(cfg).into_future(), + fut_ex: Some(self.expect.new_service(&())), + expect: None, cfg: Some(self.cfg.clone()), _t: PhantomData, } @@ -98,76 +135,122 @@ where } #[doc(hidden)] -pub struct HttpServiceResponse, B> { - fut: ::Future, +pub struct HttpServiceResponse, B, X: NewService> { + fut: S::Future, + fut_ex: Option, + expect: Option, cfg: Option, _t: PhantomData<(T, P, B)>, } -impl Future for HttpServiceResponse +impl Future for HttpServiceResponse where T: AsyncRead + AsyncWrite, S: NewService, - S::Error: Debug, + S::Error: Into, + S::InitError: fmt::Debug, S::Response: Into>, ::Future: 'static, B: MessageBody + 'static, + X: NewService, + X::Error: Into, + X::InitError: fmt::Debug, { - type Item = HttpServiceHandler; - type Error = S::InitError; + type Item = HttpServiceHandler; + type Error = (); fn poll(&mut self) -> Poll { - let service = try_ready!(self.fut.poll()); + if let Some(ref mut fut) = self.fut_ex { + let expect = try_ready!(fut + .poll() + .map_err(|e| log::error!("Init http service error: {:?}", e))); + self.expect = Some(expect); + self.fut_ex.take(); + } + + let service = try_ready!(self + .fut + .poll() + .map_err(|e| log::error!("Init http service error: {:?}", e))); Ok(Async::Ready(HttpServiceHandler::new( self.cfg.take().unwrap(), service, + self.expect.take().unwrap(), ))) } } /// `Service` implementation for http transport -pub struct HttpServiceHandler { +pub struct HttpServiceHandler { srv: CloneableService, + expect: CloneableService, cfg: ServiceConfig, - _t: PhantomData<(T, P, B)>, + _t: PhantomData<(T, P, B, X)>, } -impl HttpServiceHandler +impl HttpServiceHandler where S: Service, - S::Error: Debug, + S::Error: Into, S::Future: 'static, S::Response: Into>, B: MessageBody + 'static, + X: Service, + X::Error: Into, { - fn new(cfg: ServiceConfig, srv: S) -> HttpServiceHandler { + fn new(cfg: ServiceConfig, srv: S, expect: X) -> HttpServiceHandler { HttpServiceHandler { cfg, srv: CloneableService::new(srv), + expect: CloneableService::new(expect), _t: PhantomData, } } } -impl Service for HttpServiceHandler +impl Service for HttpServiceHandler where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Debug, + S::Error: Into, S::Future: 'static, S::Response: Into>, B: MessageBody + 'static, + X: Service, + X::Error: Into, { type Request = ServerIo; type Response = (); type Error = DispatchError; - type Future = HttpServiceHandlerResponse; + type Future = HttpServiceHandlerResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.srv.poll_ready().map_err(|e| { - error!("Service readiness error: {:?}", e); - DispatchError::Service - }) + let ready = self + .expect + .poll_ready() + .map_err(|e| { + let e = e.into(); + log::error!("Http service readiness error: {:?}", e); + DispatchError::Service(e) + })? + .is_ready(); + + let ready = self + .srv + .poll_ready() + .map_err(|e| { + let e = e.into(); + log::error!("Http service readiness error: {:?}", e); + DispatchError::Service(e) + })? + .is_ready() + && ready; + + if ready { + Ok(Async::Ready(())) + } else { + Ok(Async::NotReady) + } } fn call(&mut self, req: Self::Request) -> Self::Future { @@ -191,6 +274,7 @@ where io, self.cfg.clone(), self.srv.clone(), + self.expect.clone(), )), }, _ => HttpServiceHandlerResponse { @@ -199,46 +283,63 @@ where BytesMut::with_capacity(14), self.cfg.clone(), self.srv.clone(), + self.expect.clone(), ))), }, } } } -enum State, B: MessageBody> +enum State where + S: Service, S::Future: 'static, - S::Error: fmt::Debug, + S::Error: Into, T: AsyncRead + AsyncWrite, + B: MessageBody, + X: Service, + X::Error: Into, { - H1(h1::Dispatcher), + H1(h1::Dispatcher), H2(Dispatcher, S, B>), - Unknown(Option<(T, BytesMut, ServiceConfig, CloneableService)>), + Unknown( + Option<( + T, + BytesMut, + ServiceConfig, + CloneableService, + CloneableService, + )>, + ), Handshake(Option<(Handshake, Bytes>, ServiceConfig, CloneableService)>), } -pub struct HttpServiceHandlerResponse +pub struct HttpServiceHandlerResponse where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Debug, + S::Error: Into, S::Future: 'static, S::Response: Into>, B: MessageBody + 'static, + X: Service, + X::Error: Into, { - state: State, + state: State, } const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; -impl Future for HttpServiceHandlerResponse +impl Future for HttpServiceHandlerResponse where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Debug, + S::Error: Into, S::Future: 'static, S::Response: Into>, B: MessageBody, + X: Service, + X::Error: Into, { type Item = (); type Error = DispatchError; @@ -265,7 +366,7 @@ where } else { panic!() } - let (io, buf, cfg, srv) = data.take().unwrap(); + let (io, buf, cfg, srv, expect) = data.take().unwrap(); if buf[..14] == HTTP2_PREFACE[..] { let io = Io { inner: io, @@ -279,8 +380,9 @@ where h1::Codec::new(cfg.clone()), buf, )); - self.state = - State::H1(h1::Dispatcher::with_timeout(framed, cfg, None, srv)) + self.state = State::H1(h1::Dispatcher::with_timeout( + framed, cfg, None, srv, expect, + )) } self.poll() } diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index 817164f81..cfe0999fd 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -61,7 +61,9 @@ fn test_connection_close() { .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) }); + println!("REQ: {:?}", srv.get("/").force_close()); let response = srv.block_on(srv.get("/").force_close().send()).unwrap(); + println!("RES: {:?}", response); assert!(response.status().is_success()); } diff --git a/src/server.rs b/src/server.rs index 2817f549c..efc70773f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use std::sync::Arc; use std::{fmt, io, net}; -use actix_http::{body::MessageBody, HttpService, KeepAlive, Request, Response}; +use actix_http::{body::MessageBody, Error, HttpService, KeepAlive, Request, Response}; use actix_rt::System; use actix_server::{Server, ServerBuilder}; use actix_server_config::ServerConfig; @@ -53,7 +53,8 @@ where F: Fn() -> I + Send + Clone + 'static, I: IntoNewService, S: NewService, - S::Error: fmt::Debug, + S::Error: Into, + S::InitError: fmt::Debug, S::Response: Into>, S::Service: 'static, B: MessageBody, @@ -72,7 +73,8 @@ where F: Fn() -> I + Send + Clone + 'static, I: IntoNewService, S: NewService, - S::Error: fmt::Debug + 'static, + S::Error: Into, + S::InitError: fmt::Debug, S::Response: Into>, S::Service: 'static, B: MessageBody + 'static, @@ -442,7 +444,8 @@ where F: Fn() -> I + Send + Clone + 'static, I: IntoNewService, S: NewService, - S::Error: fmt::Debug, + S::Error: Into, + S::InitError: fmt::Debug, S::Response: Into>, S::Service: 'static, B: MessageBody, diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 98bef99be..3f77f3786 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -90,13 +90,13 @@ impl TestServer { Connector::new() .timeout(time::Duration::from_millis(500)) .ssl(builder.build()) - .service() + .finish() } #[cfg(not(feature = "ssl"))] { Connector::new() .timeout(time::Duration::from_millis(500)) - .service() + .finish() } }; diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index dca3377c9..c0d2e81c4 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -61,7 +61,7 @@ fn test_start() { .connector( client::Connector::new() .timeout(Duration::from_millis(100)) - .service(), + .finish(), ) .finish(), ) @@ -136,7 +136,7 @@ fn test_start_ssl() { awc::Connector::new() .ssl(builder.build()) .timeout(Duration::from_millis(100)) - .service(), + .finish(), ) .finish(), ) From b1523ab78c300b2e39aad9fa4139de56d88bf8c3 Mon Sep 17 00:00:00 2001 From: Darin Date: Sat, 6 Apr 2019 10:39:20 -0400 Subject: [PATCH 023/122] started 1.0 migration guide (#758) --- MIGRATION.md | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/MIGRATION.md b/MIGRATION.md index 6b49e3e6a..372d68930 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,3 +1,58 @@ +## 1.0 + +* `State` is now `Data`. You register Data during the App initialization process +and then access it from handlers either using a Data extractor or using +HttpRequest's api. + + instead of + + ```rust + App.with_state(T) + ``` + + use App's `data` method + + ```rust + App.new() + .data(T) + ``` + + and either use the Data extractor within your handler + + ```rust + use actix_web::web::Data; + + fn endpoint_handler(Data)){ + ... + } + ``` + + .. or access your Data element from the HttpRequest + + ```rust + fn endpoint_handler(req: HttpRequest) { + let data: Option> = req.app_data::(); + } + ``` + + +* AsyncResponder is deprecated. + + instead of + + ```rust + use actix_web::AsyncResponder; + + fn endpoint_handler(...) -> impl Future{ + ... + .responder() + } + ``` + + .. simply omit AsyncResponder and the corresponding responder() finish method + + + ## 0.7.15 * The `' '` character is not percent decoded anymore before matching routes. If you need to use it in From 3872d3ba5a3a1d2eacf41fffd20ee66d50ed44fd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 6 Apr 2019 00:16:04 -0700 Subject: [PATCH 024/122] refactor h1 dispatcher --- actix-http/src/h1/codec.rs | 19 +- actix-http/src/h1/decoder.rs | 4 +- actix-http/src/h1/dispatcher.rs | 385 +++++++++++++++++++++----------- actix-http/src/h1/encoder.rs | 6 - actix-http/src/response.rs | 12 - actix-http/src/service.rs | 11 +- actix-http/tests/test_server.rs | 4 +- tests/test_server.rs | 2 +- 8 files changed, 275 insertions(+), 168 deletions(-) diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index 64731ac94..3834254a2 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, reserve_readbuf}; +use super::{decoder, encoder}; use super::{Message, MessageType}; use crate::body::BodySize; use crate::config::ServiceConfig; @@ -31,7 +31,7 @@ const AVERAGE_HEADER_SIZE: usize = 30; /// HTTP/1 Codec pub struct Codec { - config: ServiceConfig, + pub(crate) config: ServiceConfig, decoder: decoder::MessageDecoder, payload: Option, version: Version, @@ -78,16 +78,25 @@ impl Codec { } } + #[inline] /// Check if request is upgrade pub fn upgrade(&self) -> bool { self.ctype == ConnectionType::Upgrade } + #[inline] /// Check if last response is keep-alive pub fn keepalive(&self) -> bool { self.ctype == ConnectionType::KeepAlive } + #[inline] + /// Check if keep-alive enabled on server level + pub fn keepalive_enabled(&self) -> bool { + self.flags.contains(Flags::KEEPALIVE_ENABLED) + } + + #[inline] /// Check last request's message type pub fn message_type(&self) -> MessageType { if self.flags.contains(Flags::STREAM) { @@ -107,10 +116,7 @@ impl Decoder for Codec { fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { if self.payload.is_some() { Ok(match self.payload.as_mut().unwrap().decode(src)? { - Some(PayloadItem::Chunk(chunk)) => { - reserve_readbuf(src); - Some(Message::Chunk(Some(chunk))) - } + Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))), Some(PayloadItem::Eof) => { self.payload.take(); Some(Message::Chunk(None)) @@ -135,7 +141,6 @@ 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/decoder.rs b/actix-http/src/h1/decoder.rs index 10652d627..88de9bc6d 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -84,7 +84,9 @@ pub(crate) trait MessageType: Sized { header::CONTENT_LENGTH => { if let Ok(s) = value.to_str() { if let Ok(len) = s.parse::() { - content_length = Some(len); + if len != 0 { + content_length = Some(len); + } } else { debug!("illegal Content-Length: {:?}", s); return Err(ParseError::Header); diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index e2306fde7..61c284a9c 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -1,20 +1,20 @@ use std::collections::VecDeque; -use std::mem; use std::time::Instant; +use std::{fmt, io}; -use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder}; use actix_service::Service; use actix_utils::cloneable::CloneableService; use bitflags::bitflags; -use futures::{Async, Future, Poll, Sink, Stream}; -use log::{debug, error, trace}; +use bytes::{BufMut, BytesMut}; +use futures::{Async, Future, Poll}; +use log::{error, trace}; use tokio_timer::Delay; use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; use crate::error::{ParseError, PayloadError}; -use crate::http::StatusCode; use crate::request::Request; use crate::response::Response; @@ -22,17 +22,19 @@ use super::codec::Codec; use super::payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; use super::{Message, MessageType}; +const LW_BUFFER_SIZE: usize = 4096; +const HW_BUFFER_SIZE: usize = 32_768; const MAX_PIPELINED_MESSAGES: usize = 16; bitflags! { pub struct Flags: u8 { const STARTED = 0b0000_0001; - const KEEPALIVE_ENABLED = 0b0000_0010; - const KEEPALIVE = 0b0000_0100; - const POLLED = 0b0000_1000; - const SHUTDOWN = 0b0010_0000; - const DISCONNECTED = 0b0100_0000; - const DROPPING = 0b1000_0000; + const KEEPALIVE = 0b0000_0010; + const POLLED = 0b0000_0100; + const SHUTDOWN = 0b0000_1000; + const READ_DISCONNECT = 0b0001_0000; + const WRITE_DISCONNECT = 0b0010_0000; + const DROPPING = 0b0100_0000; } } @@ -59,9 +61,7 @@ where service: CloneableService, expect: CloneableService, flags: Flags, - framed: Framed, error: Option, - config: ServiceConfig, state: State, payload: Option, @@ -69,6 +69,11 @@ where ka_expire: Instant, ka_timer: Option, + + io: T, + read_buf: BytesMut, + write_buf: BytesMut, + codec: Codec, } enum DispatcherMessage { @@ -101,6 +106,30 @@ where false } } + + fn is_call(&self) -> bool { + if let State::ServiceCall(_) = self { + true + } else { + false + } + } +} + +impl fmt::Debug for State +where + S: Service, + X: Service, + B: MessageBody, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + State::None => write!(f, "State::None"), + State::ExpectCall(_) => write!(f, "State::ExceptCall"), + State::ServiceCall(_) => write!(f, "State::ServiceCall"), + State::SendPayload(_) => write!(f, "State::SendPayload"), + } + } } impl Dispatcher @@ -121,8 +150,10 @@ where expect: CloneableService, ) -> Self { Dispatcher::with_timeout( - Framed::new(stream, Codec::new(config.clone())), + stream, + Codec::new(config.clone()), config, + BytesMut::with_capacity(HW_BUFFER_SIZE), None, service, expect, @@ -131,15 +162,17 @@ where /// Create http/1 dispatcher with slow request timeout. pub fn with_timeout( - framed: Framed, + io: T, + codec: Codec, config: ServiceConfig, + read_buf: BytesMut, timeout: Option, service: CloneableService, expect: CloneableService, ) -> Self { let keepalive = config.keep_alive_enabled(); let flags = if keepalive { - Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED + Flags::KEEPALIVE } else { Flags::empty() }; @@ -155,7 +188,10 @@ where Dispatcher { inner: Some(InnerDispatcher { - framed, + io, + codec, + read_buf, + write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), payload: None, state: State::None, error: None, @@ -163,7 +199,6 @@ where service, expect, flags, - config, ka_expire, ka_timer, }), @@ -182,11 +217,9 @@ where X::Error: Into, { fn can_read(&self) -> bool { - if self.flags.contains(Flags::DISCONNECTED) { + if self.flags.contains(Flags::READ_DISCONNECT) { return false; - } - - if let Some(ref info) = self.payload { + } else if let Some(ref info) = self.payload { info.need_read() == PayloadStatus::Read } else { true @@ -195,32 +228,52 @@ where // if checked is set to true, delay disconnect until all tasks have finished. fn client_disconnected(&mut self) { - self.flags.insert(Flags::DISCONNECTED); + self.flags + .insert(Flags::READ_DISCONNECT | Flags::WRITE_DISCONNECT); if let Some(mut payload) = self.payload.take() { payload.set_error(PayloadError::Incomplete(None)); } } /// Flush stream - fn poll_flush(&mut self) -> Poll { - if !self.framed.is_write_buf_empty() { - match self.framed.poll_complete() { - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(err) => { - debug!("Error sending data: {}", err); - Err(err.into()) - } - Ok(Async::Ready(_)) => { - // if payload is not consumed we can not use connection - if self.payload.is_some() && self.state.is_empty() { - return Err(DispatchError::PayloadIsNotConsumed); - } - Ok(Async::Ready(true)) - } - } - } else { - Ok(Async::Ready(false)) + /// + /// true - got whouldblock + /// false - didnt get whouldblock + fn poll_flush(&mut self) -> Result { + if self.write_buf.is_empty() { + return Ok(false); } + + let len = self.write_buf.len(); + let mut written = 0; + while written < len { + match self.io.write(&self.write_buf[written..]) { + Ok(0) => { + return Err(DispatchError::Io(io::Error::new( + io::ErrorKind::WriteZero, + "", + ))); + } + Ok(n) => { + written += n; + } + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { + if written > 0 { + let _ = self.write_buf.split_to(written); + } + return Ok(true); + } + Err(err) => return Err(DispatchError::Io(err)), + } + } + if written > 0 { + if written == self.write_buf.len() { + unsafe { self.write_buf.set_len(0) } + } else { + let _ = self.write_buf.split_to(written); + } + } + Ok(false) } fn send_response( @@ -228,8 +281,8 @@ where message: Response<()>, body: ResponseBody, ) -> Result, DispatchError> { - self.framed - .force_send(Message::Item((message, body.length()))) + self.codec + .encode(Message::Item((message, body.length())), &mut self.write_buf) .map_err(|err| { if let Some(mut payload) = self.payload.take() { payload.set_error(PayloadError::Incomplete(None)); @@ -237,113 +290,109 @@ where DispatchError::Io(err) })?; - self.flags - .set(Flags::KEEPALIVE, self.framed.get_codec().keepalive()); + self.flags.set(Flags::KEEPALIVE, self.codec.keepalive()); match body.length() { BodySize::None | BodySize::Empty => Ok(State::None), _ => Ok(State::SendPayload(body)), } } - fn send_continue(&mut self) -> Result<(), DispatchError> { - self.framed - .force_send(Message::Item(( - Response::empty(StatusCode::CONTINUE), - BodySize::Empty, - ))) - .map_err(|err| DispatchError::Io(err)) + fn send_continue(&mut self) { + self.write_buf + .extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n"); } - fn poll_response(&mut self) -> Result<(), DispatchError> { - let mut retry = self.can_read(); + fn poll_response(&mut self) -> Result { loop { - let state = match mem::replace(&mut self.state, State::None) { + let state = match self.state { State::None => match self.messages.pop_front() { Some(DispatcherMessage::Item(req)) => { Some(self.handle_request(req)?) } Some(DispatcherMessage::Error(res)) => { - self.send_response(res, ResponseBody::Other(Body::Empty))?; - None + Some(self.send_response(res, ResponseBody::Other(Body::Empty))?) } None => None, }, - State::ExpectCall(mut fut) => match fut.poll() { + State::ExpectCall(ref mut fut) => match fut.poll() { Ok(Async::Ready(req)) => { - self.send_continue()?; - Some(State::ServiceCall(self.service.call(req))) - } - Ok(Async::NotReady) => { - self.state = State::ExpectCall(fut); - None + self.send_continue(); + self.state = State::ServiceCall(self.service.call(req)); + continue; } + Ok(Async::NotReady) => None, Err(e) => { - let e = e.into(); - let res: Response = e.into(); + let res: Response = e.into().into(); let (res, body) = res.replace_body(()); Some(self.send_response(res, body.into_body())?) } }, - State::ServiceCall(mut fut) => match fut.poll() { + State::ServiceCall(ref mut fut) => match fut.poll() { Ok(Async::Ready(res)) => { let (res, body) = res.into().replace_body(()); - Some(self.send_response(res, body)?) + self.state = self.send_response(res, body)?; + continue; } - Ok(Async::NotReady) => { - self.state = State::ServiceCall(fut); - None - } - Err(_e) => { - let res: Response = Response::InternalServerError().finish(); + Ok(Async::NotReady) => None, + Err(e) => { + let res: Response = e.into().into(); let (res, body) = res.replace_body(()); Some(self.send_response(res, body.into_body())?) } }, - State::SendPayload(mut stream) => { + State::SendPayload(ref mut stream) => { loop { - if !self.framed.is_write_buf_full() { + if self.write_buf.len() < HW_BUFFER_SIZE { match stream .poll_next() .map_err(|_| DispatchError::Unknown)? { Async::Ready(Some(item)) => { - self.framed - .force_send(Message::Chunk(Some(item)))?; + self.codec.encode( + Message::Chunk(Some(item)), + &mut self.write_buf, + )?; continue; } Async::Ready(None) => { - self.framed.force_send(Message::Chunk(None))?; - } - Async::NotReady => { - self.state = State::SendPayload(stream); - return Ok(()); + self.codec.encode( + Message::Chunk(None), + &mut self.write_buf, + )?; + self.state = State::None; } + Async::NotReady => return Ok(false), } } else { - self.state = State::SendPayload(stream); - return Ok(()); + return Ok(true); } break; } - None + continue; } }; - match state { - Some(state) => self.state = state, - None => { - // if read-backpressure is enabled and we consumed some data. - // we may read more data and retry - if !retry && self.can_read() && self.poll_request()? { - retry = self.can_read(); + // set new state + if let Some(state) = state { + self.state = state; + if !self.state.is_empty() { + continue; + } + } else { + // if read-backpressure is enabled and we consumed some data. + // we may read more data and retry + if self.state.is_call() { + if self.poll_request()? { continue; } - break; + } else if !self.messages.is_empty() { + continue; } } + break; } - Ok(()) + Ok(false) } fn handle_request(&mut self, req: Request) -> Result, DispatchError> { @@ -352,7 +401,7 @@ where let mut task = self.expect.call(req); match task.poll() { Ok(Async::Ready(req)) => { - self.send_continue()?; + self.send_continue(); req } Ok(Async::NotReady) => return Ok(State::ExpectCall(task)), @@ -375,8 +424,8 @@ where self.send_response(res, body) } Ok(Async::NotReady) => Ok(State::ServiceCall(task)), - Err(_e) => { - let res: Response = Response::InternalServerError().finish(); + Err(e) => { + let res: Response = e.into().into(); let (res, body) = res.replace_body(()); self.send_response(res, body.into_body()) } @@ -386,20 +435,20 @@ where /// Process one incoming requests pub(self) fn poll_request(&mut self) -> Result { // limit a mount of non processed requests - if self.messages.len() >= MAX_PIPELINED_MESSAGES { + if self.messages.len() >= MAX_PIPELINED_MESSAGES || !self.can_read() { return Ok(false); } let mut updated = false; loop { - match self.framed.poll() { - Ok(Async::Ready(Some(msg))) => { + match self.codec.decode(&mut self.read_buf) { + Ok(Some(msg)) => { updated = true; self.flags.insert(Flags::STARTED); match msg { Message::Item(mut req) => { - match self.framed.get_codec().message_type() { + match self.codec.message_type() { MessageType::Payload | MessageType::Stream => { let (ps, pl) = Payload::create(false); let (req1, _) = @@ -424,7 +473,7 @@ where error!( "Internal server error: unexpected payload chunk" ); - self.flags.insert(Flags::DISCONNECTED); + self.flags.insert(Flags::READ_DISCONNECT); self.messages.push_back(DispatcherMessage::Error( Response::InternalServerError().finish().drop_body(), )); @@ -437,7 +486,7 @@ where payload.feed_eof(); } else { error!("Internal server error: unexpected eof"); - self.flags.insert(Flags::DISCONNECTED); + self.flags.insert(Flags::READ_DISCONNECT); self.messages.push_back(DispatcherMessage::Error( Response::InternalServerError().finish().drop_body(), )); @@ -447,11 +496,7 @@ where } } } - Ok(Async::Ready(None)) => { - self.client_disconnected(); - break; - } - Ok(Async::NotReady) => break, + Ok(None) => break, Err(ParseError::Io(e)) => { self.client_disconnected(); self.error = Some(DispatchError::Io(e)); @@ -466,15 +511,15 @@ where self.messages.push_back(DispatcherMessage::Error( Response::BadRequest().finish().drop_body(), )); - self.flags.insert(Flags::DISCONNECTED); + self.flags.insert(Flags::READ_DISCONNECT); self.error = Some(e.into()); break; } } } - if self.ka_timer.is_some() && updated { - if let Some(expire) = self.config.keep_alive_expire() { + if updated && self.ka_timer.is_some() { + if let Some(expire) = self.codec.config.keep_alive_expire() { self.ka_expire = expire; } } @@ -486,10 +531,10 @@ where if self.ka_timer.is_none() { // shutdown timeout if self.flags.contains(Flags::SHUTDOWN) { - if let Some(interval) = self.config.client_disconnect_timer() { + if let Some(interval) = self.codec.config.client_disconnect_timer() { self.ka_timer = Some(Delay::new(interval)); } else { - self.flags.insert(Flags::DISCONNECTED); + self.flags.insert(Flags::READ_DISCONNECT); return Ok(()); } } else { @@ -507,13 +552,14 @@ where return Err(DispatchError::DisconnectTimeout); } else if self.ka_timer.as_mut().unwrap().deadline() >= self.ka_expire { // check for any outstanding tasks - if self.state.is_empty() && self.framed.is_write_buf_empty() { + if self.state.is_empty() && self.write_buf.is_empty() { if self.flags.contains(Flags::STARTED) { trace!("Keep-alive timeout, close connection"); self.flags.insert(Flags::SHUTDOWN); // start shutdown timer - if let Some(deadline) = self.config.client_disconnect_timer() + if let Some(deadline) = + self.codec.config.client_disconnect_timer() { if let Some(timer) = self.ka_timer.as_mut() { timer.reset(deadline); @@ -521,7 +567,7 @@ where } } else { // no shutdown timeout, drop socket - self.flags.insert(Flags::DISCONNECTED); + self.flags.insert(Flags::WRITE_DISCONNECT); return Ok(()); } } else { @@ -538,7 +584,8 @@ where self.flags.insert(Flags::STARTED | Flags::SHUTDOWN); self.state = State::None; } - } else if let Some(deadline) = self.config.keep_alive_expire() { + } else if let Some(deadline) = self.codec.config.keep_alive_expire() + { if let Some(timer) = self.ka_timer.as_mut() { timer.reset(deadline); let _ = timer.poll(); @@ -572,34 +619,60 @@ where #[inline] fn poll(&mut self) -> Poll { let inner = self.inner.as_mut().unwrap(); + inner.poll_keepalive()?; if inner.flags.contains(Flags::SHUTDOWN) { - inner.poll_keepalive()?; - if inner.flags.contains(Flags::DISCONNECTED) { + if inner.flags.contains(Flags::WRITE_DISCONNECT) { Ok(Async::Ready(())) } else { - // try_ready!(inner.poll_flush()); - match inner.framed.get_mut().shutdown()? { - Async::Ready(_) => Ok(Async::Ready(())), - Async::NotReady => Ok(Async::NotReady), + // flush buffer + inner.poll_flush()?; + if !inner.write_buf.is_empty() { + Ok(Async::NotReady) + } else { + match inner.io.shutdown()? { + Async::Ready(_) => Ok(Async::Ready(())), + Async::NotReady => Ok(Async::NotReady), + } } } } else { - inner.poll_keepalive()?; + // read socket into a buf + if !inner.flags.contains(Flags::READ_DISCONNECT) { + if let Some(true) = read_available(&mut inner.io, &mut inner.read_buf)? { + inner.flags.insert(Flags::READ_DISCONNECT) + } + } + inner.poll_request()?; loop { - inner.poll_response()?; - if let Async::Ready(false) = inner.poll_flush()? { + if inner.write_buf.remaining_mut() < LW_BUFFER_SIZE { + inner.write_buf.reserve(HW_BUFFER_SIZE); + } + let need_write = inner.poll_response()?; + + // we didnt get WouldBlock from write operation, + // so data get written to kernel completely (OSX) + // and we have to write again otherwise response can get stuck + if inner.poll_flush()? || !need_write { break; } } - if inner.flags.contains(Flags::DISCONNECTED) { + // client is gone + if inner.flags.contains(Flags::WRITE_DISCONNECT) { return Ok(Async::Ready(())); } + let is_empty = inner.state.is_empty(); + + // read half is closed and we do not processing any responses + if inner.flags.contains(Flags::READ_DISCONNECT) && is_empty { + inner.flags.insert(Flags::SHUTDOWN); + } + // keep-alive and stream errors - if inner.state.is_empty() && inner.framed.is_write_buf_empty() { + if is_empty && inner.write_buf.is_empty() { if let Some(err) = inner.error.take() { Err(err) } @@ -623,13 +696,52 @@ where } } +fn read_available(io: &mut T, buf: &mut BytesMut) -> Result, io::Error> +where + T: io::Read, +{ + let mut read_some = false; + loop { + if buf.remaining_mut() < LW_BUFFER_SIZE { + buf.reserve(HW_BUFFER_SIZE); + } + + let read = unsafe { io.read(buf.bytes_mut()) }; + match read { + Ok(n) => { + if n == 0 { + return Ok(Some(true)); + } else { + read_some = true; + unsafe { + buf.advance_mut(n); + } + } + } + Err(e) => { + return if e.kind() == io::ErrorKind::WouldBlock { + if read_some { + Ok(Some(false)) + } else { + Ok(None) + } + } else if e.kind() == io::ErrorKind::ConnectionReset && read_some { + Ok(Some(true)) + } else { + Err(e) + }; + } + } + } +} + #[cfg(test)] mod tests { use std::{cmp, io}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_service::IntoService; - use bytes::{Buf, Bytes}; + use bytes::{Buf, Bytes, BytesMut}; use futures::future::{lazy, ok}; use super::*; @@ -638,6 +750,7 @@ mod tests { struct Buffer { buf: Bytes, + write_buf: BytesMut, err: Option, } @@ -645,6 +758,7 @@ mod tests { fn new(data: &'static str) -> Buffer { Buffer { buf: Bytes::from(data), + write_buf: BytesMut::new(), err: None, } } @@ -670,6 +784,7 @@ mod tests { impl io::Write for Buffer { fn write(&mut self, buf: &[u8]) -> io::Result { + self.write_buf.extend(buf); Ok(buf.len()) } fn flush(&mut self) -> io::Result<()> { @@ -699,15 +814,17 @@ mod tests { ), CloneableService::new(ExpectHandler), ); - assert!(h1.poll().is_ok()); - assert!(h1.poll().is_ok()); + assert!(h1.poll().is_err()); assert!(h1 .inner .as_ref() .unwrap() .flags - .contains(Flags::DISCONNECTED)); - // assert_eq!(h1.tasks.len(), 1); + .contains(Flags::READ_DISCONNECT)); + assert_eq!( + &h1.inner.as_ref().unwrap().io.write_buf[..26], + b"HTTP/1.1 400 Bad Request\r\n" + ); ok::<_, ()>(()) })); } diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 382ebe5f1..374b8bec9 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -41,8 +41,6 @@ impl Default for MessageEncoder { pub(crate) trait MessageType: Sized { fn status(&self) -> Option; - // fn connection_type(&self) -> Option; - fn headers(&self) -> &HeaderMap; fn chunked(&self) -> bool; @@ -171,10 +169,6 @@ impl MessageType for Response<()> { self.head().chunked() } - //fn connection_type(&self) -> Option { - // self.head().ctype - //} - fn headers(&self) -> &HeaderMap { &self.head().headers } diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 0c8c2eef6..c3fed133d 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -51,18 +51,6 @@ impl Response { } } - #[inline] - pub(crate) fn empty(status: StatusCode) -> Response<()> { - let mut head: Message = Message::new(); - head.status = status; - - Response { - head, - body: ResponseBody::Body(()), - error: None, - } - } - /// Constructs an error response #[inline] pub fn from_error(error: Error) -> Response { diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index f259e3021..57ab6ec25 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use std::{fmt, io}; -use actix_codec::{AsyncRead, AsyncWrite, Framed, FramedParts}; +use actix_codec::{AsyncRead, AsyncWrite}; use actix_server_config::{Io as ServerIo, Protocol, ServerConfig as SrvConfig}; use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; @@ -375,13 +375,14 @@ where self.state = State::Handshake(Some((server::handshake(io), cfg, srv))); } else { - let framed = Framed::from_parts(FramedParts::with_read_buf( + self.state = State::H1(h1::Dispatcher::with_timeout( io, h1::Codec::new(cfg.clone()), + cfg, buf, - )); - self.state = State::H1(h1::Dispatcher::with_timeout( - framed, cfg, None, srv, expect, + None, + srv, + expect, )) } self.poll() diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index d777d3d1a..da41492f2 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -911,11 +911,11 @@ fn test_h1_service_error() { }); let response = srv.block_on(srv.get("/").send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); + assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); // read response let bytes = srv.load_body(response).unwrap(); - assert!(bytes.is_empty()); + assert_eq!(bytes, Bytes::from_static(b"error")); } #[cfg(feature = "ssl")] diff --git a/tests/test_server.rs b/tests/test_server.rs index 7c91d4fed..76ce2c76e 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -770,7 +770,7 @@ fn test_reading_deflate_encoding_large_random_ssl() { awc::Connector::new() .timeout(std::time::Duration::from_millis(500)) .ssl(builder.build()) - .service(), + .finish(), ) .finish() }); From 748289f0ffe63a96f74b4739ab2c9d0862c970b6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 6 Apr 2019 15:02:02 -0700 Subject: [PATCH 025/122] use custom headers map; more optimizations --- actix-files/src/named.rs | 4 +- actix-http/Cargo.toml | 1 + actix-http/src/client/h2proto.rs | 2 +- actix-http/src/encoding/decoder.rs | 2 +- actix-http/src/encoding/encoder.rs | 2 +- actix-http/src/h1/codec.rs | 60 ++- actix-http/src/h1/decoder.rs | 19 +- actix-http/src/h1/encoder.rs | 81 ++-- actix-http/src/h2/dispatcher.rs | 2 +- actix-http/src/header/common/cache_control.rs | 2 +- .../src/header/common/content_disposition.rs | 2 +- actix-http/src/header/common/if_range.rs | 4 +- actix-http/src/header/map.rs | 384 ++++++++++++++++++ actix-http/src/header/mod.rs | 21 +- actix-http/src/httpmessage.rs | 4 +- actix-http/src/lib.rs | 3 +- actix-http/src/message.rs | 95 +++-- actix-http/src/request.rs | 5 +- actix-http/src/response.rs | 45 +- actix-http/src/test.rs | 3 +- actix-multipart/src/server.rs | 8 +- actix-web-actors/src/ws.rs | 10 +- awc/src/lib.rs | 6 +- awc/src/request.rs | 4 +- awc/src/response.rs | 6 +- awc/src/ws.rs | 8 +- src/info.rs | 10 +- src/middleware/compress.rs | 2 +- src/middleware/cors.rs | 22 +- src/middleware/defaultheaders.rs | 4 +- src/middleware/logger.rs | 13 +- src/request.rs | 8 +- src/types/form.rs | 2 +- src/types/json.rs | 2 +- src/types/payload.rs | 2 +- 35 files changed, 668 insertions(+), 180 deletions(-) create mode 100644 actix-http/src/header/map.rs diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 717b058cf..1f54c8288 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -344,7 +344,7 @@ impl Responder for NamedFile { // check last modified let not_modified = if !none_match(etag.as_ref(), req) { true - } else if req.headers().contains_key(header::IF_NONE_MATCH) { + } else if req.headers().contains_key(&header::IF_NONE_MATCH) { false } else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) = (last_modified, req.get_header()) @@ -378,7 +378,7 @@ impl Responder for NamedFile { let mut offset = 0; // check for range header - if let Some(ranges) = req.headers().get(header::RANGE) { + if let Some(ranges) = req.headers().get(&header::RANGE) { if let Ok(rangesheader) = ranges.to_str() { if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) { length = rangesvec[0].length; diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 315dcd5ca..fe9b62d14 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -60,6 +60,7 @@ bitflags = "1.0" bytes = "0.4" byteorder = "1.2" derive_more = "0.14" +either = "1.5.2" encoding = "0.2" futures = "0.1" hashbrown = "0.1.8" diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index d45716ab8..222e442c5 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -107,7 +107,7 @@ where let mut head = ResponseHead::default(); head.version = parts.version; head.status = parts.status; - head.headers = parts.headers; + head.headers = parts.headers.into(); Ok((head, payload)) }) diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index 16d15e905..4b56a1b62 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -55,7 +55,7 @@ where #[inline] pub fn from_headers(stream: S, headers: &HeaderMap) -> Decoder { // check content-encoding - let encoding = if let Some(enc) = headers.get(CONTENT_ENCODING) { + let encoding = if let Some(enc) = headers.get(&CONTENT_ENCODING) { if let Ok(enc) = enc.to_str() { ContentEncoding::from(enc) } else { diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 50e9d068b..6537379f5 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -31,7 +31,7 @@ impl Encoder { head: &mut ResponseHead, body: ResponseBody, ) -> ResponseBody> { - let can_encode = !(head.headers().contains_key(CONTENT_ENCODING) + let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING) || head.status == StatusCode::SWITCHING_PROTOCOLS || encoding == ContentEncoding::Identity || encoding == ContentEncoding::Auto); diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index 3834254a2..1f3983adb 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -22,8 +22,8 @@ use crate::response::Response; bitflags! { struct Flags: u8 { const HEAD = 0b0000_0001; - const KEEPALIVE_ENABLED = 0b0000_1000; - const STREAM = 0b0001_0000; + const KEEPALIVE_ENABLED = 0b0000_0010; + const STREAM = 0b0000_0100; } } @@ -39,8 +39,8 @@ pub struct Codec { // encoder part flags: Flags, - headers_size: u32, encoder: encoder::MessageEncoder>, + // headers_size: u32, } impl Default for Codec { @@ -73,7 +73,7 @@ impl Codec { ctype: ConnectionType::Close, flags, - headers_size: 0, + // headers_size: 0, encoder: encoder::MessageEncoder::default(), } } @@ -159,37 +159,33 @@ impl Encoder for Codec { ) -> Result<(), Self::Error> { match item { Message::Item((mut res, length)) => { - if res.head().status == StatusCode::CONTINUE { - dst.extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n"); - } else { - // set response version - res.head_mut().version = self.version; + // set response version + res.head_mut().version = self.version; - // connection status - self.ctype = if let Some(ct) = res.head().ctype() { - if ct == ConnectionType::KeepAlive { - self.ctype - } else { - ct - } - } else { + // connection status + self.ctype = if let Some(ct) = res.head().ctype() { + if ct == ConnectionType::KeepAlive { self.ctype - }; + } else { + ct + } + } else { + self.ctype + }; - // encode message - let len = dst.len(); - self.encoder.encode( - dst, - &mut res, - self.flags.contains(Flags::HEAD), - self.flags.contains(Flags::STREAM), - self.version, - length, - self.ctype, - &self.config, - )?; - self.headers_size = (dst.len() - len) as u32; - } + // encode message + let len = dst.len(); + self.encoder.encode( + dst, + &mut res, + self.flags.contains(Flags::HEAD), + self.flags.contains(Flags::STREAM), + self.version, + length, + self.ctype, + &self.config, + )?; + // self.headers_size = (dst.len() - len) as u32; } Message::Chunk(Some(bytes)) => { self.encoder.encode_chunk(bytes.as_ref(), dst)?; diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 88de9bc6d..417441c6a 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -5,11 +5,12 @@ use actix_codec::Decoder; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll}; use http::header::{HeaderName, HeaderValue}; -use http::{header, HeaderMap, HttpTryFrom, Method, StatusCode, Uri, Version}; +use http::{header, HttpTryFrom, Method, StatusCode, Uri, Version}; use httparse; use log::{debug, error, trace}; use crate::error::ParseError; +use crate::header::HeaderMap; use crate::message::{ConnectionType, ResponseHead}; use crate::request::Request; @@ -630,6 +631,7 @@ mod tests { use super::*; use crate::error::ParseError; + use crate::http::header::{HeaderName, SET_COOKIE}; use crate::httpmessage::HttpMessage; impl PayloadType { @@ -790,7 +792,13 @@ mod tests { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); - assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value"); + assert_eq!( + req.headers() + .get(HeaderName::try_from("test").unwrap()) + .unwrap() + .as_bytes(), + b"value" + ); } #[test] @@ -805,12 +813,11 @@ mod tests { let val: Vec<_> = req .headers() - .get_all("Set-Cookie") - .iter() + .get_all(SET_COOKIE) .map(|v| v.to_str().unwrap().to_owned()) .collect(); - assert_eq!(val[0], "c1=cookie1"); - assert_eq!(val[1], "c2=cookie2"); + assert_eq!(val[1], "c1=cookie1"); + assert_eq!(val[0], "c2=cookie2"); } #[test] diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 374b8bec9..9a81fb2b8 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -6,15 +6,16 @@ use std::str::FromStr; use std::{cmp, fmt, io, mem}; use bytes::{BufMut, Bytes, BytesMut}; -use http::header::{ - HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, -}; -use http::{HeaderMap, Method, StatusCode, Version}; use crate::body::BodySize; use crate::config::ServiceConfig; +use crate::header::map; use crate::header::ContentEncoding; use crate::helpers; +use crate::http::header::{ + HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, +}; +use crate::http::{HeaderMap, Method, StatusCode, Version}; use crate::message::{ConnectionType, Head, RequestHead, ResponseHead}; use crate::request::Request; use crate::response::Response; @@ -109,7 +110,7 @@ pub(crate) trait MessageType: Sized { let mut has_date = false; let mut remaining = dst.remaining_mut(); let mut buf = unsafe { &mut *(dst.bytes_mut() as *mut [u8]) }; - for (key, value) in self.headers() { + for (key, value) in self.headers().inner.iter() { match *key { CONNECTION => continue, TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => continue, @@ -118,31 +119,59 @@ pub(crate) trait MessageType: Sized { } _ => (), } - - let v = value.as_ref(); let k = key.as_str().as_bytes(); - let len = k.len() + v.len() + 4; - if len > remaining { - unsafe { - dst.advance_mut(pos); + match value { + map::Value::One(ref val) => { + let v = val.as_ref(); + let len = k.len() + v.len() + 4; + if len > remaining { + unsafe { + dst.advance_mut(pos); + } + pos = 0; + dst.reserve(len); + remaining = dst.remaining_mut(); + unsafe { + buf = &mut *(dst.bytes_mut() as *mut _); + } + } + buf[pos..pos + k.len()].copy_from_slice(k); + pos += k.len(); + buf[pos..pos + 2].copy_from_slice(b": "); + pos += 2; + buf[pos..pos + v.len()].copy_from_slice(v); + pos += v.len(); + buf[pos..pos + 2].copy_from_slice(b"\r\n"); + pos += 2; + remaining -= len; } - pos = 0; - dst.reserve(len); - remaining = dst.remaining_mut(); - unsafe { - buf = &mut *(dst.bytes_mut() as *mut _); + map::Value::Multi(ref vec) => { + for val in vec { + let v = val.as_ref(); + let len = k.len() + v.len() + 4; + if len > remaining { + unsafe { + dst.advance_mut(pos); + } + pos = 0; + dst.reserve(len); + remaining = dst.remaining_mut(); + unsafe { + buf = &mut *(dst.bytes_mut() as *mut _); + } + } + buf[pos..pos + k.len()].copy_from_slice(k); + pos += k.len(); + buf[pos..pos + 2].copy_from_slice(b": "); + pos += 2; + buf[pos..pos + v.len()].copy_from_slice(v); + pos += v.len(); + buf[pos..pos + 2].copy_from_slice(b"\r\n"); + pos += 2; + remaining -= len; + } } } - - buf[pos..pos + k.len()].copy_from_slice(k); - pos += k.len(); - buf[pos..pos + 2].copy_from_slice(b": "); - pos += 2; - buf[pos..pos + v.len()].copy_from_slice(v); - pos += v.len(); - buf[pos..pos + 2].copy_from_slice(b"\r\n"); - pos += 2; - remaining -= len; } unsafe { dst.advance_mut(pos); diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index e00996048..cbb74f609 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -116,7 +116,7 @@ where head.uri = parts.uri; head.method = parts.method; head.version = parts.version; - head.headers = parts.headers; + head.headers = parts.headers.into(); tokio_current_thread::spawn(ServiceResponse:: { state: ServiceResponseState::ServiceCall( self.service.call(req), diff --git a/actix-http/src/header/common/cache_control.rs b/actix-http/src/header/common/cache_control.rs index 0b79ea7c0..55774619b 100644 --- a/actix-http/src/header/common/cache_control.rs +++ b/actix-http/src/header/common/cache_control.rs @@ -64,7 +64,7 @@ impl Header for CacheControl { where T: crate::HttpMessage, { - let directives = from_comma_delimited(msg.headers().get_all(Self::name()))?; + let directives = from_comma_delimited(msg.headers().get_all(&Self::name()))?; if !directives.is_empty() { Ok(CacheControl(directives)) } else { diff --git a/actix-http/src/header/common/content_disposition.rs b/actix-http/src/header/common/content_disposition.rs index 700400dad..badf307a0 100644 --- a/actix-http/src/header/common/content_disposition.rs +++ b/actix-http/src/header/common/content_disposition.rs @@ -444,7 +444,7 @@ impl Header for ContentDisposition { } fn parse(msg: &T) -> Result { - if let Some(h) = msg.headers().get(Self::name()) { + if let Some(h) = msg.headers().get(&Self::name()) { Self::from_raw(&h) } else { Err(crate::error::ParseError::Header) diff --git a/actix-http/src/header/common/if_range.rs b/actix-http/src/header/common/if_range.rs index 2140ccbb3..e910ebd96 100644 --- a/actix-http/src/header/common/if_range.rs +++ b/actix-http/src/header/common/if_range.rs @@ -73,12 +73,12 @@ impl Header for IfRange { T: HttpMessage, { let etag: Result = - from_one_raw_str(msg.headers().get(header::IF_RANGE)); + from_one_raw_str(msg.headers().get(&header::IF_RANGE)); if let Ok(etag) = etag { return Ok(IfRange::EntityTag(etag)); } let date: Result = - from_one_raw_str(msg.headers().get(header::IF_RANGE)); + from_one_raw_str(msg.headers().get(&header::IF_RANGE)); if let Ok(date) = date { return Ok(IfRange::Date(date)); } diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs new file mode 100644 index 000000000..694aed02a --- /dev/null +++ b/actix-http/src/header/map.rs @@ -0,0 +1,384 @@ +use either::Either; +use hashbrown::hash_map::{self, Entry}; +use hashbrown::HashMap; +use http::header::{HeaderName, HeaderValue}; +use http::HttpTryFrom; + +/// A set of HTTP headers +/// +/// `HeaderMap` is an multimap of [`HeaderName`] to values. +/// +/// [`HeaderName`]: struct.HeaderName.html +#[derive(Debug)] +pub struct HeaderMap { + pub(crate) inner: HashMap, +} + +#[derive(Debug)] +pub(crate) enum Value { + One(HeaderValue), + Multi(Vec), +} + +impl Value { + fn get(&self) -> &HeaderValue { + match self { + Value::One(ref val) => val, + Value::Multi(ref val) => &val[0], + } + } + + fn get_mut(&mut self) -> &mut HeaderValue { + match self { + Value::One(ref mut val) => val, + Value::Multi(ref mut val) => &mut val[0], + } + } + + fn append(&mut self, val: HeaderValue) { + match self { + Value::One(_) => { + let data = std::mem::replace(self, Value::Multi(vec![val])); + match data { + Value::One(val) => self.append(val), + Value::Multi(_) => unreachable!(), + } + } + Value::Multi(ref mut vec) => vec.push(val), + } + } +} + +impl HeaderMap { + /// Create an empty `HeaderMap`. + /// + /// The map will be created without any capacity. This function will not + /// allocate. + pub fn new() -> Self { + HeaderMap { + inner: HashMap::new(), + } + } + + /// Create an empty `HeaderMap` with the specified capacity. + /// + /// The returned map will allocate internal storage in order to hold about + /// `capacity` elements without reallocating. However, this is a "best + /// effort" as there are usage patterns that could cause additional + /// allocations before `capacity` headers are stored in the map. + /// + /// More capacity than requested may be allocated. + pub fn with_capacity(capacity: usize) -> HeaderMap { + HeaderMap { + inner: HashMap::with_capacity(capacity), + } + } + + /// Returns the number of keys stored in the map. + /// + /// This number could be be less than or equal to actual headers stored in + /// the map. + pub fn len(&self) -> usize { + self.inner.len() + } + + /// Returns true if the map contains no elements. + pub fn is_empty(&self) -> bool { + self.inner.len() == 0 + } + + /// Clears the map, removing all key-value pairs. Keeps the allocated memory + /// for reuse. + pub fn clear(&mut self) { + self.inner.clear(); + } + + /// Returns the number of headers the map can hold without reallocating. + /// + /// This number is an approximation as certain usage patterns could cause + /// additional allocations before the returned capacity is filled. + pub fn capacity(&self) -> usize { + self.inner.capacity() + } + + /// Reserves capacity for at least `additional` more headers to be inserted + /// into the `HeaderMap`. + /// + /// The header map may reserve more space to avoid frequent reallocations. + /// Like with `with_capacity`, this will be a "best effort" to avoid + /// allocations until `additional` more headers are inserted. Certain usage + /// patterns could cause additional allocations before the number is + /// reached. + pub fn reserve(&mut self, additional: usize) { + self.inner.reserve(additional) + } + + /// Returns a reference to the value associated with the key. + /// + /// If there are multiple values associated with the key, then the first one + /// is returned. Use `get_all` to get all values associated with a given + /// key. Returns `None` if there are no values associated with the key. + pub fn get(&self, name: N) -> Option<&HeaderValue> { + self.get2(name).map(|v| v.get()) + } + + fn get2(&self, name: N) -> Option<&Value> { + match name.as_name() { + Either::Left(name) => self.inner.get(name), + Either::Right(s) => { + if let Ok(name) = HeaderName::try_from(s) { + self.inner.get(&name) + } else { + None + } + } + } + } + + /// Returns a view of all values associated with a key. + /// + /// The returned view does not incur any allocations and allows iterating + /// the values associated with the key. See [`GetAll`] for more details. + /// Returns `None` if there are no values associated with the key. + /// + /// [`GetAll`]: struct.GetAll.html + pub fn get_all(&self, name: N) -> GetAll { + GetAll { + idx: 0, + item: self.get2(name), + } + } + + /// Returns a mutable reference to the value associated with the key. + /// + /// If there are multiple values associated with the key, then the first one + /// is returned. Use `entry` to get all values associated with a given + /// key. Returns `None` if there are no values associated with the key. + pub fn get_mut(&mut self, name: N) -> Option<&mut HeaderValue> { + match name.as_name() { + Either::Left(name) => self.inner.get_mut(name).map(|v| v.get_mut()), + Either::Right(s) => { + if let Ok(name) = HeaderName::try_from(s) { + self.inner.get_mut(&name).map(|v| v.get_mut()) + } else { + None + } + } + } + } + + /// Returns true if the map contains a value for the specified key. + pub fn contains_key(&self, key: N) -> bool { + match key.as_name() { + Either::Left(name) => self.inner.contains_key(name), + Either::Right(s) => { + if let Ok(name) = HeaderName::try_from(s) { + self.inner.contains_key(&name) + } else { + false + } + } + } + } + + /// An iterator visiting all key-value pairs. + /// + /// The iteration order is arbitrary, but consistent across platforms for + /// the same crate version. Each key will be yielded once per associated + /// value. So, if a key has 3 associated values, it will be yielded 3 times. + pub fn iter(&self) -> Iter { + Iter::new(self.inner.iter()) + } + + /// An iterator visiting all keys. + /// + /// The iteration order is arbitrary, but consistent across platforms for + /// the same crate version. Each key will be yielded only once even if it + /// has multiple associated values. + pub fn keys(&self) -> Keys { + Keys(self.inner.keys()) + } + + /// Inserts a key-value pair into the map. + /// + /// If the map did not previously have this key present, then `None` is + /// returned. + /// + /// If the map did have this key present, the new value is associated with + /// the key and all previous values are removed. **Note** that only a single + /// one of the previous values is returned. If there are multiple values + /// that have been previously associated with the key, then the first one is + /// returned. See `insert_mult` on `OccupiedEntry` for an API that returns + /// all values. + /// + /// The key is not updated, though; this matters for types that can be `==` + /// without being identical. + pub fn insert(&mut self, key: HeaderName, val: HeaderValue) { + let _ = self.inner.insert(key, Value::One(val)); + } + + /// Inserts a key-value pair into the map. + /// + /// If the map did not previously have this key present, then `false` is + /// returned. + /// + /// If the map did have this key present, the new value is pushed to the end + /// of the list of values currently associated with the key. The key is not + /// updated, though; this matters for types that can be `==` without being + /// identical. + pub fn append(&mut self, key: HeaderName, value: HeaderValue) { + match self.inner.entry(key) { + Entry::Occupied(mut entry) => entry.get_mut().append(value), + Entry::Vacant(entry) => { + entry.insert(Value::One(value)); + } + } + } + + /// Removes all headers for a particular header name from the map. + pub fn remove(&mut self, key: N) { + match key.as_name() { + Either::Left(name) => { + let _ = self.inner.remove(name); + } + Either::Right(s) => { + if let Ok(name) = HeaderName::try_from(s) { + let _ = self.inner.remove(&name); + } + } + } + } +} + +#[doc(hidden)] +pub trait AsName { + fn as_name(&self) -> Either<&HeaderName, &str>; +} + +impl AsName for HeaderName { + fn as_name(&self) -> Either<&HeaderName, &str> { + Either::Left(self) + } +} + +impl<'a> AsName for &'a HeaderName { + fn as_name(&self) -> Either<&HeaderName, &str> { + Either::Left(self) + } +} + +impl<'a> AsName for &'a str { + fn as_name(&self) -> Either<&HeaderName, &str> { + Either::Right(self) + } +} + +impl AsName for String { + fn as_name(&self) -> Either<&HeaderName, &str> { + Either::Right(self.as_str()) + } +} + +impl<'a> AsName for &'a String { + fn as_name(&self) -> Either<&HeaderName, &str> { + Either::Right(self.as_str()) + } +} + +pub struct GetAll<'a> { + idx: usize, + item: Option<&'a Value>, +} + +impl<'a> Iterator for GetAll<'a> { + type Item = &'a HeaderValue; + + #[inline] + fn next(&mut self) -> Option<&'a HeaderValue> { + if let Some(ref val) = self.item { + match val { + Value::One(ref val) => { + self.item.take(); + Some(val) + } + Value::Multi(ref vec) => { + if self.idx < vec.len() { + let item = Some(&vec[self.idx]); + self.idx += 1; + item + } else { + self.item.take(); + None + } + } + } + } else { + None + } + } +} + +pub struct Keys<'a>(hash_map::Keys<'a, HeaderName, Value>); + +impl<'a> Iterator for Keys<'a> { + type Item = &'a HeaderName; + + #[inline] + fn next(&mut self) -> Option<&'a HeaderName> { + self.0.next() + } +} + +impl<'a> IntoIterator for &'a HeaderMap { + type Item = (&'a HeaderName, &'a HeaderValue); + type IntoIter = Iter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +pub struct Iter<'a> { + idx: usize, + current: Option<(&'a HeaderName, &'a Vec)>, + iter: hash_map::Iter<'a, HeaderName, Value>, +} + +impl<'a> Iter<'a> { + fn new(iter: hash_map::Iter<'a, HeaderName, Value>) -> Self { + Self { + iter, + idx: 0, + current: None, + } + } +} + +impl<'a> Iterator for Iter<'a> { + type Item = (&'a HeaderName, &'a HeaderValue); + + #[inline] + fn next(&mut self) -> Option<(&'a HeaderName, &'a HeaderValue)> { + if let Some(ref mut item) = self.current { + if self.idx < item.1.len() { + let item = (item.0, &item.1[self.idx]); + self.idx += 1; + return Some(item); + } else { + self.idx = 0; + self.current.take(); + } + } + if let Some(item) = self.iter.next() { + match item.1 { + Value::One(ref value) => Some((item.0, value)), + Value::Multi(ref vec) => { + self.current = Some((item.0, vec)); + self.next() + } + } + } else { + None + } + } +} diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index deedd693f..620183476 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -4,7 +4,6 @@ use std::{fmt, str::FromStr}; use bytes::{Bytes, BytesMut}; -use http::header::GetAll; use http::Error as HttpError; use mime::Mime; @@ -14,12 +13,17 @@ use crate::error::ParseError; use crate::httpmessage::HttpMessage; mod common; +pub(crate) mod map; mod shared; #[doc(hidden)] pub use self::common::*; #[doc(hidden)] pub use self::shared::*; +#[doc(hidden)] +pub use self::map::GetAll; +pub use self::map::HeaderMap; + /// A trait for any object that will represent a header field and value. pub trait Header where @@ -220,8 +224,8 @@ impl fmt::Write for Writer { #[inline] #[doc(hidden)] /// Reads a comma-delimited raw header into a Vec. -pub fn from_comma_delimited( - all: GetAll, +pub fn from_comma_delimited<'a, I: Iterator + 'a, T: FromStr>( + all: I, ) -> Result, ParseError> { let mut result = Vec::new(); for h in all { @@ -379,6 +383,17 @@ pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result fmt::Display::fmt(&encoded, f) } +/// Convert http::HeaderMap to a HeaderMap +impl From for HeaderMap { + fn from(map: http::HeaderMap) -> HeaderMap { + let mut new_map = HeaderMap::with_capacity(map.capacity()); + for (h, v) in map.iter() { + new_map.append(h.clone(), v.clone()); + } + new_map + } +} + mod percent_encoding_http { use percent_encoding::{self, define_encode_set}; diff --git a/actix-http/src/httpmessage.rs b/actix-http/src/httpmessage.rs index 7a2db52d6..1534973a8 100644 --- a/actix-http/src/httpmessage.rs +++ b/actix-http/src/httpmessage.rs @@ -4,13 +4,13 @@ use std::str; use encoding::all::UTF_8; use encoding::label::encoding_from_whatwg_label; use encoding::EncodingRef; -use http::{header, HeaderMap}; +use http::header; use mime::Mime; use crate::cookie::Cookie; use crate::error::{ContentTypeError, CookieParseError, ParseError}; use crate::extensions::Extensions; -use crate::header::Header; +use crate::header::{Header, HeaderMap}; use crate::payload::Payload; struct Cookies(Vec>); diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index ed3669e85..5879e1915 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -45,10 +45,11 @@ pub mod http { // re-exports pub use http::header::{HeaderName, HeaderValue}; pub use http::uri::PathAndQuery; - pub use http::{uri, Error, HeaderMap, HttpTryFrom, Uri}; + pub use http::{uri, Error, HttpTryFrom, Uri}; pub use http::{Method, StatusCode, Version}; pub use crate::cookie::{Cookie, CookieBuilder}; + pub use crate::header::HeaderMap; /// Various http headers pub mod header { diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 2fdb28e40..25bc55baf 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -5,7 +5,8 @@ use std::rc::Rc; use bitflags::bitflags; use crate::extensions::Extensions; -use crate::http::{header, HeaderMap, Method, StatusCode, Uri, Version}; +use crate::header::HeaderMap; +use crate::http::{header, Method, StatusCode, Uri, Version}; /// Represents various types of connection #[derive(Copy, Clone, PartialEq, Debug)] @@ -174,7 +175,7 @@ impl Default for ResponseHead { ResponseHead { version: Version::default(), status: StatusCode::OK, - headers: HeaderMap::with_capacity(16), + headers: HeaderMap::with_capacity(12), reason: None, flags: Flags::empty(), extensions: RefCell::new(Extensions::new()), @@ -182,18 +183,6 @@ impl Default for ResponseHead { } } -impl Head for ResponseHead { - fn clear(&mut self) { - self.reason = None; - self.flags = Flags::empty(); - self.headers.clear(); - } - - fn pool() -> &'static MessagePool { - RESPONSE_POOL.with(|p| *p) - } -} - impl ResponseHead { /// Message extensions #[inline] @@ -300,7 +289,6 @@ impl ResponseHead { pub struct Message { head: Rc, - pool: &'static MessagePool, } impl Message { @@ -314,7 +302,6 @@ impl Clone for Message { fn clone(&self) -> Self { Message { head: self.head.clone(), - pool: self.pool, } } } @@ -336,17 +323,52 @@ impl std::ops::DerefMut for Message { impl Drop for Message { fn drop(&mut self) { if Rc::strong_count(&self.head) == 1 { - self.pool.release(self.head.clone()); + T::pool().release(self.head.clone()); } } } +pub(crate) struct BoxedResponseHead { + head: Option>, +} + +impl BoxedResponseHead { + /// Get new message from the pool of objects + pub fn new() -> Self { + RESPONSE_POOL.with(|p| p.get_message()) + } +} + +impl std::ops::Deref for BoxedResponseHead { + type Target = ResponseHead; + + fn deref(&self) -> &Self::Target { + self.head.as_ref().unwrap() + } +} + +impl std::ops::DerefMut for BoxedResponseHead { + fn deref_mut(&mut self) -> &mut Self::Target { + self.head.as_mut().unwrap() + } +} + +impl Drop for BoxedResponseHead { + fn drop(&mut self) { + RESPONSE_POOL.with(|p| p.release(self.head.take().unwrap())) + } +} + #[doc(hidden)] /// Request's objects pool pub struct MessagePool(RefCell>>); +#[doc(hidden)] +/// Request's objects pool +pub struct BoxedResponsePool(RefCell>>); + thread_local!(static REQUEST_POOL: &'static MessagePool = MessagePool::::create()); -thread_local!(static RESPONSE_POOL: &'static MessagePool = MessagePool::::create()); +thread_local!(static RESPONSE_POOL: &'static BoxedResponsePool = BoxedResponsePool::create()); impl MessagePool { fn create() -> &'static MessagePool { @@ -361,14 +383,10 @@ impl MessagePool { if let Some(r) = Rc::get_mut(&mut msg) { r.clear(); } - Message { - head: msg, - pool: self, - } + Message { head: msg } } else { Message { head: Rc::new(T::default()), - pool: self, } } } @@ -382,3 +400,34 @@ impl MessagePool { } } } + +impl BoxedResponsePool { + fn create() -> &'static BoxedResponsePool { + let pool = BoxedResponsePool(RefCell::new(VecDeque::with_capacity(128))); + Box::leak(Box::new(pool)) + } + + /// Get message from the pool + #[inline] + fn get_message(&'static self) -> BoxedResponseHead { + if let Some(mut head) = self.0.borrow_mut().pop_front() { + head.reason = None; + head.headers.clear(); + head.flags = Flags::empty(); + BoxedResponseHead { head: Some(head) } + } else { + BoxedResponseHead { + head: Some(Box::new(ResponseHead::default())), + } + } + } + + #[inline] + /// Release request instance + fn release(&self, msg: Box) { + let v = &mut self.0.borrow_mut(); + if v.len() < 128 { + v.push_front(msg); + } + } +} diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index a645c7aeb..468b4e337 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -1,9 +1,10 @@ use std::cell::{Ref, RefMut}; use std::fmt; -use http::{header, HeaderMap, Method, Uri, Version}; +use http::{header, Method, Uri, Version}; use crate::extensions::Extensions; +use crate::header::HeaderMap; use crate::httpmessage::HttpMessage; use crate::message::{Message, RequestHead}; use crate::payload::{Payload, PayloadStream}; @@ -161,7 +162,7 @@ impl

fmt::Debug for Request

{ writeln!(f, " query: ?{:?}", q)?; } writeln!(f, " headers:")?; - for (key, val) in self.headers().iter() { + for (key, val) in self.headers() { writeln!(f, " {:?}: {:?}", key, val)?; } Ok(()) diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index c3fed133d..707c9af63 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -6,8 +6,6 @@ use std::{fmt, io, str}; use bytes::{BufMut, Bytes, BytesMut}; use futures::future::{ok, FutureResult, IntoFuture}; use futures::Stream; -use http::header::{self, HeaderName, HeaderValue}; -use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode}; use serde::Serialize; use serde_json; @@ -16,11 +14,13 @@ use crate::cookie::{Cookie, CookieJar}; use crate::error::Error; use crate::extensions::Extensions; use crate::header::{Header, IntoHeaderValue}; -use crate::message::{ConnectionType, Message, ResponseHead}; +use crate::http::header::{self, HeaderName, HeaderValue}; +use crate::http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode}; +use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead}; /// An HTTP Response pub struct Response { - head: Message, + head: BoxedResponseHead, body: ResponseBody, error: Option, } @@ -41,7 +41,7 @@ impl Response { /// Constructs a response #[inline] pub fn new(status: StatusCode) -> Response { - let mut head: Message = Message::new(); + let mut head = BoxedResponseHead::new(); head.status = status; Response { @@ -93,7 +93,7 @@ impl Response { /// Constructs a response with body #[inline] pub fn with_body(status: StatusCode, body: B) -> Response { - let mut head: Message = Message::new(); + let mut head = BoxedResponseHead::new(); head.status = status; Response { head, @@ -136,7 +136,7 @@ impl Response { #[inline] pub fn cookies(&self) -> CookieIter { CookieIter { - iter: self.head.headers.get_all(header::SET_COOKIE).iter(), + iter: self.head.headers.get_all(header::SET_COOKIE), } } @@ -158,7 +158,6 @@ impl Response { let h = &mut self.head.headers; let vals: Vec = h .get_all(header::SET_COOKIE) - .iter() .map(|v| v.to_owned()) .collect(); h.remove(header::SET_COOKIE); @@ -286,7 +285,7 @@ impl IntoFuture for Response { } pub struct CookieIter<'a> { - iter: header::ValueIter<'a, HeaderValue>, + iter: header::GetAll<'a>, } impl<'a> Iterator for CookieIter<'a> { @@ -320,7 +319,7 @@ impl<'a> io::Write for Writer<'a> { /// This type can be used to construct an instance of `Response` through a /// builder-like pattern. pub struct ResponseBuilder { - head: Option>, + head: Option, err: Option, cookies: Option, } @@ -328,7 +327,7 @@ pub struct ResponseBuilder { impl ResponseBuilder { /// Create response builder pub fn new(status: StatusCode) -> Self { - let mut head: Message = Message::new(); + let mut head = BoxedResponseHead::new(); head.status = status; ResponseBuilder { @@ -701,13 +700,13 @@ impl ResponseBuilder { #[inline] fn parts<'a>( - parts: &'a mut Option>, + parts: &'a mut Option, err: &Option, -) -> Option<&'a mut Message> { +) -> Option<&'a mut ResponseHead> { if err.is_some() { return None; } - parts.as_mut() + parts.as_mut().map(|r| &mut **r) } /// Convert `Response` to a `ResponseBuilder`. Body get dropped. @@ -740,7 +739,7 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { let mut jar: Option = None; let cookies = CookieIter { - iter: head.headers.get_all(header::SET_COOKIE).iter(), + iter: head.headers.get_all(header::SET_COOKIE), }; for c in cookies { if let Some(ref mut j) = jar { @@ -752,11 +751,11 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { } } - let mut msg: Message = Message::new(); + let mut msg = BoxedResponseHead::new(); msg.version = head.version; msg.status = head.status; msg.reason = head.reason; - msg.headers = head.headers.clone(); + // msg.headers = head.headers.clone(); msg.no_chunking(!head.chunked()); ResponseBuilder { @@ -845,7 +844,7 @@ impl From for Response { mod tests { use super::*; use crate::body::Body; - use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; + use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE, SET_COOKIE}; #[test] fn test_debug() { @@ -876,13 +875,12 @@ mod tests { .max_age(time::Duration::days(1)) .finish(), ) - .del_cookie(&cookies[0]) + .del_cookie(&cookies[1]) .finish(); let mut val: Vec<_> = resp .headers() - .get_all("Set-Cookie") - .iter() + .get_all(SET_COOKIE) .map(|v| v.to_str().unwrap().to_owned()) .collect(); val.sort(); @@ -911,9 +909,9 @@ mod tests { let mut iter = r.cookies(); let v = iter.next().unwrap(); - assert_eq!((v.name(), v.value()), ("original", "val100")); - let v = iter.next().unwrap(); assert_eq!((v.name(), v.value()), ("cookie3", "val300")); + let v = iter.next().unwrap(); + assert_eq!((v.name(), v.value()), ("original", "val100")); } #[test] @@ -1049,6 +1047,7 @@ mod tests { resp.headers().get(CONTENT_TYPE).unwrap(), HeaderValue::from_static("application/octet-stream") ); + assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().get_ref(), b"test"); } diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 023161246..302d75d73 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -4,10 +4,11 @@ use std::str::FromStr; use bytes::Bytes; use http::header::{self, HeaderName, HeaderValue}; -use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; +use http::{HttpTryFrom, Method, Uri, Version}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use crate::cookie::{Cookie, CookieJar}; +use crate::header::HeaderMap; use crate::header::{Header, IntoHeaderValue}; use crate::payload::Payload; use crate::Request; diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index c1536af60..39559b082 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -92,7 +92,7 @@ impl Multipart { /// Extract boundary info from headers. fn boundary(headers: &HeaderMap) -> Result { - if let Some(content_type) = headers.get(header::CONTENT_TYPE) { + if let Some(content_type) = headers.get(&header::CONTENT_TYPE) { if let Ok(content_type) = content_type.to_str() { if let Ok(ct) = content_type.parse::() { if let Some(boundary) = ct.get_param(mime::BOUNDARY) { @@ -334,7 +334,7 @@ impl InnerMultipart { // content type let mut mt = mime::APPLICATION_OCTET_STREAM; - if let Some(content_type) = headers.get(header::CONTENT_TYPE) { + if let Some(content_type) = headers.get(&header::CONTENT_TYPE) { if let Ok(content_type) = content_type.to_str() { if let Ok(ct) = content_type.parse::() { mt = ct; @@ -427,7 +427,7 @@ impl Field { pub fn content_disposition(&self) -> Option { // RFC 7578: 'Each part MUST contain a Content-Disposition header field // where the disposition type is "form-data".' - if let Some(content_disposition) = self.headers.get(header::CONTENT_DISPOSITION) + if let Some(content_disposition) = self.headers.get(&header::CONTENT_DISPOSITION) { ContentDisposition::from_raw(content_disposition).ok() } else { @@ -480,7 +480,7 @@ impl InnerField { boundary: String, headers: &HeaderMap, ) -> Result { - let len = if let Some(len) = headers.get(header::CONTENT_LENGTH) { + let len = if let Some(len) = headers.get(&header::CONTENT_LENGTH) { if let Ok(s) = len.to_str() { if let Ok(len) = s.parse::() { Some(len) diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 642222560..0ef3c9169 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -50,7 +50,7 @@ pub fn handshake(req: &HttpRequest) -> Result Result Result, { let mut req = self.request(head.method.clone(), url); - for (key, value) in &head.headers { + for (key, value) in head.headers.iter() { req = req.set_header_if_none(key.clone(), value.clone()); } req @@ -187,7 +187,7 @@ impl Client { Uri: HttpTryFrom, { let mut req = ws::WebsocketsRequest::new(url, self.0.clone()); - for (key, value) in &self.0.headers { + for (key, value) in self.0.headers.iter() { req.head.headers.insert(key.clone(), value.clone()); } req diff --git a/awc/src/request.rs b/awc/src/request.rs index 32ab7d78f..09a7eecc4 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -396,7 +396,7 @@ impl ClientRequest { if self.default_headers { // set request host header if let Some(host) = self.head.uri.host() { - if !self.head.headers.contains_key(header::HOST) { + if !self.head.headers.contains_key(&header::HOST) { let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); let _ = match self.head.uri.port_u16() { @@ -414,7 +414,7 @@ impl ClientRequest { } // user agent - if !self.head.headers.contains_key(header::USER_AGENT) { + 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"))), diff --git a/awc/src/response.rs b/awc/src/response.rs index b6d7bba65..b9b007b87 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -46,7 +46,7 @@ impl HttpMessage for ClientResponse { if self.extensions().get::().is_none() { let mut cookies = Vec::new(); - for hdr in self.headers().get_all(SET_COOKIE) { + for hdr in self.headers().get_all(&SET_COOKIE) { let s = std::str::from_utf8(hdr.as_bytes()) .map_err(CookieParseError::from)?; cookies.push(Cookie::parse_encoded(s)?.into_owned()); @@ -160,7 +160,7 @@ where /// Create `MessageBody` for request. pub fn new(res: &mut ClientResponse) -> MessageBody { let mut len = None; - if let Some(l) = res.headers().get(CONTENT_LENGTH) { + if let Some(l) = res.headers().get(&CONTENT_LENGTH) { if let Ok(s) = l.to_str() { if let Ok(l) = s.parse::() { len = Some(l) @@ -254,7 +254,7 @@ where } let mut len = None; - if let Some(l) = req.headers().get(CONTENT_LENGTH) { + if let Some(l) = req.headers().get(&CONTENT_LENGTH) { if let Ok(s) = l.to_str() { if let Ok(l) = s.parse::() { len = Some(l) diff --git a/awc/src/ws.rs b/awc/src/ws.rs index a28518983..967820f3f 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -236,7 +236,7 @@ impl WebsocketsRequest { let mut slf = if self.default_headers { // set request host header if let Some(host) = self.head.uri.host() { - if !self.head.headers.contains_key(header::HOST) { + if !self.head.headers.contains_key(&header::HOST) { let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); let _ = match self.head.uri.port_u16() { @@ -324,7 +324,7 @@ impl WebsocketsRequest { return Err(WsClientError::InvalidResponseStatus(head.status)); } // Check for "UPGRADE" to websocket header - let has_hdr = if let Some(hdr) = head.headers.get(header::UPGRADE) { + let has_hdr = if let Some(hdr) = head.headers.get(&header::UPGRADE) { if let Ok(s) = hdr.to_str() { s.to_ascii_lowercase().contains("websocket") } else { @@ -338,7 +338,7 @@ impl WebsocketsRequest { return Err(WsClientError::InvalidUpgradeHeader); } // Check for "CONNECTION" header - if let Some(conn) = head.headers.get(header::CONNECTION) { + if let Some(conn) = head.headers.get(&header::CONNECTION) { if let Ok(s) = conn.to_str() { if !s.to_ascii_lowercase().contains("upgrade") { log::trace!("Invalid connection header: {}", s); @@ -355,7 +355,7 @@ impl WebsocketsRequest { return Err(WsClientError::MissingConnectionHeader); } - if let Some(hdr_key) = head.headers.get(header::SEC_WEBSOCKET_ACCEPT) { + if let Some(hdr_key) = head.headers.get(&header::SEC_WEBSOCKET_ACCEPT) { let encoded = ws::hash_key(key.as_ref()); if hdr_key.as_bytes() != encoded.as_bytes() { log::trace!( diff --git a/src/info.rs b/src/info.rs index 9a97c3353..ece17bf04 100644 --- a/src/info.rs +++ b/src/info.rs @@ -33,7 +33,7 @@ impl ConnectionInfo { let peer = None; // load forwarded header - for hdr in req.headers.get_all(header::FORWARDED) { + for hdr in req.headers.get_all(&header::FORWARDED) { if let Ok(val) = hdr.to_str() { for pair in val.split(';') { for el in pair.split(',') { @@ -69,7 +69,7 @@ impl ConnectionInfo { if scheme.is_none() { if let Some(h) = req .headers - .get(HeaderName::from_lowercase(X_FORWARDED_PROTO).unwrap()) + .get(&HeaderName::from_lowercase(X_FORWARDED_PROTO).unwrap()) { if let Ok(h) = h.to_str() { scheme = h.split(',').next().map(|v| v.trim()); @@ -87,14 +87,14 @@ impl ConnectionInfo { if host.is_none() { if let Some(h) = req .headers - .get(HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap()) + .get(&HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap()) { if let Ok(h) = h.to_str() { host = h.split(',').next().map(|v| v.trim()); } } if host.is_none() { - if let Some(h) = req.headers.get(header::HOST) { + if let Some(h) = req.headers.get(&header::HOST) { host = h.to_str().ok(); } if host.is_none() { @@ -110,7 +110,7 @@ impl ConnectionInfo { if remote.is_none() { if let Some(h) = req .headers - .get(HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap()) + .get(&HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap()) { if let Ok(h) = h.to_str() { remote = h.split(',').next().map(|v| v.trim()); diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index ed3943711..a4b6a4608 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -109,7 +109,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 { diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index f003ac95b..27d24b432 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -584,7 +584,7 @@ struct Inner { impl Inner { fn validate_origin(&self, req: &RequestHead) -> Result<(), CorsError> { - if let Some(hdr) = req.headers().get(header::ORIGIN) { + if let Some(hdr) = req.headers().get(&header::ORIGIN) { if let Ok(origin) = hdr.to_str() { return match self.origins { AllOrSome::All => Ok(()), @@ -608,7 +608,7 @@ impl Inner { AllOrSome::All => { if self.send_wildcard { Some(HeaderValue::from_static("*")) - } else if let Some(origin) = req.headers().get(header::ORIGIN) { + } else if let Some(origin) = req.headers().get(&header::ORIGIN) { Some(origin.clone()) } else { None @@ -617,7 +617,7 @@ impl Inner { AllOrSome::Some(ref origins) => { if let Some(origin) = req.headers() - .get(header::ORIGIN) + .get(&header::ORIGIN) .filter(|o| match o.to_str() { Ok(os) => origins.contains(os), _ => false, @@ -632,7 +632,7 @@ impl Inner { } fn validate_allowed_method(&self, req: &RequestHead) -> Result<(), CorsError> { - if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) { + if let Some(hdr) = req.headers().get(&header::ACCESS_CONTROL_REQUEST_METHOD) { if let Ok(meth) = hdr.to_str() { if let Ok(method) = Method::try_from(meth) { return self @@ -653,7 +653,7 @@ impl Inner { AllOrSome::All => Ok(()), AllOrSome::Some(ref allowed_headers) => { if let Some(hdr) = - req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) + req.headers().get(&header::ACCESS_CONTROL_REQUEST_HEADERS) { if let Ok(headers) = hdr.to_str() { let mut hdrs = HashSet::new(); @@ -720,7 +720,7 @@ where .unwrap(), ) } else if let Some(hdr) = - req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) + req.headers().get(&header::ACCESS_CONTROL_REQUEST_HEADERS) { Some(hdr.clone()) } else { @@ -759,7 +759,7 @@ where .into_body(); Either::A(ok(req.into_response(res))) - } else if req.headers().contains_key(header::ORIGIN) { + } else if req.headers().contains_key(&header::ORIGIN) { // Only check requests with a origin header. if let Err(e) = self.inner.validate_origin(req.head()) { return Either::A(ok(req.error_response(e))); @@ -790,7 +790,7 @@ where } if inner.vary_header { let value = - if let Some(hdr) = res.headers_mut().get(header::VARY) { + if let Some(hdr) = res.headers_mut().get(&header::VARY) { let mut val: Vec = Vec::with_capacity(hdr.as_bytes().len() + 8); val.extend(hdr.as_bytes()); @@ -893,20 +893,20 @@ mod tests { assert_eq!( &b"*"[..], resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .get(&header::ACCESS_CONTROL_ALLOW_ORIGIN) .unwrap() .as_bytes() ); assert_eq!( &b"3600"[..], resp.headers() - .get(header::ACCESS_CONTROL_MAX_AGE) + .get(&header::ACCESS_CONTROL_MAX_AGE) .unwrap() .as_bytes() ); let hdr = resp .headers() - .get(header::ACCESS_CONTROL_ALLOW_HEADERS) + .get(&header::ACCESS_CONTROL_ALLOW_HEADERS) .unwrap() .to_str() .unwrap(); diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index 72e866dbd..a2bc6f27a 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -131,11 +131,11 @@ where // set response headers for (key, value) in inner.headers.iter() { if !res.headers().contains_key(key) { - res.headers_mut().insert(key, value.clone()); + res.headers_mut().insert(key.clone(), value.clone()); } } // default content-type - if inner.ct && !res.headers().contains_key(CONTENT_TYPE) { + if inner.ct && !res.headers().contains_key(&CONTENT_TYPE) { res.headers_mut().insert( CONTENT_TYPE, HeaderValue::from_static("application/octet-stream"), diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index f4b7517de..aaf381386 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -14,6 +14,7 @@ use time; use crate::dev::{BodySize, MessageBody, ResponseBody}; use crate::error::{Error, Result}; +use crate::http::{HeaderName, HttpTryFrom}; use crate::service::{ServiceRequest, ServiceResponse}; use crate::HttpResponse; @@ -288,8 +289,12 @@ impl Format { if let Some(key) = cap.get(2) { results.push(match cap.get(3).unwrap().as_str() { - "i" => FormatText::RequestHeader(key.as_str().to_owned()), - "o" => FormatText::ResponseHeader(key.as_str().to_owned()), + "i" => FormatText::RequestHeader( + HeaderName::try_from(key.as_str()).unwrap(), + ), + "o" => FormatText::ResponseHeader( + HeaderName::try_from(key.as_str()).unwrap(), + ), "e" => FormatText::EnvironHeader(key.as_str().to_owned()), _ => unreachable!(), }) @@ -332,8 +337,8 @@ pub enum FormatText { TimeMillis, RemoteAddr, UrlPath, - RequestHeader(String), - ResponseHeader(String), + RequestHeader(HeaderName), + ResponseHeader(HeaderName), EnvironHeader(String), } diff --git a/src/request.rs b/src/request.rs index b5ba74122..2eab1ee19 100644 --- a/src/request.rs +++ b/src/request.rs @@ -286,10 +286,10 @@ mod tests { { let cookies = req.cookies().unwrap(); assert_eq!(cookies.len(), 2); - assert_eq!(cookies[0].name(), "cookie1"); - assert_eq!(cookies[0].value(), "value1"); - assert_eq!(cookies[1].name(), "cookie2"); - assert_eq!(cookies[1].value(), "value2"); + assert_eq!(cookies[0].name(), "cookie2"); + assert_eq!(cookies[0].value(), "value2"); + assert_eq!(cookies[1].name(), "cookie1"); + assert_eq!(cookies[1].value(), "value1"); } let cookie = req.cookie("cookie1"); diff --git a/src/types/form.rs b/src/types/form.rs index 812a08e52..b2171e540 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -209,7 +209,7 @@ where }; let mut len = None; - if let Some(l) = req.headers().get(CONTENT_LENGTH) { + if let Some(l) = req.headers().get(&CONTENT_LENGTH) { if let Ok(s) = l.to_str() { if let Ok(l) = s.parse::() { len = Some(l) diff --git a/src/types/json.rs b/src/types/json.rs index c8ed5afd3..f7b94a9bd 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -297,7 +297,7 @@ where } let mut len = None; - if let Some(l) = req.headers().get(CONTENT_LENGTH) { + if let Some(l) = req.headers().get(&CONTENT_LENGTH) { if let Ok(s) = l.to_str() { if let Ok(l) = s.parse::() { len = Some(l) diff --git a/src/types/payload.rs b/src/types/payload.rs index 170b9c627..9cdbd0577 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -313,7 +313,7 @@ where /// Create `MessageBody` for request. pub fn new(req: &mut T) -> HttpMessageBody { let mut len = None; - if let Some(l) = req.headers().get(header::CONTENT_LENGTH) { + if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) { if let Ok(s) = l.to_str() { if let Ok(l) = s.parse::() { len = Some(l) From 68d2203dd6382b1dd7a18bced26d3471f0100b8c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 7 Apr 2019 08:17:29 -0700 Subject: [PATCH 026/122] run travis with stable rust only --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b1b0769e1..20e4c4a52 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,6 @@ cache: matrix: include: - - rust: 1.31.0 - rust: stable - rust: beta - rust: nightly-2019-04-02 From ec09d6fbe651083ef85b3391e967179134f84451 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 7 Apr 2019 10:03:38 -0700 Subject: [PATCH 027/122] optimize encode headers and body split --- actix-http/src/body.rs | 2 +- actix-http/src/h1/encoder.rs | 29 ++++++++++++++--------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index 0d015b2e9..88b6c492f 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -153,7 +153,7 @@ impl MessageBody for Body { if len == 0 { Ok(Async::Ready(None)) } else { - Ok(Async::Ready(Some(bin.split_to(len)))) + Ok(Async::Ready(Some(mem::replace(bin, Bytes::new())))) } } Body::Message(ref mut body) => body.poll_next(), diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 9a81fb2b8..8f98fe67e 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -9,8 +9,7 @@ use bytes::{BufMut, Bytes, BytesMut}; use crate::body::BodySize; use crate::config::ServiceConfig; -use crate::header::map; -use crate::header::ContentEncoding; +use crate::header::{map, ContentEncoding}; use crate::helpers; use crate::http::header::{ HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, @@ -75,32 +74,31 @@ pub(crate) trait MessageType: Sized { match length { BodySize::Stream => { if chunked { - dst.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") + dst.put_slice(b"\r\ntransfer-encoding: chunked\r\n") } else { skip_len = false; - dst.extend_from_slice(b"\r\n"); + dst.put_slice(b"\r\n"); } } BodySize::Empty => { - dst.extend_from_slice(b"\r\ncontent-length: 0\r\n"); + dst.put_slice(b"\r\ncontent-length: 0\r\n"); } BodySize::Sized(len) => helpers::write_content_length(len, dst), BodySize::Sized64(len) => { - dst.extend_from_slice(b"\r\ncontent-length: "); - write!(dst.writer(), "{}", len)?; - dst.extend_from_slice(b"\r\n"); + dst.put_slice(b"\r\ncontent-length: "); + write!(dst.writer(), "{}\r\n", len)?; } - BodySize::None => dst.extend_from_slice(b"\r\n"), + BodySize::None => dst.put_slice(b"\r\n"), } // Connection match ctype { - ConnectionType::Upgrade => dst.extend_from_slice(b"connection: upgrade\r\n"), + ConnectionType::Upgrade => dst.put_slice(b"connection: upgrade\r\n"), ConnectionType::KeepAlive if version < Version::HTTP_11 => { - dst.extend_from_slice(b"connection: keep-alive\r\n") + dst.put_slice(b"connection: keep-alive\r\n") } ConnectionType::Close if version >= Version::HTTP_11 => { - dst.extend_from_slice(b"connection: close\r\n") + dst.put_slice(b"connection: close\r\n") } _ => (), } @@ -129,7 +127,7 @@ pub(crate) trait MessageType: Sized { dst.advance_mut(pos); } pos = 0; - dst.reserve(len); + dst.reserve(len * 2); remaining = dst.remaining_mut(); unsafe { buf = &mut *(dst.bytes_mut() as *mut _); @@ -154,7 +152,7 @@ pub(crate) trait MessageType: Sized { dst.advance_mut(pos); } pos = 0; - dst.reserve(len); + dst.reserve(len * 2); remaining = dst.remaining_mut(); unsafe { buf = &mut *(dst.bytes_mut() as *mut _); @@ -209,7 +207,7 @@ impl MessageType for Response<()> { // status line helpers::write_status_line(head.version, head.status.as_u16(), dst); - dst.extend_from_slice(reason); + dst.put_slice(reason); Ok(()) } } @@ -228,6 +226,7 @@ impl MessageType for RequestHead { } fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> { + dst.reserve(256 + self.headers.len() * AVERAGE_HEADER_SIZE); write!( Writer(dst), "{} {} {}", From 219baf332344e31e0a8ed63beb32aacadf9ce56b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 7 Apr 2019 10:29:26 -0700 Subject: [PATCH 028/122] remove PayloadWriter trait --- actix-http/src/h1/dispatcher.rs | 2 +- actix-http/src/h1/mod.rs | 2 +- actix-http/src/h1/payload.rs | 33 +++++---------------------------- actix-multipart/src/server.rs | 2 +- 4 files changed, 8 insertions(+), 31 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 61c284a9c..bff05ab5c 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -19,7 +19,7 @@ use crate::request::Request; use crate::response::Response; use super::codec::Codec; -use super::payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; +use super::payload::{Payload, PayloadSender, PayloadStatus}; use super::{Message, MessageType}; const LW_BUFFER_SIZE: usize = 4096; diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs index dd29547e5..79d7cda8b 100644 --- a/actix-http/src/h1/mod.rs +++ b/actix-http/src/h1/mod.rs @@ -14,7 +14,7 @@ pub use self::client::{ClientCodec, ClientPayloadCodec}; pub use self::codec::Codec; pub use self::dispatcher::Dispatcher; pub use self::expect::ExpectHandler; -pub use self::payload::{Payload, PayloadWriter}; +pub use self::payload::Payload; pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; #[derive(Debug)] diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index bef87f7dc..e880d46ab 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -97,58 +97,35 @@ impl Stream for Payload { } } -impl Clone for Payload { - fn clone(&self) -> Payload { - Payload { - inner: Rc::clone(&self.inner), - } - } -} - -/// Payload writer interface. -pub trait PayloadWriter { - /// Set stream error. - fn set_error(&mut self, err: PayloadError); - - /// Write eof into a stream which closes reading side of a stream. - fn feed_eof(&mut self); - - /// Feed bytes into a payload stream - fn feed_data(&mut self, data: Bytes); - - /// Need read data - fn need_read(&self) -> PayloadStatus; -} - /// Sender part of the payload stream pub struct PayloadSender { inner: Weak>, } -impl PayloadWriter for PayloadSender { +impl PayloadSender { #[inline] - fn set_error(&mut self, err: PayloadError) { + pub fn set_error(&mut self, err: PayloadError) { if let Some(shared) = self.inner.upgrade() { shared.borrow_mut().set_error(err) } } #[inline] - fn feed_eof(&mut self) { + pub fn feed_eof(&mut self) { if let Some(shared) = self.inner.upgrade() { shared.borrow_mut().feed_eof() } } #[inline] - fn feed_data(&mut self, data: Bytes) { + pub fn feed_data(&mut self, data: Bytes) { if let Some(shared) = self.inner.upgrade() { shared.borrow_mut().feed_data(data) } } #[inline] - fn need_read(&self) -> PayloadStatus { + pub fn need_read(&self) -> PayloadStatus { // we check need_read only if Payload (other side) is alive, // otherwise always return true (consume payload) if let Some(shared) = self.inner.upgrade() { diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 39559b082..2dae5fd33 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -776,7 +776,7 @@ impl PayloadBuffer { #[cfg(test)] mod tests { - use actix_http::h1::{Payload, PayloadWriter}; + use actix_http::h1::Payload; use bytes::Bytes; use futures::unsync::mpsc; From 3c650ca194ffe3fa58652b57325436be1681e744 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 7 Apr 2019 10:40:45 -0700 Subject: [PATCH 029/122] remove buffer capacity for payload --- actix-http/Cargo.toml | 1 + actix-http/src/h1/payload.rs | 14 ++------------ actix-http/src/message.rs | 3 ++- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index fe9b62d14..528ab617c 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -59,6 +59,7 @@ base64 = "0.10" bitflags = "1.0" bytes = "0.4" byteorder = "1.2" +copyless = "0.1.2" derive_more = "0.14" either = "1.5.2" encoding = "0.2" diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index e880d46ab..28acb64bb 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -77,14 +77,6 @@ impl Payload { pub fn unread_data(&mut self, data: Bytes) { self.inner.borrow_mut().unread_data(data); } - - #[inline] - /// Set read buffer capacity - /// - /// Default buffer capacity is 32Kb. - pub fn set_read_buffer_capacity(&mut self, cap: usize) { - self.inner.borrow_mut().capacity = cap; - } } impl Stream for Payload { @@ -153,7 +145,6 @@ struct Inner { err: Option, need_read: bool, items: VecDeque, - capacity: usize, task: Option, io_task: Option, } @@ -166,7 +157,6 @@ impl Inner { err: None, items: VecDeque::new(), need_read: true, - capacity: MAX_BUFFER_SIZE, task: None, io_task: None, } @@ -186,7 +176,7 @@ impl Inner { fn feed_data(&mut self, data: Bytes) { self.len += data.len(); self.items.push_back(data); - self.need_read = self.len < self.capacity; + self.need_read = self.len < MAX_BUFFER_SIZE; if let Some(task) = self.task.take() { task.notify() } @@ -200,7 +190,7 @@ impl Inner { fn readany(&mut self) -> Poll, PayloadError> { if let Some(data) = self.items.pop_front() { self.len -= data.len(); - self.need_read = self.len < self.capacity; + self.need_read = self.len < MAX_BUFFER_SIZE; if self.need_read && self.task.is_none() && !self.eof { self.task = Some(current_task()); diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 25bc55baf..e1fb3a111 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -3,6 +3,7 @@ use std::collections::VecDeque; use std::rc::Rc; use bitflags::bitflags; +use copyless::BoxHelper; use crate::extensions::Extensions; use crate::header::HeaderMap; @@ -417,7 +418,7 @@ impl BoxedResponsePool { BoxedResponseHead { head: Some(head) } } else { BoxedResponseHead { - head: Some(Box::new(ResponseHead::default())), + head: Some(Box::alloc().init(ResponseHead::default())), } } } From 75b213a6f04dea816895639c94561d55e63facf7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 7 Apr 2019 14:43:07 -0700 Subject: [PATCH 030/122] refactor FromRequest trait --- CHANGES.md | 2 + actix-files/src/lib.rs | 8 +-- actix-multipart/src/extractor.rs | 9 +-- actix-session/src/lib.rs | 18 ++--- src/data.rs | 9 +-- src/extract.rs | 110 ++++++++++++++++++------------- src/handler.rs | 35 +++++----- src/lib.rs | 4 +- src/middleware/identity.rs | 10 ++- src/request.rs | 57 ++++++++++------ src/route.rs | 10 +-- src/service.rs | 92 +------------------------- src/test.rs | 33 ++++++---- src/types/form.rs | 61 ++++++++--------- src/types/json.rs | 66 +++++++++---------- src/types/path.rs | 34 +++++----- src/types/payload.rs | 85 ++++++++++++------------ src/types/query.rs | 7 +- 18 files changed, 298 insertions(+), 352 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b7e0d7423..3c619eee4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,8 @@ ### Changed +* `FromRequest` trait refactoring + * Move multipart support to actix-multipart crate diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index e2fa06e12..6820d3622 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -10,8 +10,8 @@ use std::{cmp, io}; use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::{IntoNewService, NewService, Service}; use actix_web::dev::{ - HttpServiceFactory, Payload, ResourceDef, ServiceConfig, ServiceFromRequest, - ServiceRequest, ServiceResponse, + HttpServiceFactory, Payload, ResourceDef, ServiceConfig, ServiceRequest, + ServiceResponse, }; use actix_web::error::{BlockingError, Error, ErrorInternalServerError}; use actix_web::http::header::DispositionType; @@ -551,8 +551,8 @@ impl

FromRequest

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

) -> Self::Future { - PathBufWrp::get_pathbuf(req.request().match_info().path()) + fn from_request(req: &HttpRequest, _: &mut Payload

) -> Self::Future { + PathBufWrp::get_pathbuf(req.match_info().path()) } } diff --git a/actix-multipart/src/extractor.rs b/actix-multipart/src/extractor.rs index 18c26c6fb..94eb4c305 100644 --- a/actix-multipart/src/extractor.rs +++ b/actix-multipart/src/extractor.rs @@ -2,10 +2,8 @@ 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 actix_web::{dev::Payload, FromRequest, HttpRequest}; use crate::server::Multipart; @@ -50,8 +48,7 @@ where type Future = Result; #[inline] - fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - let pl = req.take_payload(); - Ok(Multipart::new(req.headers(), pl)) + fn from_request(req: &HttpRequest, payload: &mut Payload

) -> Self::Future { + Ok(Multipart::new(req.headers(), payload.take())) } } diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index 0cd1b9ed8..4b7ae2fde 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -45,8 +45,8 @@ use std::cell::RefCell; use std::rc::Rc; -use actix_web::dev::{ServiceFromRequest, ServiceRequest, ServiceResponse}; -use actix_web::{Error, FromRequest, HttpMessage}; +use actix_web::dev::{Extensions, Payload, ServiceRequest, ServiceResponse}; +use actix_web::{Error, FromRequest, HttpMessage, HttpRequest}; use hashbrown::HashMap; use serde::de::DeserializeOwned; use serde::Serialize; @@ -123,7 +123,7 @@ impl Session { data: impl Iterator, req: &mut ServiceRequest

, ) { - let session = Session::get_session(req); + let session = Session::get_session(&mut *req.extensions_mut()); let mut inner = session.0.borrow_mut(); inner.state.extend(data); } @@ -144,12 +144,12 @@ impl Session { } } - fn get_session(req: R) -> Session { - if let Some(s_impl) = req.extensions().get::>>() { + fn get_session(extensions: &mut Extensions) -> Session { + if let Some(s_impl) = extensions.get::>>() { return Session(Rc::clone(&s_impl)); } let inner = Rc::new(RefCell::new(SessionInner::default())); - req.extensions_mut().insert(inner.clone()); + extensions.insert(inner.clone()); Session(inner) } } @@ -177,8 +177,8 @@ impl

FromRequest

for Session { type Future = Result; #[inline] - fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - Ok(Session::get_session(req)) + fn from_request(req: &HttpRequest, _: &mut Payload

) -> Self::Future { + Ok(Session::get_session(&mut *req.extensions_mut())) } } @@ -196,7 +196,7 @@ mod tests { vec![("key".to_string(), "\"value\"".to_string())].into_iter(), &mut req, ); - let session = Session::get_session(&mut req); + let session = Session::get_session(&mut *req.extensions_mut()); let res = session.get::("key").unwrap(); assert_eq!(res, Some("value".to_string())); diff --git a/src/data.rs b/src/data.rs index a79a303bc..502dd6be8 100644 --- a/src/data.rs +++ b/src/data.rs @@ -5,8 +5,9 @@ use actix_http::error::{Error, ErrorInternalServerError}; use actix_http::Extensions; use futures::{Async, Future, IntoFuture, Poll}; +use crate::dev::Payload; use crate::extract::FromRequest; -use crate::service::ServiceFromRequest; +use crate::request::HttpRequest; /// Application data factory pub(crate) trait DataFactory { @@ -91,8 +92,8 @@ impl FromRequest

for Data { type Future = Result; #[inline] - fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - if let Some(st) = req.request().config().extensions().get::>() { + fn from_request(req: &HttpRequest, _: &mut Payload

) -> Self::Future { + if let Some(st) = req.app_config().extensions().get::>() { Ok(st.clone()) } else { Err(ErrorInternalServerError( @@ -230,7 +231,7 @@ impl FromRequest

for RouteData { type Future = Result; #[inline] - fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { + fn from_request(req: &HttpRequest, _: &mut Payload

) -> Self::Future { if let Some(st) = req.route_data::() { Ok(st.clone()) } else { diff --git a/src/extract.rs b/src/extract.rs index 4cd04be2b..73cbb4cee 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -4,7 +4,8 @@ use actix_http::error::Error; use futures::future::ok; use futures::{future, Async, Future, IntoFuture, Poll}; -use crate::service::ServiceFromRequest; +use crate::dev::Payload; +use crate::request::HttpRequest; /// Trait implemented by types that can be extracted from request. /// @@ -17,7 +18,14 @@ pub trait FromRequest

: Sized { type Future: IntoFuture; /// Convert request to a Self - fn from_request(req: &mut ServiceFromRequest

) -> Self::Future; + fn from_request(req: &HttpRequest, payload: &mut Payload

) -> Self::Future; + + /// Convert request to a Self + /// + /// This method uses `Payload::None` as payload stream. + fn extract(req: &HttpRequest) -> Self::Future { + Self::from_request(req, &mut Payload::None) + } } /// Optionally extract a field from the request @@ -28,7 +36,7 @@ pub trait FromRequest

: Sized { /// /// ```rust /// # #[macro_use] extern crate serde_derive; -/// use actix_web::{web, dev, App, Error, FromRequest}; +/// use actix_web::{web, dev, App, Error, HttpRequest, FromRequest}; /// use actix_web::error::ErrorBadRequest; /// use rand; /// @@ -41,7 +49,7 @@ pub trait FromRequest

: Sized { /// type Error = Error; /// type Future = Result; /// -/// fn from_request(req: &mut dev::ServiceFromRequest

) -> Self::Future { +/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload

) -> Self::Future { /// if rand::random() { /// Ok(Thing { name: "thingy".into() }) /// } else { @@ -76,14 +84,18 @@ where type Future = Box, Error = Error>>; #[inline] - fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - Box::new(T::from_request(req).into_future().then(|r| match r { - Ok(v) => future::ok(Some(v)), - Err(e) => { - log::debug!("Error for Option extractor: {}", e.into()); - future::ok(None) - } - })) + fn from_request(req: &HttpRequest, payload: &mut Payload

) -> Self::Future { + Box::new( + T::from_request(req, payload) + .into_future() + .then(|r| match r { + Ok(v) => future::ok(Some(v)), + Err(e) => { + log::debug!("Error for Option extractor: {}", e.into()); + future::ok(None) + } + }), + ) } } @@ -95,7 +107,7 @@ where /// /// ```rust /// # #[macro_use] extern crate serde_derive; -/// use actix_web::{web, dev, App, Result, Error, FromRequest}; +/// use actix_web::{web, dev, App, Result, Error, HttpRequest, FromRequest}; /// use actix_web::error::ErrorBadRequest; /// use rand; /// @@ -108,7 +120,7 @@ where /// type Error = Error; /// type Future = Result; /// -/// fn from_request(req: &mut dev::ServiceFromRequest

) -> Self::Future { +/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload

) -> Self::Future { /// if rand::random() { /// Ok(Thing { name: "thingy".into() }) /// } else { @@ -141,11 +153,15 @@ where type Future = Box, Error = Error>>; #[inline] - fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - Box::new(T::from_request(req).into_future().then(|res| match res { - Ok(v) => ok(Ok(v)), - Err(e) => ok(Err(e)), - })) + fn from_request(req: &HttpRequest, payload: &mut Payload

) -> Self::Future { + Box::new( + T::from_request(req, payload) + .into_future() + .then(|res| match res { + Ok(v) => ok(Ok(v)), + Err(e) => ok(Err(e)), + }), + ) } } @@ -154,7 +170,7 @@ impl

FromRequest

for () { type Error = Error; type Future = Result<(), Error>; - fn from_request(_req: &mut ServiceFromRequest

) -> Self::Future { + fn from_request(_: &HttpRequest, _: &mut Payload

) -> Self::Future { Ok(()) } } @@ -168,10 +184,10 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { type Error = Error; type Future = $fut_type; - fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { + fn from_request(req: &HttpRequest, payload: &mut Payload

) -> Self::Future { $fut_type { items: <($(Option<$T>,)+)>::default(), - futs: ($($T::from_request(req).into_future(),)+), + futs: ($($T::from_request(req, payload).into_future(),)+), } } } @@ -247,25 +263,25 @@ mod tests { #[test] fn test_option() { - let mut req = TestRequest::with_header( + let (req, mut pl) = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) .route_data(FormConfig::default().limit(4096)) - .to_from(); + .to_http_parts(); - let r = block_on(Option::>::from_request(&mut req)).unwrap(); + let r = block_on(Option::>::from_request(&req, &mut pl)).unwrap(); assert_eq!(r, None); - let mut req = TestRequest::with_header( + let (req, mut pl) = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) .header(header::CONTENT_LENGTH, "9") .set_payload(Bytes::from_static(b"hello=world")) - .to_from(); + .to_http_parts(); - let r = block_on(Option::>::from_request(&mut req)).unwrap(); + let r = block_on(Option::>::from_request(&req, &mut pl)).unwrap(); assert_eq!( r, Some(Form(Info { @@ -273,29 +289,29 @@ mod tests { })) ); - let mut req = TestRequest::with_header( + let (req, mut pl) = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) .header(header::CONTENT_LENGTH, "9") .set_payload(Bytes::from_static(b"bye=world")) - .to_from(); + .to_http_parts(); - let r = block_on(Option::>::from_request(&mut req)).unwrap(); + let r = block_on(Option::>::from_request(&req, &mut pl)).unwrap(); assert_eq!(r, None); } #[test] fn test_result() { - let mut req = TestRequest::with_header( + let (req, mut pl) = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) .header(header::CONTENT_LENGTH, "11") .set_payload(Bytes::from_static(b"hello=world")) - .to_from(); + .to_http_parts(); - let r = block_on(Result::, Error>::from_request(&mut req)) + let r = block_on(Result::, Error>::from_request(&req, &mut pl)) .unwrap() .unwrap(); assert_eq!( @@ -305,15 +321,16 @@ mod tests { }) ); - let mut req = TestRequest::with_header( + let (req, mut pl) = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) .header(header::CONTENT_LENGTH, "9") .set_payload(Bytes::from_static(b"bye=world")) - .to_from(); + .to_http_parts(); - let r = block_on(Result::, Error>::from_request(&mut req)).unwrap(); + let r = + block_on(Result::, Error>::from_request(&req, &mut pl)).unwrap(); assert!(r.is_err()); } @@ -336,37 +353,38 @@ mod tests { #[test] fn test_request_extract() { - let mut req = TestRequest::with_uri("/name/user1/?id=test").to_from(); + let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); let resource = ResourceDef::new("/{key}/{value}/"); resource.match_path(req.match_info_mut()); - let s = Path::::from_request(&mut req).unwrap(); + let (req, mut pl) = req.into_parts(); + let s = Path::::from_request(&req, &mut pl).unwrap(); assert_eq!(s.key, "name"); assert_eq!(s.value, "user1"); - let s = Path::<(String, String)>::from_request(&mut req).unwrap(); + let s = Path::<(String, String)>::from_request(&req, &mut pl).unwrap(); assert_eq!(s.0, "name"); assert_eq!(s.1, "user1"); - let s = Query::::from_request(&mut req).unwrap(); + let s = Query::::from_request(&req, &mut pl).unwrap(); assert_eq!(s.id, "test"); - let mut req = TestRequest::with_uri("/name/32/").to_from(); + let mut req = TestRequest::with_uri("/name/32/").to_srv_request(); let resource = ResourceDef::new("/{key}/{value}/"); resource.match_path(req.match_info_mut()); - let s = Path::::from_request(&mut req).unwrap(); + let (req, mut pl) = req.into_parts(); + let s = Path::::from_request(&req, &mut pl).unwrap(); assert_eq!(s.as_ref().key, "name"); assert_eq!(s.value, 32); - let s = Path::<(String, u8)>::from_request(&mut req).unwrap(); + let s = Path::<(String, u8)>::from_request(&req, &mut pl).unwrap(); assert_eq!(s.0, "name"); assert_eq!(s.1, 32); - let res = Path::>::from_request(&mut req).unwrap(); + let res = Path::>::from_request(&req, &mut pl).unwrap(); assert_eq!(res[0], "name".to_owned()); assert_eq!(res[1], "32".to_owned()); } - } diff --git a/src/handler.rs b/src/handler.rs index a11a5d0b6..921b8334d 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::marker::PhantomData; use std::rc::Rc; -use actix_http::{Error, Extensions, Response}; +use actix_http::{Error, Extensions, Payload, Response}; use actix_service::{NewService, Service, Void}; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, IntoFuture, Poll}; @@ -10,7 +10,7 @@ use futures::{try_ready, Async, Future, IntoFuture, Poll}; use crate::extract::FromRequest; use crate::request::HttpRequest; use crate::responder::Responder; -use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; +use crate::service::{ServiceRequest, ServiceResponse}; /// Handler converter factory pub trait Factory: Clone @@ -293,7 +293,7 @@ impl> Extract { impl> NewService for Extract { type Request = ServiceRequest

; type Response = (T, HttpRequest); - type Error = (Error, ServiceFromRequest

); + type Error = (Error, ServiceRequest

); type InitError = (); type Service = ExtractService; type Future = FutureResult; @@ -314,7 +314,7 @@ pub struct ExtractService> { impl> Service for ExtractService { type Request = ServiceRequest

; type Response = (T, HttpRequest); - type Error = (Error, ServiceFromRequest

); + type Error = (Error, ServiceRequest

); type Future = ExtractResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { @@ -322,33 +322,34 @@ impl> Service for ExtractService { } fn call(&mut self, req: ServiceRequest

) -> Self::Future { - let mut req = ServiceFromRequest::new(req, self.config.clone()); + let (mut req, mut payload) = req.into_parts(); + req.set_route_data(self.config.clone()); + let fut = T::from_request(&req, &mut payload).into_future(); + ExtractResponse { - fut: T::from_request(&mut req).into_future(), - req: Some(req), + fut, + req: Some((req, payload)), } } } pub struct ExtractResponse> { - req: Option>, + req: Option<(HttpRequest, Payload

)>, fut: ::Future, } impl> Future for ExtractResponse { type Item = (T, HttpRequest); - type Error = (Error, ServiceFromRequest

); + type Error = (Error, ServiceRequest

); fn poll(&mut self) -> Poll { - let item = try_ready!(self - .fut - .poll() - .map_err(|e| (e.into(), self.req.take().unwrap()))); + let item = try_ready!(self.fut.poll().map_err(|e| { + let (req, payload) = self.req.take().unwrap(); + let req = ServiceRequest::from_parts(req, payload); + (e.into(), req) + })); - let req = self.req.take().unwrap(); - let req = req.into_request(); - - Ok(Async::Ready((item, req))) + Ok(Async::Ready((item, self.req.take().unwrap().0))) } } diff --git a/src/lib.rs b/src/lib.rs index 39c054bc4..bebf6ef3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -138,9 +138,7 @@ pub mod dev { pub use crate::config::{AppConfig, ServiceConfig}; pub use crate::info::ConnectionInfo; pub use crate::rmap::ResourceMap; - pub use crate::service::{ - HttpServiceFactory, ServiceFromRequest, ServiceRequest, ServiceResponse, - }; + pub use crate::service::{HttpServiceFactory, ServiceRequest, ServiceResponse}; pub use crate::types::form::UrlEncoded; pub use crate::types::json::JsonBody; pub use crate::types::readlines::Readlines; diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index 3df2f0e3b..e263099f4 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -58,10 +58,8 @@ use time::Duration; use crate::cookie::{Cookie, CookieJar, Key, SameSite}; use crate::error::{Error, Result}; use crate::http::header::{self, HeaderValue}; -use crate::request::HttpRequest; -use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; -use crate::FromRequest; -use crate::HttpMessage; +use crate::service::{ServiceRequest, ServiceResponse}; +use crate::{dev::Payload, FromRequest, HttpMessage, HttpRequest}; /// The extractor type to obtain your identity from a request. /// @@ -147,8 +145,8 @@ impl

FromRequest

for Identity { type Future = Result; #[inline] - fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - Ok(Identity(req.request().clone())) + fn from_request(req: &HttpRequest, _: &mut Payload

) -> Self::Future { + Ok(Identity(req.clone())) } } diff --git a/src/request.rs b/src/request.rs index 2eab1ee19..ff38c879c 100644 --- a/src/request.rs +++ b/src/request.rs @@ -7,12 +7,11 @@ use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead}; use actix_router::{Path, Url}; use crate::config::AppConfig; -use crate::data::Data; +use crate::data::{Data, RouteData}; use crate::error::UrlGenerationError; use crate::extract::FromRequest; use crate::info::ConnectionInfo; use crate::rmap::ResourceMap; -use crate::service::ServiceFromRequest; #[derive(Clone)] /// An HTTP Request @@ -21,6 +20,7 @@ pub struct HttpRequest { pub(crate) path: Path, rmap: Rc, config: AppConfig, + route_data: Option>, } impl HttpRequest { @@ -36,6 +36,7 @@ impl HttpRequest { path, rmap, config, + route_data: None, } } } @@ -100,22 +101,6 @@ impl HttpRequest { &self.path } - /// App config - #[inline] - pub fn config(&self) -> &AppConfig { - &self.config - } - - /// Get an application data stored with `App::data()` method during - /// application configuration. - pub fn app_data(&self) -> Option> { - if let Some(st) = self.config.extensions().get::>() { - Some(st.clone()) - } else { - None - } - } - /// Request extensions #[inline] pub fn extensions(&self) -> Ref { @@ -171,7 +156,37 @@ impl HttpRequest { /// Get *ConnectionInfo* for the current request. #[inline] pub fn connection_info(&self) -> Ref { - ConnectionInfo::get(self.head(), &*self.config()) + ConnectionInfo::get(self.head(), &*self.app_config()) + } + + /// App config + #[inline] + pub fn app_config(&self) -> &AppConfig { + &self.config + } + + /// Get an application data stored with `App::data()` method during + /// application configuration. + pub fn app_data(&self) -> Option> { + if let Some(st) = self.config.extensions().get::>() { + Some(st.clone()) + } else { + None + } + } + + /// Load route data. Route data could be set during + /// route configuration with `Route::data()` method. + pub fn route_data(&self) -> Option<&RouteData> { + if let Some(ref ext) = self.route_data { + ext.get::>() + } else { + None + } + } + + pub(crate) fn set_route_data(&mut self, data: Option>) { + self.route_data = data; } } @@ -227,8 +242,8 @@ impl

FromRequest

for HttpRequest { type Future = Result; #[inline] - fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - Ok(req.request().clone()) + fn from_request(req: &HttpRequest, _: &mut Payload

) -> Self::Future { + Ok(req.clone()) } } diff --git a/src/route.rs b/src/route.rs index 7f1cee3d4..349668ef4 100644 --- a/src/route.rs +++ b/src/route.rs @@ -11,7 +11,7 @@ use crate::extract::FromRequest; use crate::guard::{self, Guard}; use crate::handler::{AsyncFactory, AsyncHandler, Extract, Factory, Handler}; use crate::responder::Responder; -use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; +use crate::service::{ServiceRequest, ServiceResponse}; use crate::HttpResponse; type BoxedRouteService = Box< @@ -317,7 +317,7 @@ impl Route

{ struct RouteNewService where - T: NewService, Error = (Error, ServiceFromRequest

)>, + T: NewService, Error = (Error, ServiceRequest

)>, { service: T, _t: PhantomData

, @@ -328,7 +328,7 @@ where T: NewService< Request = ServiceRequest

, Response = ServiceResponse, - Error = (Error, ServiceFromRequest

), + Error = (Error, ServiceRequest

), >, T::Future: 'static, T::Service: 'static, @@ -347,7 +347,7 @@ where T: NewService< Request = ServiceRequest

, Response = ServiceResponse, - Error = (Error, ServiceFromRequest

), + Error = (Error, ServiceRequest

), >, T::Future: 'static, T::Service: 'static, @@ -388,7 +388,7 @@ where T: Service< Request = ServiceRequest

, Response = ServiceResponse, - Error = (Error, ServiceFromRequest

), + Error = (Error, ServiceRequest

), >, { type Request = ServiceRequest

; diff --git a/src/service.rs b/src/service.rs index 0f11b89e1..13eea9d14 100644 --- a/src/service.rs +++ b/src/service.rs @@ -13,7 +13,7 @@ use actix_router::{Path, Resource, Url}; use futures::future::{ok, FutureResult, IntoFuture}; use crate::config::{AppConfig, ServiceConfig}; -use crate::data::{Data, RouteData}; +use crate::data::Data; use crate::request::HttpRequest; use crate::rmap::ResourceMap; @@ -171,13 +171,13 @@ impl

ServiceRequest

{ /// Service configuration #[inline] pub fn app_config(&self) -> &AppConfig { - self.req.config() + self.req.app_config() } /// Get an application data stored with `App::data()` method during /// application configuration. pub fn app_data(&self) -> Option> { - if let Some(st) = self.req.config().extensions().get::>() { + if let Some(st) = self.req.app_config().extensions().get::>() { Some(st.clone()) } else { None @@ -241,92 +241,6 @@ impl

fmt::Debug for ServiceRequest

{ } } -pub struct ServiceFromRequest

{ - req: HttpRequest, - payload: Payload

, - data: Option>, -} - -impl

ServiceFromRequest

{ - pub(crate) fn new(req: ServiceRequest

, data: Option>) -> Self { - Self { - req: req.req, - payload: req.payload, - data, - } - } - - #[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 - } - - /// Create service response for error - #[inline] - pub fn error_response>(self, err: E) -> ServiceResponse { - ServiceResponse::new(self.req, err.into().into()) - } - - /// Get an application data stored with `App::data()` method during - /// application configuration. - pub fn app_data(&self) -> Option> { - if let Some(st) = self.req.config().extensions().get::>() { - Some(st.clone()) - } else { - None - } - } - - /// Load route data. Route data could be set during - /// route configuration with `Route::data()` method. - pub fn route_data(&self) -> Option<&RouteData> { - if let Some(ref ext) = self.data { - ext.get::>() - } else { - None - } - } -} - -impl

HttpMessage for ServiceFromRequest

{ - type Stream = P; - - #[inline] - fn headers(&self) -> &HeaderMap { - self.req.headers() - } - - /// Request extensions - #[inline] - fn extensions(&self) -> Ref { - self.req.head.extensions() - } - - /// Mutable reference to a the request's extensions - #[inline] - fn extensions_mut(&self) -> RefMut { - self.req.head.extensions_mut() - } - - #[inline] - fn take_payload(&mut self) -> Payload { - std::mem::replace(&mut self.payload, Payload::None) - } -} - pub struct ServiceResponse { request: HttpRequest, response: Response, diff --git a/src/test.rs b/src/test.rs index 209edac54..58cb1211e 100644 --- a/src/test.rs +++ b/src/test.rs @@ -16,9 +16,9 @@ use futures::future::{lazy, Future}; use crate::config::{AppConfig, AppConfigInner}; use crate::data::RouteData; -use crate::dev::Body; +use crate::dev::{Body, Payload}; use crate::rmap::ResourceMap; -use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; +use crate::service::{ServiceRequest, ServiceResponse}; use crate::{Error, HttpRequest, HttpResponse}; thread_local! { @@ -319,6 +319,11 @@ impl TestRequest { self } + /// Complete request creation and generate `Request` instance + pub fn to_request(mut self) -> Request { + self.req.finish() + } + /// Complete request creation and generate `ServiceRequest` instance pub fn to_srv_request(mut self) -> ServiceRequest { let req = self.req.finish(); @@ -336,36 +341,36 @@ impl TestRequest { self.to_srv_request().into_response(res) } - /// Complete request creation and generate `Request` instance - pub fn to_request(mut self) -> Request { - self.req.finish() - } - /// Complete request creation and generate `HttpRequest` instance pub fn to_http_request(mut self) -> HttpRequest { let req = self.req.finish(); - ServiceRequest::new( + let mut req = ServiceRequest::new( Path::new(Url::new(req.uri().clone())), req, Rc::new(self.rmap), AppConfig::new(self.config), ) .into_parts() - .0 + .0; + req.set_route_data(Some(Rc::new(self.route_data))); + req } - /// Complete request creation and generate `ServiceFromRequest` instance - pub fn to_from(mut self) -> ServiceFromRequest { + /// Complete request creation and generate `HttpRequest` and `Payload` instances + pub fn to_http_parts(mut self) -> (HttpRequest, Payload) { let req = self.req.finish(); - let req = ServiceRequest::new( + let (mut req, pl) = ServiceRequest::new( Path::new(Url::new(req.uri().clone())), req, Rc::new(self.rmap), AppConfig::new(self.config), - ); - ServiceFromRequest::new(req, Some(Rc::new(self.route_data))) + ) + .into_parts(); + + req.set_route_data(Some(Rc::new(self.route_data))); + (req, pl) } /// Runs the provided future, blocking the current thread until the future diff --git a/src/types/form.rs b/src/types/form.rs index b2171e540..2c876e260 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -16,7 +16,6 @@ use crate::error::UrlencodedError; use crate::extract::FromRequest; use crate::http::header::CONTENT_LENGTH; use crate::request::HttpRequest; -use crate::service::ServiceFromRequest; #[derive(PartialEq, Eq, PartialOrd, Ord)] /// Extract typed information from the request's body. @@ -79,15 +78,15 @@ where type Future = Box>; #[inline] - fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - let req2 = req.request().clone(); + fn from_request(req: &HttpRequest, payload: &mut Payload

) -> Self::Future { + let req2 = req.clone(); let (limit, err) = req .route_data::() .map(|c| (c.limit, c.ehandler.clone())) .unwrap_or((16384, None)); Box::new( - UrlEncoded::new(req) + UrlEncoded::new(req, payload) .limit(limit) .map_err(move |e| { if let Some(err) = err { @@ -183,8 +182,8 @@ impl Default for FormConfig { /// * content type is not `application/x-www-form-urlencoded` /// * content-length is greater than 32k /// -pub struct UrlEncoded { - stream: Payload, +pub struct UrlEncoded { + stream: Payload

, limit: usize, length: Option, encoding: EncodingRef, @@ -192,13 +191,12 @@ pub struct UrlEncoded { fut: Option>>, } -impl UrlEncoded +impl UrlEncoded where - T: HttpMessage, - T::Stream: Stream, + P: Stream, { /// Create a new future to URL encode a request - pub fn new(req: &mut T) -> UrlEncoded { + pub fn new(req: &HttpRequest, payload: &mut Payload

) -> UrlEncoded { // check content type if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { return Self::err(UrlencodedError::ContentType); @@ -223,7 +221,7 @@ where UrlEncoded { encoding, - stream: req.take_payload(), + stream: payload.take(), limit: 32_768, length: len, fut: None, @@ -249,10 +247,9 @@ where } } -impl Future for UrlEncoded +impl Future for UrlEncoded where - T: HttpMessage, - T::Stream: Stream + 'static, + P: Stream + 'static, U: DeserializeOwned + 'static, { type Item = U; @@ -320,13 +317,13 @@ mod tests { #[test] fn test_form() { - let mut req = + let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") .header(CONTENT_LENGTH, "11") .set_payload(Bytes::from_static(b"hello=world")) - .to_from(); + .to_http_parts(); - let s = block_on(Form::::from_request(&mut req)).unwrap(); + let s = block_on(Form::::from_request(&req, &mut pl)).unwrap(); assert_eq!(s.hello, "world"); } @@ -354,36 +351,36 @@ mod tests { #[test] fn test_urlencoded_error() { - let mut req = + let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") .header(CONTENT_LENGTH, "xxxx") - .to_request(); - let info = block_on(UrlEncoded::<_, Info>::new(&mut req)); + .to_http_parts(); + let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl)); assert!(eq(info.err().unwrap(), UrlencodedError::UnknownLength)); - let mut req = + let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") .header(CONTENT_LENGTH, "1000000") - .to_request(); - let info = block_on(UrlEncoded::<_, Info>::new(&mut req)); + .to_http_parts(); + let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl)); assert!(eq(info.err().unwrap(), UrlencodedError::Overflow)); - let mut req = TestRequest::with_header(CONTENT_TYPE, "text/plain") + let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "text/plain") .header(CONTENT_LENGTH, "10") - .to_request(); - let info = block_on(UrlEncoded::<_, Info>::new(&mut req)); + .to_http_parts(); + let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl)); assert!(eq(info.err().unwrap(), UrlencodedError::ContentType)); } #[test] fn test_urlencoded() { - let mut req = + let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") .header(CONTENT_LENGTH, "11") .set_payload(Bytes::from_static(b"hello=world")) - .to_request(); + .to_http_parts(); - let info = block_on(UrlEncoded::<_, Info>::new(&mut req)).unwrap(); + let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl)).unwrap(); assert_eq!( info, Info { @@ -391,15 +388,15 @@ mod tests { } ); - let mut req = TestRequest::with_header( + let (req, mut pl) = TestRequest::with_header( CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8", ) .header(CONTENT_LENGTH, "11") .set_payload(Bytes::from_static(b"hello=world")) - .to_request(); + .to_http_parts(); - let info = block_on(UrlEncoded::<_, Info>::new(&mut req)).unwrap(); + let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl)).unwrap(); assert_eq!( info, Info { diff --git a/src/types/json.rs b/src/types/json.rs index f7b94a9bd..f001ee1f1 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -16,7 +16,6 @@ use crate::error::{Error, JsonPayloadError, PayloadError}; use crate::extract::FromRequest; use crate::request::HttpRequest; use crate::responder::Responder; -use crate::service::ServiceFromRequest; /// Json helper /// @@ -173,15 +172,15 @@ where type Future = Box>; #[inline] - fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - let req2 = req.request().clone(); + fn from_request(req: &HttpRequest, payload: &mut Payload

) -> Self::Future { + let req2 = req.clone(); let (limit, err) = req .route_data::() .map(|c| (c.limit, c.ehandler.clone())) .unwrap_or((32768, None)); Box::new( - JsonBody::new(req) + JsonBody::new(req, payload) .limit(limit) .map_err(move |e| { if let Some(err) = err { @@ -264,22 +263,21 @@ impl Default for JsonConfig { /// /// * content type is not `application/json` /// * content length is greater than 256k -pub struct JsonBody { +pub struct JsonBody { limit: usize, length: Option, - stream: Payload, + stream: Payload

, err: Option, fut: Option>>, } -impl JsonBody +impl JsonBody where - T: HttpMessage, - T::Stream: Stream + 'static, + P: Stream + 'static, U: DeserializeOwned + 'static, { /// Create `JsonBody` for request. - pub fn new(req: &mut T) -> Self { + pub fn new(req: &HttpRequest, payload: &mut Payload

) -> Self { // check content-type let json = if let Ok(Some(mime)) = req.mime_type() { mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) @@ -308,7 +306,7 @@ where JsonBody { limit: 262_144, length: len, - stream: req.take_payload(), + stream: payload.take(), fut: None, err: None, } @@ -321,10 +319,9 @@ where } } -impl Future for JsonBody +impl Future for JsonBody where - T: HttpMessage, - T::Stream: Stream + 'static, + P: Stream + 'static, U: DeserializeOwned + 'static, { type Item = U; @@ -410,7 +407,7 @@ mod tests { #[test] fn test_extract() { - let mut req = TestRequest::default() + let (req, mut pl) = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), @@ -420,9 +417,9 @@ mod tests { header::HeaderValue::from_static("16"), ) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .to_from(); + .to_http_parts(); - let s = block_on(Json::::from_request(&mut req)).unwrap(); + let s = block_on(Json::::from_request(&req, &mut pl)).unwrap(); assert_eq!(s.name, "test"); assert_eq!( s.into_inner(), @@ -431,7 +428,7 @@ mod tests { } ); - let mut req = TestRequest::default() + let (req, mut pl) = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), @@ -442,12 +439,13 @@ mod tests { ) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .route_data(JsonConfig::default().limit(10)) - .to_from(); - let s = block_on(Json::::from_request(&mut req)); + .to_http_parts(); + + let s = block_on(Json::::from_request(&req, &mut pl)); assert!(format!("{}", s.err().unwrap()) .contains("Json payload size is bigger than allowed.")); - let mut req = TestRequest::default() + let (req, mut pl) = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), @@ -462,27 +460,27 @@ mod tests { .limit(10) .error_handler(|_, _| JsonPayloadError::ContentType.into()), ) - .to_from(); - let s = block_on(Json::::from_request(&mut req)); + .to_http_parts(); + let s = block_on(Json::::from_request(&req, &mut pl)); assert!(format!("{}", s.err().unwrap()).contains("Content type error")); } #[test] fn test_json_body() { - let mut req = TestRequest::default().to_request(); - let json = block_on(JsonBody::<_, MyObject>::new(&mut req)); + let (req, mut pl) = TestRequest::default().to_http_parts(); + let json = block_on(JsonBody::<_, MyObject>::new(&req, &mut pl)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); - let mut req = TestRequest::default() + let (req, mut pl) = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/text"), ) - .to_request(); - let json = block_on(JsonBody::<_, MyObject>::new(&mut req)); + .to_http_parts(); + let json = block_on(JsonBody::<_, MyObject>::new(&req, &mut pl)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); - let mut req = TestRequest::default() + let (req, mut pl) = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), @@ -491,12 +489,12 @@ mod tests { header::CONTENT_LENGTH, header::HeaderValue::from_static("10000"), ) - .to_request(); + .to_http_parts(); - let json = block_on(JsonBody::<_, MyObject>::new(&mut req).limit(100)); + let json = block_on(JsonBody::<_, MyObject>::new(&req, &mut pl).limit(100)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow)); - let mut req = TestRequest::default() + let (req, mut pl) = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), @@ -506,9 +504,9 @@ mod tests { header::HeaderValue::from_static("16"), ) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .to_request(); + .to_http_parts(); - let json = block_on(JsonBody::<_, MyObject>::new(&mut req)); + let json = block_on(JsonBody::<_, MyObject>::new(&req, &mut pl)); assert_eq!( json.ok().unwrap(), MyObject { diff --git a/src/types/path.rs b/src/types/path.rs index fbd106630..d8334679a 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -6,8 +6,8 @@ use actix_http::error::{Error, ErrorNotFound}; use actix_router::PathDeserializer; use serde::de; +use crate::dev::Payload; use crate::request::HttpRequest; -use crate::service::ServiceFromRequest; use crate::FromRequest; #[derive(PartialEq, Eq, PartialOrd, Ord)] @@ -66,15 +66,6 @@ impl Path { pub fn into_inner(self) -> T { self.inner } - - /// Extract path information from a request - pub fn extract(req: &HttpRequest) -> Result, de::value::Error> - where - T: de::DeserializeOwned, - { - de::Deserialize::deserialize(PathDeserializer::new(req.match_info())) - .map(|inner| Path { inner }) - } } impl AsRef for Path { @@ -169,8 +160,10 @@ where type Future = Result; #[inline] - fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - Self::extract(req.request()).map_err(ErrorNotFound) + fn from_request(req: &HttpRequest, _: &mut Payload

) -> Self::Future { + de::Deserialize::deserialize(PathDeserializer::new(req.match_info())) + .map(|inner| Path { inner }) + .map_err(ErrorNotFound) } } @@ -185,25 +178,30 @@ mod tests { fn test_extract_path_single() { let resource = ResourceDef::new("/{value}/"); - let mut req = TestRequest::with_uri("/32/").to_from(); + let mut req = TestRequest::with_uri("/32/").to_srv_request(); resource.match_path(req.match_info_mut()); - assert_eq!(*Path::::from_request(&mut req).unwrap(), 32); + let (req, mut pl) = req.into_parts(); + assert_eq!(*Path::::from_request(&req, &mut pl).unwrap(), 32); } #[test] fn test_tuple_extract() { let resource = ResourceDef::new("/{key}/{value}/"); - let mut req = TestRequest::with_uri("/name/user1/?id=test").to_from(); + let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); resource.match_path(req.match_info_mut()); - let res = block_on(<(Path<(String, String)>,)>::from_request(&mut req)).unwrap(); + let (req, mut pl) = req.into_parts(); + let res = + block_on(<(Path<(String, String)>,)>::from_request(&req, &mut pl)).unwrap(); assert_eq!((res.0).0, "name"); assert_eq!((res.0).1, "user1"); let res = block_on( - <(Path<(String, String)>, Path<(String, String)>)>::from_request(&mut req), + <(Path<(String, String)>, Path<(String, String)>)>::from_request( + &req, &mut pl, + ), ) .unwrap(); assert_eq!((res.0).0, "name"); @@ -211,7 +209,7 @@ mod tests { assert_eq!((res.1).0, "name"); assert_eq!((res.1).1, "user1"); - let () = <()>::from_request(&mut req).unwrap(); + let () = <()>::from_request(&req, &mut pl).unwrap(); } } diff --git a/src/types/payload.rs b/src/types/payload.rs index 9cdbd0577..4c7dbdcc6 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -10,9 +10,10 @@ use futures::future::{err, Either, FutureResult}; use futures::{Future, Poll, Stream}; use mime::Mime; +use crate::dev; use crate::extract::FromRequest; use crate::http::header; -use crate::service::ServiceFromRequest; +use crate::request::HttpRequest; /// Payload extractor returns request 's payload stream. /// @@ -92,8 +93,8 @@ where type Future = Result; #[inline] - fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - let pl = match req.take_payload() { + fn from_request(_: &HttpRequest, payload: &mut dev::Payload

) -> Self::Future { + let pl = match payload.take() { crate::dev::Payload::Stream(s) => { let pl: Box> = Box::new(s); @@ -141,7 +142,7 @@ where Either>, FutureResult>; #[inline] - fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { + fn from_request(req: &HttpRequest, payload: &mut dev::Payload

) -> Self::Future { let mut tmp; let cfg = if let Some(cfg) = req.route_data::() { cfg @@ -155,7 +156,9 @@ where } let limit = cfg.limit; - Either::A(Box::new(HttpMessageBody::new(req).limit(limit).from_err())) + Either::A(Box::new( + HttpMessageBody::new(req, payload).limit(limit).from_err(), + )) } } @@ -194,7 +197,7 @@ where Either>, FutureResult>; #[inline] - fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { + fn from_request(req: &HttpRequest, payload: &mut dev::Payload

) -> Self::Future { let mut tmp; let cfg = if let Some(cfg) = req.route_data::() { cfg @@ -216,7 +219,7 @@ where let limit = cfg.limit; Either::A(Box::new( - HttpMessageBody::new(req) + HttpMessageBody::new(req, payload) .limit(limit) .from_err() .and_then(move |body| { @@ -260,7 +263,7 @@ impl PayloadConfig { self } - fn check_mimetype

(&self, req: &ServiceFromRequest

) -> Result<(), Error> { + fn check_mimetype(&self, req: &HttpRequest) -> Result<(), Error> { // check content-type if let Some(ref mt) = self.mimetype { match req.mime_type() { @@ -297,21 +300,20 @@ impl Default for PayloadConfig { /// By default only 256Kb payload reads to a memory, then /// `PayloadError::Overflow` get returned. Use `MessageBody::limit()` /// method to change upper limit. -pub struct HttpMessageBody { +pub struct HttpMessageBody

{ limit: usize, length: Option, - stream: actix_http::Payload, + stream: dev::Payload

, err: Option, fut: Option>>, } -impl HttpMessageBody +impl

HttpMessageBody

where - T: HttpMessage, - T::Stream: Stream, + P: Stream, { /// Create `MessageBody` for request. - pub fn new(req: &mut T) -> HttpMessageBody { + pub fn new(req: &HttpRequest, payload: &mut dev::Payload

) -> HttpMessageBody

{ let mut len = None; if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) { if let Ok(s) = l.to_str() { @@ -326,7 +328,7 @@ where } HttpMessageBody { - stream: req.take_payload(), + stream: payload.take(), limit: 262_144, length: len, fut: None, @@ -342,7 +344,7 @@ where fn err(e: PayloadError) -> Self { HttpMessageBody { - stream: actix_http::Payload::None, + stream: dev::Payload::None, limit: 262_144, fut: None, err: Some(e), @@ -351,10 +353,9 @@ where } } -impl Future for HttpMessageBody +impl

Future for HttpMessageBody

where - T: HttpMessage, - T::Stream: Stream + 'static, + P: Stream + 'static, { type Item = Bytes; type Error = PayloadError; @@ -403,7 +404,7 @@ mod tests { #[test] fn test_payload_config() { - let req = TestRequest::default().to_from(); + let req = TestRequest::default().to_http_request(); let cfg = PayloadConfig::default().mimetype(mime::APPLICATION_JSON); assert!(cfg.check_mimetype(&req).is_err()); @@ -411,62 +412,64 @@ mod tests { header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) - .to_from(); + .to_http_request(); assert!(cfg.check_mimetype(&req).is_err()); - let req = - TestRequest::with_header(header::CONTENT_TYPE, "application/json").to_from(); + let req = TestRequest::with_header(header::CONTENT_TYPE, "application/json") + .to_http_request(); assert!(cfg.check_mimetype(&req).is_ok()); } #[test] fn test_bytes() { - let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11") + let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "11") .set_payload(Bytes::from_static(b"hello=world")) - .to_from(); + .to_http_parts(); - let s = block_on(Bytes::from_request(&mut req)).unwrap(); + let s = block_on(Bytes::from_request(&req, &mut pl)).unwrap(); assert_eq!(s, Bytes::from_static(b"hello=world")); } #[test] fn test_string() { - let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11") + let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "11") .set_payload(Bytes::from_static(b"hello=world")) - .to_from(); + .to_http_parts(); - let s = block_on(String::from_request(&mut req)).unwrap(); + let s = block_on(String::from_request(&req, &mut pl)).unwrap(); assert_eq!(s, "hello=world"); } #[test] fn test_message_body() { - let mut req = - TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").to_request(); - let res = block_on(HttpMessageBody::new(&mut req)); + let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx") + .to_srv_request() + .into_parts(); + let res = block_on(HttpMessageBody::new(&req, &mut pl)); match res.err().unwrap() { PayloadError::UnknownLength => (), _ => unreachable!("error"), } - let mut req = - TestRequest::with_header(header::CONTENT_LENGTH, "1000000").to_request(); - let res = block_on(HttpMessageBody::new(&mut req)); + let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "1000000") + .to_srv_request() + .into_parts(); + let res = block_on(HttpMessageBody::new(&req, &mut pl)); match res.err().unwrap() { PayloadError::Overflow => (), _ => unreachable!("error"), } - let mut req = TestRequest::default() + let (req, mut pl) = TestRequest::default() .set_payload(Bytes::from_static(b"test")) - .to_request(); - let res = block_on(HttpMessageBody::new(&mut req)); + .to_http_parts(); + let res = block_on(HttpMessageBody::new(&req, &mut pl)); assert_eq!(res.ok().unwrap(), Bytes::from_static(b"test")); - let mut req = TestRequest::default() + let (req, mut pl) = TestRequest::default() .set_payload(Bytes::from_static(b"11111111111111")) - .to_request(); - let res = block_on(HttpMessageBody::new(&mut req).limit(5)); + .to_http_parts(); + let res = block_on(HttpMessageBody::new(&req, &mut pl).limit(5)); match res.err().unwrap() { PayloadError::Overflow => (), _ => unreachable!("error"), diff --git a/src/types/query.rs b/src/types/query.rs index 85dab0610..3bbb465c9 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -6,8 +6,9 @@ use actix_http::error::Error; use serde::de; use serde_urlencoded; +use crate::dev::Payload; use crate::extract::FromRequest; -use crate::service::ServiceFromRequest; +use crate::request::HttpRequest; #[derive(PartialEq, Eq, PartialOrd, Ord)] /// Extract typed information from from the request's query. @@ -118,8 +119,8 @@ where type Future = Result; #[inline] - fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - serde_urlencoded::from_str::(req.request().query_string()) + fn from_request(req: &HttpRequest, _: &mut Payload

) -> Self::Future { + serde_urlencoded::from_str::(req.query_string()) .map(|val| Ok(Query(val))) .unwrap_or_else(|e| Err(e.into())) } From aa78565453cbe337608eedaa16eb8e5dbb6a52e9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 7 Apr 2019 23:06:21 -0700 Subject: [PATCH 031/122] use objects pool for HttpRequest; optimize nested services call --- Cargo.toml | 6 +- actix-files/src/lib.rs | 3 +- actix-http/src/client/h2proto.rs | 3 +- actix-http/src/client/pool.rs | 1 + actix-http/src/h1/decoder.rs | 133 +++++++++++++++---------------- actix-http/src/h1/dispatcher.rs | 2 +- actix-http/src/httpcodes.rs | 2 +- actix-http/src/lib.rs | 6 +- actix-http/src/message.rs | 36 ++++----- actix-http/src/response.rs | 60 ++++++-------- actix-http/src/test.rs | 2 +- awc/src/test.rs | 4 +- src/app_service.rs | 41 +++++++--- src/handler.rs | 114 ++++++++++++-------------- src/request.rs | 80 +++++++++++++++---- src/resource.rs | 13 ++- src/route.rs | 54 ++++++++----- src/scope.rs | 5 +- src/service.rs | 31 ++----- src/test.rs | 42 +++++----- 20 files changed, 343 insertions(+), 295 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 86a168093..8318ada5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,9 +68,9 @@ rust-tls = ["rustls", "actix-server/rust-tls"] [dependencies] actix-codec = "0.1.1" -actix-service = "0.3.4" +actix-service = "0.3.6" actix-utils = "0.3.4" -actix-router = "0.1.1" +actix-router = "0.1.2" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-alpha.1" actix-http = { version = "0.1.0-alpha.3", features=["fail"] } @@ -124,4 +124,4 @@ 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" } +awc = { path = "awc" } \ No newline at end of file diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 6820d3622..e8eb8afda 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -423,7 +423,7 @@ impl

FilesService

{ > { log::debug!("Files: Failed to handle {}: {}", req.path(), e); if let Some(ref mut default) = self.default { - Either::B(default.call(ServiceRequest::from_parts(req, payload))) + default.call(ServiceRequest::from_parts(req, payload)) } else { Either::A(ok(ServiceResponse::from_err(e, req.clone()))) } @@ -955,6 +955,7 @@ mod tests { .method(Method::POST) .to_http_request(); let resp = file.respond_to(&req).unwrap(); + println!("RES: {:?}", resp); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); let file = NamedFile::open("Cargo.toml").unwrap(); diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 222e442c5..da70a878e 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -104,9 +104,8 @@ where let (parts, body) = resp.into_parts(); let payload = if head_req { Payload::None } else { body.into() }; - let mut head = ResponseHead::default(); + let mut head = ResponseHead::new(parts.status); head.version = parts.version; - head.status = parts.status; head.headers = parts.headers.into(); Ok((head, payload)) diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index aff11181b..2d1785381 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -21,6 +21,7 @@ use tokio_timer::{sleep, Delay}; use super::connection::{ConnectionType, IoConnection}; use super::error::ConnectError; +#[allow(dead_code)] #[derive(Clone, Copy, PartialEq)] pub enum Protocol { Http1, diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 417441c6a..411649fc1 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -73,78 +73,75 @@ pub(crate) trait MessageType: Sized { let headers = self.headers_mut(); for idx in raw_headers.iter() { - if let Ok(name) = HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) - { - // Unsafe: httparse check header value for valid utf-8 - let value = unsafe { - HeaderValue::from_shared_unchecked( - slice.slice(idx.value.0, idx.value.1), - ) - }; - match name { - header::CONTENT_LENGTH => { - if let Ok(s) = value.to_str() { - if let Ok(len) = s.parse::() { - if len != 0 { - content_length = Some(len); - } - } else { - debug!("illegal Content-Length: {:?}", s); - return Err(ParseError::Header); + let name = + HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]).unwrap(); + + // Unsafe: httparse check header value for valid utf-8 + let value = unsafe { + HeaderValue::from_shared_unchecked( + slice.slice(idx.value.0, idx.value.1), + ) + }; + match name { + header::CONTENT_LENGTH => { + if let Ok(s) = value.to_str() { + if let Ok(len) = s.parse::() { + if len != 0 { + content_length = Some(len); } } else { - debug!("illegal Content-Length: {:?}", value); + debug!("illegal Content-Length: {:?}", s); return Err(ParseError::Header); } + } else { + debug!("illegal Content-Length: {:?}", value); + return Err(ParseError::Header); } - // transfer-encoding - header::TRANSFER_ENCODING => { - if let Ok(s) = value.to_str().map(|s| s.trim()) { - chunked = s.eq_ignore_ascii_case("chunked"); - } else { - return Err(ParseError::Header); - } + } + // transfer-encoding + header::TRANSFER_ENCODING => { + if let Ok(s) = value.to_str().map(|s| s.trim()) { + chunked = s.eq_ignore_ascii_case("chunked"); + } else { + return Err(ParseError::Header); } - // connection keep-alive state - header::CONNECTION => { - ka = if let Ok(conn) = value.to_str().map(|conn| conn.trim()) - { - if conn.eq_ignore_ascii_case("keep-alive") { - Some(ConnectionType::KeepAlive) - } else if conn.eq_ignore_ascii_case("close") { - Some(ConnectionType::Close) - } else if conn.eq_ignore_ascii_case("upgrade") { - Some(ConnectionType::Upgrade) - } else { - None - } + } + // connection keep-alive state + header::CONNECTION => { + ka = if let Ok(conn) = value.to_str().map(|conn| conn.trim()) { + if conn.eq_ignore_ascii_case("keep-alive") { + Some(ConnectionType::KeepAlive) + } else if conn.eq_ignore_ascii_case("close") { + Some(ConnectionType::Close) + } else if conn.eq_ignore_ascii_case("upgrade") { + Some(ConnectionType::Upgrade) } else { None - }; - } - header::UPGRADE => { - has_upgrade = true; - // check content-length, some clients (dart) - // sends "content-length: 0" with websocket upgrade - if let Ok(val) = value.to_str().map(|val| val.trim()) { - if val.eq_ignore_ascii_case("websocket") { - content_length = None; - } } - } - header::EXPECT => { - let bytes = value.as_bytes(); - if bytes.len() >= 4 && &bytes[0..4] == b"100-" { - expect = true; - } - } - _ => (), + } else { + None + }; } - - headers.append(name, value); - } else { - return Err(ParseError::Header); + header::UPGRADE => { + has_upgrade = true; + // check content-length, some clients (dart) + // sends "content-length: 0" with websocket upgrade + if let Ok(val) = value.to_str().map(|val| val.trim()) { + if val.eq_ignore_ascii_case("websocket") { + content_length = None; + } + } + } + header::EXPECT => { + let bytes = value.as_bytes(); + if bytes.len() >= 4 && &bytes[0..4] == b"100-" { + expect = true; + } + } + _ => (), } + + headers.append(name, value); } } self.set_connection_type(ka); @@ -217,10 +214,10 @@ impl MessageType for Request { let mut msg = Request::new(); // convert headers - let len = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?; + let length = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?; // payload decoder - let decoder = match len { + let decoder = match length { PayloadLength::Payload(pl) => pl, PayloadLength::Upgrade => { // upgrade(websocket) @@ -287,13 +284,14 @@ impl MessageType for ResponseHead { } }; - let mut msg = ResponseHead::default(); + let mut msg = ResponseHead::new(status); + msg.version = ver; // convert headers - let len = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?; + let length = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?; // message payload - let decoder = if let PayloadLength::Payload(pl) = len { + let decoder = if let PayloadLength::Payload(pl) = length { pl } else if status == StatusCode::SWITCHING_PROTOCOLS { // switching protocol or connect @@ -305,9 +303,6 @@ impl MessageType for ResponseHead { PayloadType::None }; - msg.status = status; - msg.version = ver; - Ok(Some((msg, decoder))) } } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index bff05ab5c..a223161f9 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -218,7 +218,7 @@ where { fn can_read(&self) -> bool { if self.flags.contains(Flags::READ_DISCONNECT) { - return false; + false } else if let Some(ref info) = self.payload { info.need_read() == PayloadStatus::Read } else { diff --git a/actix-http/src/httpcodes.rs b/actix-http/src/httpcodes.rs index 5dfeefa9e..85c384374 100644 --- a/actix-http/src/httpcodes.rs +++ b/actix-http/src/httpcodes.rs @@ -8,7 +8,7 @@ macro_rules! STATIC_RESP { ($name:ident, $status:expr) => { #[allow(non_snake_case, missing_docs)] pub fn $name() -> ResponseBuilder { - Response::build($status) + ResponseBuilder::new($status) } }; } diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 5879e1915..5af802601 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -1,5 +1,9 @@ //! Basic http primitives for actix-net framework. -#![allow(clippy::type_complexity, clippy::new_without_default)] +#![allow( + clippy::type_complexity, + clippy::new_without_default, + clippy::borrow_interior_mutable_const +)] #[macro_use] extern crate log; diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index e1fb3a111..61ca5161e 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -1,5 +1,4 @@ use std::cell::{Ref, RefCell, RefMut}; -use std::collections::VecDeque; use std::rc::Rc; use bitflags::bitflags; @@ -171,20 +170,20 @@ pub struct ResponseHead { flags: Flags, } -impl Default for ResponseHead { - fn default() -> ResponseHead { +impl ResponseHead { + /// Create new instance of `ResponseHead` type + #[inline] + pub fn new(status: StatusCode) -> ResponseHead { ResponseHead { + status, version: Version::default(), - status: StatusCode::OK, headers: HeaderMap::with_capacity(12), reason: None, flags: Flags::empty(), extensions: RefCell::new(Extensions::new()), } } -} -impl ResponseHead { /// Message extensions #[inline] pub fn extensions(&self) -> Ref { @@ -335,8 +334,8 @@ pub(crate) struct BoxedResponseHead { impl BoxedResponseHead { /// Get new message from the pool of objects - pub fn new() -> Self { - RESPONSE_POOL.with(|p| p.get_message()) + pub fn new(status: StatusCode) -> Self { + RESPONSE_POOL.with(|p| p.get_message(status)) } } @@ -362,25 +361,25 @@ impl Drop for BoxedResponseHead { #[doc(hidden)] /// Request's objects pool -pub struct MessagePool(RefCell>>); +pub struct MessagePool(RefCell>>); #[doc(hidden)] /// Request's objects pool -pub struct BoxedResponsePool(RefCell>>); +pub struct BoxedResponsePool(RefCell>>); thread_local!(static REQUEST_POOL: &'static MessagePool = MessagePool::::create()); thread_local!(static RESPONSE_POOL: &'static BoxedResponsePool = BoxedResponsePool::create()); impl MessagePool { fn create() -> &'static MessagePool { - let pool = MessagePool(RefCell::new(VecDeque::with_capacity(128))); + let pool = MessagePool(RefCell::new(Vec::with_capacity(128))); Box::leak(Box::new(pool)) } /// Get message from the pool #[inline] fn get_message(&'static self) -> Message { - if let Some(mut msg) = self.0.borrow_mut().pop_front() { + if let Some(mut msg) = self.0.borrow_mut().pop() { if let Some(r) = Rc::get_mut(&mut msg) { r.clear(); } @@ -397,28 +396,29 @@ impl MessagePool { fn release(&self, msg: Rc) { let v = &mut self.0.borrow_mut(); if v.len() < 128 { - v.push_front(msg); + v.push(msg); } } } impl BoxedResponsePool { fn create() -> &'static BoxedResponsePool { - let pool = BoxedResponsePool(RefCell::new(VecDeque::with_capacity(128))); + let pool = BoxedResponsePool(RefCell::new(Vec::with_capacity(128))); Box::leak(Box::new(pool)) } /// Get message from the pool #[inline] - fn get_message(&'static self) -> BoxedResponseHead { - if let Some(mut head) = self.0.borrow_mut().pop_front() { + fn get_message(&'static self, status: StatusCode) -> BoxedResponseHead { + if let Some(mut head) = self.0.borrow_mut().pop() { head.reason = None; + head.status = status; head.headers.clear(); head.flags = Flags::empty(); BoxedResponseHead { head: Some(head) } } else { BoxedResponseHead { - head: Some(Box::alloc().init(ResponseHead::default())), + head: Some(Box::alloc().init(ResponseHead::new(status))), } } } @@ -428,7 +428,7 @@ impl BoxedResponsePool { fn release(&self, msg: Box) { let v = &mut self.0.borrow_mut(); if v.len() < 128 { - v.push_front(msg); + v.push(msg); } } } diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 707c9af63..330d33a45 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -41,11 +41,8 @@ impl Response { /// Constructs a response #[inline] pub fn new(status: StatusCode) -> Response { - let mut head = BoxedResponseHead::new(); - head.status = status; - Response { - head, + head: BoxedResponseHead::new(status), body: ResponseBody::Body(Body::Empty), error: None, } @@ -78,6 +75,16 @@ impl Response { } impl Response { + /// Constructs a response with body + #[inline] + pub fn with_body(status: StatusCode, body: B) -> Response { + Response { + head: BoxedResponseHead::new(status), + body: ResponseBody::Body(body), + error: None, + } + } + #[inline] /// Http message part of the response pub fn head(&self) -> &ResponseHead { @@ -90,18 +97,6 @@ impl Response { &mut *self.head } - /// Constructs a response with body - #[inline] - pub fn with_body(status: StatusCode, body: B) -> Response { - let mut head = BoxedResponseHead::new(); - head.status = status; - Response { - head, - body: ResponseBody::Body(body), - error: None, - } - } - /// The source `error` for this response #[inline] pub fn error(&self) -> Option<&Error> { @@ -325,13 +320,11 @@ pub struct ResponseBuilder { } impl ResponseBuilder { + #[inline] /// Create response builder pub fn new(status: StatusCode) -> Self { - let mut head = BoxedResponseHead::new(); - head.status = status; - ResponseBuilder { - head: Some(head), + head: Some(BoxedResponseHead::new(status)), err: None, cookies: None, } @@ -555,15 +548,13 @@ impl ResponseBuilder { /// } /// ``` pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self { - { - if self.cookies.is_none() { - self.cookies = Some(CookieJar::new()) - } - let jar = self.cookies.as_mut().unwrap(); - let cookie = cookie.clone().into_owned(); - jar.add_original(cookie.clone()); - jar.remove(cookie); + if self.cookies.is_none() { + self.cookies = Some(CookieJar::new()) } + let jar = self.cookies.as_mut().unwrap(); + let cookie = cookie.clone().into_owned(); + jar.add_original(cookie.clone()); + jar.remove(cookie); self } @@ -605,6 +596,7 @@ impl ResponseBuilder { head.extensions.borrow_mut() } + #[inline] /// Set a body and generate `Response`. /// /// `ResponseBuilder` can not be used after this call. @@ -625,9 +617,7 @@ impl ResponseBuilder { if let Some(ref jar) = self.cookies { for cookie in jar.delta() { match HeaderValue::from_str(&cookie.to_string()) { - Ok(val) => { - let _ = response.headers.append(header::SET_COOKIE, val); - } + Ok(val) => response.headers.append(header::SET_COOKIE, val), Err(e) => return Response::from(Error::from(e)).into_body(), }; } @@ -652,6 +642,7 @@ impl ResponseBuilder { self.body(Body::from_message(BodyStream::new(stream))) } + #[inline] /// Set a json body and generate `Response` /// /// `ResponseBuilder` can not be used after this call. @@ -751,11 +742,12 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { } } - let mut msg = BoxedResponseHead::new(); + let mut msg = BoxedResponseHead::new(head.status); msg.version = head.version; - msg.status = head.status; msg.reason = head.reason; - // msg.headers = head.headers.clone(); + for (k, v) in &head.headers { + msg.headers.append(k.clone(), v.clone()); + } msg.no_chunking(!head.chunked()); ResponseBuilder { diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 302d75d73..2c5dc502b 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -178,6 +178,6 @@ impl TestRequest { } #[inline] -fn parts<'a>(parts: &'a mut Option) -> &'a mut Inner { +fn parts(parts: &mut Option) -> &mut Inner { parts.as_mut().expect("cannot reuse test request builder") } diff --git a/awc/src/test.rs b/awc/src/test.rs index 1c772905e..fbbadef3a 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -3,7 +3,7 @@ use std::fmt::Write as FmtWrite; use actix_http::cookie::{Cookie, CookieJar}; use actix_http::http::header::{self, Header, HeaderValue, IntoHeaderValue}; -use actix_http::http::{HeaderName, HttpTryFrom, Version}; +use actix_http::http::{HeaderName, HttpTryFrom, StatusCode, Version}; use actix_http::{h1, Payload, ResponseHead}; use bytes::Bytes; #[cfg(test)] @@ -49,7 +49,7 @@ pub struct TestResponse { impl Default for TestResponse { fn default() -> TestResponse { TestResponse { - head: ResponseHead::default(), + head: ResponseHead::new(StatusCode::OK), cookies: CookieJar::new(), payload: None, } diff --git a/src/app_service.rs b/src/app_service.rs index 236eed9f9..593fbe673 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -14,6 +14,7 @@ use crate::config::{AppConfig, ServiceConfig}; use crate::data::{DataFactory, DataFactoryResult}; use crate::error::Error; use crate::guard::Guard; +use crate::request::{HttpRequest, HttpRequestPool}; use crate::rmap::ResourceMap; use crate::service::{ServiceFactory, ServiceRequest, ServiceResponse}; @@ -21,7 +22,10 @@ type Guards = Vec>; type HttpService

= BoxedService, ServiceResponse, Error>; type HttpNewService

= BoxedNewService<(), ServiceRequest

, ServiceResponse, Error, ()>; -type BoxedResponse = Box>; +type BoxedResponse = Either< + FutureResult, + Box>, +>; /// Service factory to convert `Request` to a `ServiceRequest`. /// It also executes data factories. @@ -191,6 +195,7 @@ where chain: self.chain.take().unwrap(), rmap: self.rmap.clone(), config: self.config.clone(), + pool: HttpRequestPool::create(), } .and_then(self.endpoint.take().unwrap()), )) @@ -208,6 +213,7 @@ where chain: C, rmap: Rc, config: AppConfig, + pool: &'static HttpRequestPool, } impl Service for AppInitService @@ -224,13 +230,24 @@ where } fn call(&mut self, req: Request) -> Self::Future { - let req = ServiceRequest::new( - Path::new(Url::new(req.uri().clone())), - req, - self.rmap.clone(), - self.config.clone(), - ); - self.chain.call(req) + let (head, payload) = req.into_parts(); + + let req = if let Some(mut req) = self.pool.get_request() { + let inner = Rc::get_mut(&mut req.0).unwrap(); + inner.path.get_mut().update(&head.uri); + inner.path.reset(); + inner.head = head; + req + } else { + HttpRequest::new( + Path::new(Url::new(head.uri.clone())), + head, + self.rmap.clone(), + self.config.clone(), + self.pool, + ) + }; + self.chain.call(ServiceRequest::from_parts(req, payload)) } } @@ -353,7 +370,7 @@ impl

Service for AppRouting

{ type Request = ServiceRequest

; type Response = ServiceResponse; type Error = Error; - type Future = Either>; + type Future = BoxedResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { if self.ready.is_none() { @@ -376,12 +393,12 @@ impl

Service for AppRouting

{ }); if let Some((srv, _info)) = res { - Either::A(srv.call(req)) + srv.call(req) } else if let Some(ref mut default) = self.default { - Either::A(default.call(req)) + default.call(req) } else { let req = req.into_parts().0; - Either::B(ok(ServiceResponse::new(req, Response::NotFound().finish()))) + Either::A(ok(ServiceResponse::new(req, Response::NotFound().finish()))) } } } diff --git a/src/handler.rs b/src/handler.rs index 921b8334d..42a9d88d7 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -52,37 +52,21 @@ where } } } -impl NewService for Handler + +impl Clone for Handler where F: Factory, R: Responder, { - type Request = (T, HttpRequest); - type Response = ServiceResponse; - type Error = Void; - type InitError = (); - type Service = HandlerService; - type Future = FutureResult; - - fn new_service(&self, _: &()) -> Self::Future { - ok(HandlerService { + fn clone(&self) -> Self { + Self { hnd: self.hnd.clone(), _t: PhantomData, - }) + } } } -#[doc(hidden)] -pub struct HandlerService -where - F: Factory, - R: Responder, -{ - hnd: F, - _t: PhantomData<(T, R)>, -} - -impl Service for HandlerService +impl Service for Handler where F: Factory, R: Responder, @@ -184,41 +168,23 @@ where } } } -impl NewService for AsyncHandler + +impl Clone for AsyncHandler where F: AsyncFactory, R: IntoFuture, R::Item: Into, R::Error: Into, { - type Request = (T, HttpRequest); - type Response = ServiceResponse; - type Error = Error; - type InitError = (); - type Service = AsyncHandlerService; - type Future = FutureResult; - - fn new_service(&self, _: &()) -> Self::Future { - ok(AsyncHandlerService { + fn clone(&self) -> Self { + AsyncHandler { hnd: self.hnd.clone(), _t: PhantomData, - }) + } } } -#[doc(hidden)] -pub struct AsyncHandlerService -where - F: AsyncFactory, - R: IntoFuture, - R::Item: Into, - R::Error: Into, -{ - hnd: F, - _t: PhantomData<(T, R)>, -} - -impl Service for AsyncHandlerService +impl Service for AsyncHandler where F: AsyncFactory, R: IntoFuture, @@ -227,7 +193,7 @@ where { type Request = (T, HttpRequest); type Response = ServiceResponse; - type Error = Error; + type Error = Void; type Future = AsyncHandlerServiceResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { @@ -255,7 +221,7 @@ where T::Error: Into, { type Item = ServiceResponse; - type Error = Error; + type Error = Void; fn poll(&mut self) -> Poll { match self.fut.poll() { @@ -276,46 +242,58 @@ where } /// Extract arguments from request -pub struct Extract> { +pub struct Extract, S> { config: Rc>>>, + service: S, _t: PhantomData<(P, T)>, } -impl> Extract { - pub fn new(config: Rc>>>) -> Self { +impl, S> Extract { + pub fn new(config: Rc>>>, service: S) -> Self { Extract { config, + service, _t: PhantomData, } } } -impl> NewService for Extract { +impl, S> NewService for Extract +where + S: Service + + Clone, +{ type Request = ServiceRequest

; - type Response = (T, HttpRequest); + type Response = ServiceResponse; type Error = (Error, ServiceRequest

); type InitError = (); - type Service = ExtractService; + type Service = ExtractService; type Future = FutureResult; fn new_service(&self, _: &()) -> Self::Future { ok(ExtractService { _t: PhantomData, config: self.config.borrow().clone(), + service: self.service.clone(), }) } } -pub struct ExtractService> { +pub struct ExtractService, S> { config: Option>, + service: S, _t: PhantomData<(P, T)>, } -impl> Service for ExtractService { +impl, S> Service for ExtractService +where + S: Service + + Clone, +{ type Request = ServiceRequest

; - type Response = (T, HttpRequest); + type Response = ServiceResponse; type Error = (Error, ServiceRequest

); - type Future = ExtractResponse; + type Future = ExtractResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { Ok(Async::Ready(())) @@ -328,28 +306,40 @@ impl> Service for ExtractService { ExtractResponse { fut, + fut_s: None, req: Some((req, payload)), + service: self.service.clone(), } } } -pub struct ExtractResponse> { +pub struct ExtractResponse, S: Service> { req: Option<(HttpRequest, Payload

)>, + service: S, fut: ::Future, + fut_s: Option, } -impl> Future for ExtractResponse { - type Item = (T, HttpRequest); +impl, S> Future for ExtractResponse +where + S: Service, +{ + type Item = ServiceResponse; type Error = (Error, ServiceRequest

); fn poll(&mut self) -> Poll { + if let Some(ref mut fut) = self.fut_s { + return fut.poll().map_err(|_| panic!()); + } + let item = try_ready!(self.fut.poll().map_err(|e| { let (req, payload) = self.req.take().unwrap(); let req = ServiceRequest::from_parts(req, payload); (e.into(), req) })); - Ok(Async::Ready((item, self.req.take().unwrap().0))) + self.fut_s = Some(self.service.call((item, self.req.take().unwrap().0))); + self.poll() } } diff --git a/src/request.rs b/src/request.rs index ff38c879c..53d848f0d 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,4 +1,4 @@ -use std::cell::{Ref, RefMut}; +use std::cell::{Ref, RefCell, RefMut}; use std::fmt; use std::rc::Rc; @@ -15,29 +15,34 @@ use crate::rmap::ResourceMap; #[derive(Clone)] /// An HTTP Request -pub struct HttpRequest { +pub struct HttpRequest(pub(crate) Rc); + +pub(crate) struct HttpRequestInner { pub(crate) head: Message, pub(crate) path: Path, rmap: Rc, config: AppConfig, route_data: Option>, + pool: &'static HttpRequestPool, } impl HttpRequest { #[inline] pub(crate) fn new( - head: Message, path: Path, + head: Message, rmap: Rc, config: AppConfig, + pool: &'static HttpRequestPool, ) -> HttpRequest { - HttpRequest { + HttpRequest(Rc::new(HttpRequestInner { head, path, rmap, config, + pool, route_data: None, - } + })) } } @@ -45,7 +50,14 @@ impl HttpRequest { /// This method returns reference to the request head #[inline] pub fn head(&self) -> &RequestHead { - &self.head + &self.0.head + } + + /// This method returns muttable reference to the request head. + /// panics if multiple references of http request exists. + #[inline] + pub(crate) fn head_mut(&mut self) -> &mut RequestHead { + &mut Rc::get_mut(&mut self.0).unwrap().head } /// Request's uri. @@ -98,7 +110,12 @@ impl HttpRequest { /// access the matched value for that segment. #[inline] pub fn match_info(&self) -> &Path { - &self.path + &self.0.path + } + + #[inline] + pub(crate) fn match_info_mut(&mut self) -> &mut Path { + &mut Rc::get_mut(&mut self.0).unwrap().path } /// Request extensions @@ -141,7 +158,7 @@ impl HttpRequest { U: IntoIterator, I: AsRef, { - self.rmap.url_for(&self, name, elements) + self.0.rmap.url_for(&self, name, elements) } /// Generate url for named resource @@ -162,13 +179,13 @@ impl HttpRequest { /// App config #[inline] pub fn app_config(&self) -> &AppConfig { - &self.config + &self.0.config } /// Get an application data stored with `App::data()` method during /// application configuration. pub fn app_data(&self) -> Option> { - if let Some(st) = self.config.extensions().get::>() { + if let Some(st) = self.0.config.extensions().get::>() { Some(st.clone()) } else { None @@ -178,7 +195,7 @@ impl HttpRequest { /// Load route data. Route data could be set during /// route configuration with `Route::data()` method. pub fn route_data(&self) -> Option<&RouteData> { - if let Some(ref ext) = self.route_data { + if let Some(ref ext) = self.0.route_data { ext.get::>() } else { None @@ -186,7 +203,7 @@ impl HttpRequest { } pub(crate) fn set_route_data(&mut self, data: Option>) { - self.route_data = data; + Rc::get_mut(&mut self.0).unwrap().route_data = data; } } @@ -202,13 +219,13 @@ impl HttpMessage for HttpRequest { /// Request extensions #[inline] fn extensions(&self) -> Ref { - self.head.extensions() + self.0.head.extensions() } /// Mutable reference to a the request's extensions #[inline] fn extensions_mut(&self) -> RefMut { - self.head.extensions_mut() + self.0.head.extensions_mut() } #[inline] @@ -217,6 +234,17 @@ impl HttpMessage for HttpRequest { } } +impl Drop for HttpRequest { + fn drop(&mut self) { + if Rc::strong_count(&self.0) == 1 { + let v = &mut self.0.pool.0.borrow_mut(); + if v.len() < 128 { + v.push(self.0.clone()); + } + } + } +} + /// It is possible to get `HttpRequest` as an extractor handler parameter /// /// ## Example @@ -252,8 +280,8 @@ impl fmt::Debug for HttpRequest { writeln!( f, "\nHttpRequest {:?} {}:{}", - self.head.version, - self.head.method, + self.0.head.version, + self.0.head.method, self.path() )?; if !self.query_string().is_empty() { @@ -270,6 +298,26 @@ impl fmt::Debug for HttpRequest { } } +/// Request's objects pool +pub(crate) struct HttpRequestPool(RefCell>>); + +impl HttpRequestPool { + pub(crate) fn create() -> &'static HttpRequestPool { + let pool = HttpRequestPool(RefCell::new(Vec::with_capacity(128))); + Box::leak(Box::new(pool)) + } + + /// Get message from the pool + #[inline] + pub(crate) fn get_request(&self) -> Option { + if let Some(inner) = self.0.borrow_mut().pop() { + Some(HttpRequest(inner)) + } else { + None + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/resource.rs b/src/resource.rs index 957795cd7..313a3bc06 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -487,11 +487,8 @@ impl

Service for ResourceService

{ type Response = ServiceResponse; type Error = Error; type Future = Either< + FutureResult, Box>, - Either< - Box>, - FutureResult, - >, >; fn poll_ready(&mut self) -> Poll<(), Self::Error> { @@ -501,17 +498,17 @@ impl

Service for ResourceService

{ fn call(&mut self, mut req: ServiceRequest

) -> Self::Future { for route in self.routes.iter_mut() { if route.check(&mut req) { - return Either::A(route.call(req)); + return route.call(req); } } if let Some(ref mut default) = self.default { - Either::B(Either::A(default.call(req))) + default.call(req) } else { let req = req.into_parts().0; - Either::B(Either::B(ok(ServiceResponse::new( + Either::A(ok(ServiceResponse::new( req, Response::MethodNotAllowed().finish(), - )))) + ))) } } } diff --git a/src/route.rs b/src/route.rs index 349668ef4..8bff863fe 100644 --- a/src/route.rs +++ b/src/route.rs @@ -4,6 +4,7 @@ use std::rc::Rc; use actix_http::{http::Method, Error, Extensions, Response}; use actix_service::{NewService, Service}; +use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; use crate::data::RouteData; @@ -19,7 +20,10 @@ type BoxedRouteService = Box< Request = Req, Response = Res, Error = Error, - Future = Box>, + Future = Either< + FutureResult, + Box>, + >, >, >; @@ -50,11 +54,10 @@ impl Route

{ pub fn new() -> Route

{ let data_ref = Rc::new(RefCell::new(None)); Route { - service: Box::new(RouteNewService::new( - Extract::new(data_ref.clone()).and_then( - Handler::new(HttpResponse::NotFound).map_err(|_| panic!()), - ), - )), + service: Box::new(RouteNewService::new(Extract::new( + data_ref.clone(), + Handler::new(|| HttpResponse::NotFound()), + ))), guards: Rc::new(Vec::new()), data: None, data_ref, @@ -131,7 +134,10 @@ impl

Service for RouteService

{ type Request = ServiceRequest

; type Response = ServiceResponse; type Error = Error; - type Future = Box>; + type Future = Either< + FutureResult, + Box>, + >; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.service.poll_ready() @@ -235,10 +241,10 @@ impl Route

{ T: FromRequest

+ 'static, R: Responder + 'static, { - self.service = Box::new(RouteNewService::new( - Extract::new(self.data_ref.clone()) - .and_then(Handler::new(handler).map_err(|_| panic!())), - )); + self.service = Box::new(RouteNewService::new(Extract::new( + self.data_ref.clone(), + Handler::new(handler), + ))); self } @@ -277,10 +283,10 @@ impl Route

{ R::Item: Into, R::Error: Into, { - self.service = Box::new(RouteNewService::new( - Extract::new(self.data_ref.clone()) - .and_then(AsyncHandler::new(handler).map_err(|_| panic!())), - )); + self.service = Box::new(RouteNewService::new(Extract::new( + self.data_ref.clone(), + AsyncHandler::new(handler), + ))); self } @@ -394,17 +400,25 @@ where type Request = ServiceRequest

; type Response = ServiceResponse; type Error = Error; - type Future = Box>; + type Future = Either< + FutureResult, + Box>, + >; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.service.poll_ready().map_err(|(e, _)| e) } fn call(&mut self, req: ServiceRequest

) -> Self::Future { - Box::new(self.service.call(req).then(|res| match res { - Ok(res) => Ok(res), - Err((err, req)) => Ok(req.error_response(err)), - })) + let mut fut = self.service.call(req); + match fut.poll() { + Ok(Async::Ready(res)) => Either::A(ok(res)), + Err((e, req)) => Either::A(ok(req.error_response(e))), + Ok(Async::NotReady) => Either::B(Box::new(fut.then(|res| match res { + Ok(res) => Ok(res), + Err((err, req)) => Ok(req.error_response(err)), + }))), + } } } diff --git a/src/scope.rs b/src/scope.rs index 7ad2d95eb..2cb01961a 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -24,7 +24,10 @@ type Guards = Vec>; type HttpService

= BoxedService, ServiceResponse, Error>; type HttpNewService

= BoxedNewService<(), ServiceRequest

, ServiceResponse, Error, ()>; -type BoxedResponse = Box>; +type BoxedResponse = Either< + FutureResult, + Box>, +>; /// Resources scope. /// diff --git a/src/service.rs b/src/service.rs index 13eea9d14..f0ff02158 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,13 +1,12 @@ use std::cell::{Ref, RefMut}; use std::fmt; use std::marker::PhantomData; -use std::rc::Rc; use actix_http::body::{Body, MessageBody, ResponseBody}; use actix_http::http::{HeaderMap, Method, StatusCode, Uri, Version}; use actix_http::{ - Error, Extensions, HttpMessage, Payload, PayloadStream, Request, RequestHead, - Response, ResponseHead, + Error, Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response, + ResponseHead, }; use actix_router::{Path, Resource, Url}; use futures::future::{ok, FutureResult, IntoFuture}; @@ -15,7 +14,6 @@ use futures::future::{ok, FutureResult, IntoFuture}; use crate::config::{AppConfig, ServiceConfig}; use crate::data::Data; use crate::request::HttpRequest; -use crate::rmap::ResourceMap; pub trait HttpServiceFactory

{ fn register(self, config: &mut ServiceConfig

); @@ -56,19 +54,6 @@ pub struct ServiceRequest

{ } impl

ServiceRequest

{ - pub(crate) fn new( - path: Path, - request: Request

, - rmap: Rc, - config: AppConfig, - ) -> Self { - let (head, payload) = request.into_parts(); - ServiceRequest { - payload, - req: HttpRequest::new(head, path, rmap, config), - } - } - /// Construct service request from parts pub fn from_parts(req: HttpRequest, payload: Payload

) -> Self { ServiceRequest { req, payload } @@ -95,13 +80,13 @@ impl

ServiceRequest

{ /// This method returns reference to the request head #[inline] pub fn head(&self) -> &RequestHead { - &self.req.head + &self.req.head() } /// This method returns reference to the request head #[inline] pub fn head_mut(&mut self) -> &mut RequestHead { - &mut self.req.head + self.req.head_mut() } /// Request's uri. @@ -160,12 +145,12 @@ impl

ServiceRequest

{ /// access the matched value for that segment. #[inline] pub fn match_info(&self) -> &Path { - &self.req.path + self.req.match_info() } #[inline] pub fn match_info_mut(&mut self) -> &mut Path { - &mut self.req.path + self.req.match_info_mut() } /// Service configuration @@ -203,13 +188,13 @@ impl

HttpMessage for ServiceRequest

{ /// Request extensions #[inline] fn extensions(&self) -> Ref { - self.req.head.extensions() + self.req.extensions() } /// Mutable reference to a the request's extensions #[inline] fn extensions_mut(&self) -> RefMut { - self.req.head.extensions_mut() + self.req.extensions_mut() } #[inline] diff --git a/src/test.rs b/src/test.rs index 58cb1211e..5444726e1 100644 --- a/src/test.rs +++ b/src/test.rs @@ -17,6 +17,7 @@ use futures::future::{lazy, Future}; use crate::config::{AppConfig, AppConfigInner}; use crate::data::RouteData; use crate::dev::{Body, Payload}; +use crate::request::HttpRequestPool; use crate::rmap::ResourceMap; use crate::service::{ServiceRequest, ServiceResponse}; use crate::{Error, HttpRequest, HttpResponse}; @@ -326,14 +327,17 @@ impl TestRequest { /// Complete request creation and generate `ServiceRequest` instance pub fn to_srv_request(mut self) -> ServiceRequest { - let req = self.req.finish(); + let (head, payload) = self.req.finish().into_parts(); - ServiceRequest::new( - Path::new(Url::new(req.uri().clone())), - req, + let req = HttpRequest::new( + Path::new(Url::new(head.uri.clone())), + head, Rc::new(self.rmap), AppConfig::new(self.config), - ) + HttpRequestPool::create(), + ); + + ServiceRequest::from_parts(req, payload) } /// Complete request creation and generate `ServiceResponse` instance @@ -343,34 +347,32 @@ impl TestRequest { /// Complete request creation and generate `HttpRequest` instance pub fn to_http_request(mut self) -> HttpRequest { - let req = self.req.finish(); + let (head, _) = self.req.finish().into_parts(); - let mut req = ServiceRequest::new( - Path::new(Url::new(req.uri().clone())), - req, + let mut req = HttpRequest::new( + Path::new(Url::new(head.uri.clone())), + head, Rc::new(self.rmap), AppConfig::new(self.config), - ) - .into_parts() - .0; + HttpRequestPool::create(), + ); req.set_route_data(Some(Rc::new(self.route_data))); req } /// Complete request creation and generate `HttpRequest` and `Payload` instances pub fn to_http_parts(mut self) -> (HttpRequest, Payload) { - let req = self.req.finish(); + let (head, payload) = self.req.finish().into_parts(); - let (mut req, pl) = ServiceRequest::new( - Path::new(Url::new(req.uri().clone())), - req, + let mut req = HttpRequest::new( + Path::new(Url::new(head.uri.clone())), + head, Rc::new(self.rmap), AppConfig::new(self.config), - ) - .into_parts(); - + HttpRequestPool::create(), + ); req.set_route_data(Some(Rc::new(self.route_data))); - (req, pl) + (req, payload) } /// Runs the provided future, blocking the current thread until the future From 53da55aa3c39f47ade8bb16b9a05e827db7fbecc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 7 Apr 2019 23:42:05 -0700 Subject: [PATCH 032/122] alpha4 release --- CHANGES.md | 2 +- Cargo.toml | 8 ++++---- actix-files/CHANGES.md | 5 +++++ actix-files/Cargo.toml | 6 +++--- actix-files/src/lib.rs | 1 - actix-http/CHANGES.md | 2 +- actix-http/Cargo.toml | 4 ++-- actix-session/CHANGES.md | 4 ++++ actix-session/Cargo.toml | 4 ++-- awc/CHANGES.md | 7 +++++++ awc/Cargo.toml | 8 ++++---- 11 files changed, 33 insertions(+), 18 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3c619eee4..f05137ab9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [1.0.0-alpha.4] - 2019-04-xx +## [1.0.0-alpha.4] - 2019-04-08 ### Added diff --git a/Cargo.toml b/Cargo.toml index 8318ada5a..22b7b13a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.0-alpha.3" +version = "1.0.0-alpha.4" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -73,11 +73,11 @@ actix-utils = "0.3.4" actix-router = "0.1.2" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-alpha.1" -actix-http = { version = "0.1.0-alpha.3", features=["fail"] } +actix-http = { version = "0.1.0-alpha.4", features=["fail"] } actix-server = "0.4.2" actix-server-config = "0.1.0" actix-threadpool = "0.1.0" -awc = { version = "0.1.0-alpha.3", optional = true } +awc = { version = "0.1.0-alpha.4", optional = true } bytes = "0.4" derive_more = "0.14" @@ -101,7 +101,7 @@ openssl = { version="0.10", optional = true } rustls = { version = "^0.15", optional = true } [dev-dependencies] -actix-http = { version = "0.1.0-alpha.3", features=["ssl", "brotli", "flate2-zlib"] } +actix-http = { version = "0.1.0-alpha.4", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } rand = "0.6" env_logger = "0.6" diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 7c46b40f7..f7a88ba43 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## [0.1.0-alpha.4] - 2019-04-08 + +* Update actix-web to alpha4 + + ## [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 a1044c6da..e017b1326 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.1.0-alpha.2" +version = "0.1.0-alpha.4" authors = ["Nikolay Kim "] description = "Static files support for actix web." readme = "README.md" @@ -18,7 +18,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = "1.0.0-alpha.3" +actix-web = "1.0.0-alpha.4" 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.3", features=["ssl"] } +actix-web = { version = "1.0.0-alpha.4", features=["ssl"] } diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index e8eb8afda..6ebf43363 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -955,7 +955,6 @@ mod tests { .method(Method::POST) .to_http_request(); let resp = file.respond_to(&req).unwrap(); - println!("RES: {:?}", resp); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); let file = NamedFile::open("Cargo.toml").unwrap(); diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 25439604f..4cc18b479 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.1.0-alpha.4] - 2019-04-xx +## [0.1.0-alpha.4] - 2019-04-08 ### Added diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 528ab617c..967a224e2 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.1.0-alpha.3" +version = "0.1.0-alpha.4" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" @@ -48,7 +48,7 @@ fail = ["failure"] secure-cookies = ["ring"] [dependencies] -actix-service = "0.3.4" +actix-service = "0.3.6" actix-codec = "0.1.2" actix-connect = "0.1.2" actix-utils = "0.3.5" diff --git a/actix-session/CHANGES.md b/actix-session/CHANGES.md index 85e1123a9..305fa561e 100644 --- a/actix-session/CHANGES.md +++ b/actix-session/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.0-alpha.4] - 2019-04-08 + +* Update actix-web + ## [0.1.0-alpha.3] - 2019-04-02 * Update actix-web diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index 956906fad..00a4c5244 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-session" -version = "0.1.0-alpha.3" +version = "0.1.0-alpha.4" 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.3" +actix-web = "1.0.0-alpha.4" actix-service = "0.3.4" bytes = "0.4" derive_more = "0.14" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 0f0bd9f5b..fc32cd7eb 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.1.0-alpha.4] - 2019-04-08 + +### Changed + +* Update actix-http dependency + + ## [0.1.0-alpha.3] - 2019-04-02 ### Added diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 9f4d916e4..17a5d18fe 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "0.1.0-alpha.3" +version = "0.1.0-alpha.4" authors = ["Nikolay Kim "] description = "Actix http client." readme = "README.md" @@ -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-alpha.3" +actix-http = "0.1.0-alpha.4" base64 = "0.10.1" bytes = "0.4" derive_more = "0.14" @@ -55,8 +55,8 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.2" -actix-web = { version = "1.0.0-alpha.3", features=["ssl"] } -actix-http = { version = "0.1.0-alpha.3", features=["ssl"] } +actix-web = { version = "1.0.0-alpha.4", features=["ssl"] } +actix-http = { version = "0.1.0-alpha.4", 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"] } From a7fdac1043a0a13985e46a5935c9eebd2834e4f4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Apr 2019 10:31:29 -0700 Subject: [PATCH 033/122] fix expect service registration and tests --- actix-http/src/builder.rs | 25 +++++++++++++-- actix-http/tests/test_server.rs | 56 ++++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 2a8a8360f..6d93c156f 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -87,6 +87,27 @@ where self } + /// Provide service for `EXPECT: 100-Continue` support. + /// + /// Service get called with request that contains `EXPECT` header. + /// Service must return request in case of success, in that case + /// request will be forwarded to main service. + pub fn expect(self, expect: F) -> HttpServiceBuilder + where + F: IntoNewService, + U: NewService, + U::Error: Into, + U::InitError: fmt::Debug, + { + HttpServiceBuilder { + keep_alive: self.keep_alive, + client_timeout: self.client_timeout, + client_disconnect: self.client_disconnect, + expect: expect.into_new_service(), + _t: PhantomData, + } + } + // #[cfg(feature = "ssl")] // /// Configure alpn protocols for SslAcceptorBuilder. // pub fn configure_openssl( @@ -142,7 +163,7 @@ where } /// Finish service configuration and create `HttpService` instance. - pub fn finish(self, service: F) -> HttpService + pub fn finish(self, service: F) -> HttpService where B: MessageBody + 'static, F: IntoNewService, @@ -156,6 +177,6 @@ where self.client_timeout, self.client_disconnect, ); - HttpService::with_config(cfg, service.into_new_service()) + HttpService::with_config(cfg, service.into_new_service()).expect(self.expect) } } diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index da41492f2..e7b539372 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -5,7 +5,7 @@ use std::{net, thread}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_http_test::TestServer; use actix_server_config::ServerConfig; -use actix_service::{fn_cfg_factory, NewService}; +use actix_service::{fn_cfg_factory, fn_service, NewService}; use bytes::{Bytes, BytesMut}; use futures::future::{self, ok, Future}; use futures::stream::{once, Stream}; @@ -153,6 +153,60 @@ fn test_h2_body() -> std::io::Result<()> { Ok(()) } +#[test] +fn test_expect_continue() { + let srv = TestServer::new(|| { + HttpService::build() + .expect(fn_service(|req: Request| { + if req.head().uri.query() == Some("yes=") { + Ok(req) + } else { + Err(error::ErrorPreconditionFailed("error")) + } + })) + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 412 Precondition Failed\r\ncontent-length")); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\n")); +} + +#[test] +fn test_expect_continue_h1() { + let srv = TestServer::new(|| { + HttpService::build() + .expect(fn_service(|req: Request| { + if req.head().uri.query() == Some("yes=") { + Ok(req) + } else { + Err(error::ErrorPreconditionFailed("error")) + } + })) + .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 412 Precondition Failed\r\ncontent-length")); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\n")); +} + #[test] fn test_slow_request() { let srv = TestServer::new(|| { From b1547bbbb68938977e236e11c01a113adb06bb1f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Apr 2019 11:09:57 -0700 Subject: [PATCH 034/122] do not set default headers --- actix-http/src/client/connection.rs | 19 +++++++- actix-http/src/client/h2proto.rs | 1 - actix-http/src/client/mod.rs | 1 + actix-http/src/client/pool.rs | 2 +- awc/CHANGES.md | 7 +++ awc/src/request.rs | 76 ++++++++--------------------- 6 files changed, 46 insertions(+), 60 deletions(-) diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index c5d720efd..9354fca4a 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -12,7 +12,7 @@ use crate::message::{RequestHead, ResponseHead}; use crate::payload::Payload; use super::error::SendRequestError; -use super::pool::Acquired; +use super::pool::{Acquired, Protocol}; use super::{h1proto, h2proto}; pub(crate) enum ConnectionType { @@ -24,6 +24,8 @@ pub trait Connection { type Io: AsyncRead + AsyncWrite; type Future: Future; + fn protocol(&self) -> Protocol; + /// Send request and body fn send_request( self, @@ -94,6 +96,14 @@ where type Io = T; type Future = Box>; + fn protocol(&self) -> Protocol { + match self.io { + Some(ConnectionType::H1(_)) => Protocol::Http1, + Some(ConnectionType::H2(_)) => Protocol::Http2, + None => Protocol::Http1, + } + } + fn send_request( mut self, head: RequestHead, @@ -161,6 +171,13 @@ where type Io = EitherIo; type Future = Box>; + fn protocol(&self) -> Protocol { + match self { + EitherConnection::A(con) => con.protocol(), + EitherConnection::B(con) => con.protocol(), + } + } + fn send_request( self, head: RequestHead, diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index da70a878e..ec5beee6b 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -107,7 +107,6 @@ where let mut head = ResponseHead::new(parts.status); head.version = parts.version; head.headers = parts.headers.into(); - Ok((head, payload)) }) .from_err() diff --git a/actix-http/src/client/mod.rs b/actix-http/src/client/mod.rs index 87c374742..cf526e25e 100644 --- a/actix-http/src/client/mod.rs +++ b/actix-http/src/client/mod.rs @@ -9,3 +9,4 @@ mod pool; pub use self::connection::Connection; pub use self::connector::Connector; pub use self::error::{ConnectError, InvalidUrl, SendRequestError}; +pub use self::pool::Protocol; diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 2d1785381..68ac6fbc8 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -21,8 +21,8 @@ use tokio_timer::{sleep, Delay}; use super::connection::{ConnectionType, IoConnection}; use super::error::ConnectError; -#[allow(dead_code)] #[derive(Clone, Copy, PartialEq)] +/// Protocol version pub enum Protocol { Http1, Http2, diff --git a/awc/CHANGES.md b/awc/CHANGES.md index fc32cd7eb..767761cef 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.1.0-alpha.5] - 2019-04-xx + +### Changed + +* Do not set any default headers + + ## [0.1.0-alpha.4] - 2019-04-08 ### Changed diff --git a/awc/src/request.rs b/awc/src/request.rs index 09a7eecc4..b21c101c4 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -61,7 +61,6 @@ pub struct ClientRequest { pub(crate) head: RequestHead, err: Option, cookies: Option, - default_headers: bool, response_decompress: bool, timeout: Option, config: Rc, @@ -79,7 +78,6 @@ impl ClientRequest { err: None, cookies: None, timeout: None, - default_headers: true, response_decompress: true, } .method(method) @@ -316,13 +314,6 @@ impl ClientRequest { self } - /// Do not add default request headers. - /// By default `Date` and `User-Agent` headers are set. - 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) -> Self { self.response_decompress = false; @@ -392,36 +383,6 @@ impl ClientRequest { return Either::A(err(InvalidUrl::UnknownScheme.into())); } - // set default headers - if self.default_headers { - // set request host header - 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 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) => { - self.head.headers.insert(header::HOST, value); - } - Err(e) => return Either::A(err(HttpError::from(e).into())), - } - } - } - - // user agent - 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"))), - ); - } - } - // set cookies if let Some(ref mut jar) = self.cookies { let mut cookie = String::new(); @@ -436,7 +397,7 @@ impl ClientRequest { ); } - let slf = self; + let mut slf = self; // enable br only for https #[cfg(any( @@ -444,25 +405,26 @@ impl ClientRequest { 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 slf.response_decompress { + 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 + if https { + slf = slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING) + } else { + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] + { + slf = slf + .set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate") + } + }; } - }; + } let head = slf.head; let config = slf.config.as_ref(); From bc58dbb2f5fa8cae488cf3ac632732f99a8f0827 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Apr 2019 11:19:56 -0700 Subject: [PATCH 035/122] add async expect service test --- actix-http/tests/test_server.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index e7b539372..e53ff0212 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -9,6 +9,7 @@ use actix_service::{fn_cfg_factory, fn_service, NewService}; use bytes::{Bytes, BytesMut}; use futures::future::{self, ok, Future}; use futures::stream::{once, Stream}; +use tokio_timer::sleep; use actix_http::body::Body; use actix_http::error::PayloadError; @@ -185,11 +186,13 @@ fn test_expect_continue_h1() { let srv = TestServer::new(|| { HttpService::build() .expect(fn_service(|req: Request| { - if req.head().uri.query() == Some("yes=") { - Ok(req) - } else { - Err(error::ErrorPreconditionFailed("error")) - } + sleep(Duration::from_millis(20)).then(move |_| { + if req.head().uri.query() == Some("yes=") { + Ok(req) + } else { + Err(error::ErrorPreconditionFailed("error")) + } + }) })) .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); From 9bcd5d6664f46176f9c97b94f9e6dd87be043052 Mon Sep 17 00:00:00 2001 From: Darin Date: Mon, 8 Apr 2019 14:20:46 -0400 Subject: [PATCH 036/122] updated legacy code in call_success example (#762) --- src/test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test.rs b/src/test.rs index 5444726e1..5b44d1c79 100644 --- a/src/test.rs +++ b/src/test.rs @@ -133,7 +133,7 @@ where /// let req = test::TestRequest::with_uri("/test").to_request(); /// /// // Call application -/// let resp = test::call_succ_service(&mut app, req); +/// let resp = test::call_success(&mut app, req); /// assert_eq!(resp.status(), StatusCode::OK); /// } /// ``` From b921abf18ff7608296b7c5df6512cbad53f26811 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Apr 2019 12:48:26 -0700 Subject: [PATCH 037/122] set host header for http1 connections --- actix-http/src/client/h1proto.rs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 5fec9c4f1..3a8b119d3 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -1,12 +1,14 @@ +use std::io::Write; use std::{io, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use bytes::Bytes; +use bytes::{BufMut, Bytes, BytesMut}; use futures::future::{ok, Either}; use futures::{Async, Future, Poll, Sink, Stream}; use crate::error::PayloadError; use crate::h1; +use crate::http::header::{IntoHeaderValue, HOST}; use crate::message::{RequestHead, ResponseHead}; use crate::payload::{Payload, PayloadStream}; @@ -17,7 +19,7 @@ use crate::body::{BodySize, MessageBody}; pub(crate) fn send_request( io: T, - head: RequestHead, + mut head: RequestHead, body: B, created: time::Instant, pool: Option>, @@ -26,6 +28,27 @@ where T: AsyncRead + AsyncWrite + 'static, B: MessageBody, { + // set request host header + if !head.headers.contains_key(HOST) { + if let Some(host) = head.uri.host() { + let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); + + let _ = match 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(HOST, value); + } + Err(e) => { + log::error!("Can not set HOST header {}", e); + } + } + } + } + let io = H1Connection { created, pool, From 0a6dd0efdf77a9b938fc61e7216517e219ff668a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Apr 2019 12:48:39 -0700 Subject: [PATCH 038/122] fix compression tests --- tests/test_server.rs | 55 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/tests/test_server.rs b/tests/test_server.rs index 76ce2c76e..597e69300 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -73,7 +73,14 @@ 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() + .header(ACCEPT_ENCODING, "gzip") + .send(), + ) + .unwrap(); assert!(response.status().is_success()); // read response @@ -111,7 +118,14 @@ 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() + .header(ACCEPT_ENCODING, "deflate") + .send(), + ) + .unwrap(); assert!(response.status().is_success()); // read response @@ -128,6 +142,7 @@ fn test_body_encoding_override() { .block_on( srv.request(actix_web::http::Method::GET, srv.url("/raw")) .no_decompress() + .header(ACCEPT_ENCODING, "deflate") .send(), ) .unwrap(); @@ -161,7 +176,14 @@ 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() + .header(ACCEPT_ENCODING, "gzip") + .send(), + ) + .unwrap(); assert!(response.status().is_success()); // read response @@ -195,7 +217,14 @@ 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() + .header(ACCEPT_ENCODING, "gzip") + .send(), + ) + .unwrap(); assert!(response.status().is_success()); // read response @@ -224,7 +253,14 @@ 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() + .header(ACCEPT_ENCODING, "gzip") + .send(), + ) + .unwrap(); assert!(response.status().is_success()); assert_eq!( response.headers().get(TRANSFER_ENCODING).unwrap(), @@ -333,7 +369,14 @@ 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("/") + .header(ACCEPT_ENCODING, "deflate") + .no_decompress() + .send(), + ) + .unwrap(); assert!(response.status().is_success()); // read response From 43d325a139eb16676ffea26eac3495f85fd452e4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Apr 2019 14:51:16 -0700 Subject: [PATCH 039/122] allow to specify upgrade service --- actix-http/src/builder.rs | 58 +++++++--------- actix-http/src/h1/dispatcher.rs | 29 ++++++-- actix-http/src/h1/mod.rs | 2 + actix-http/src/h1/service.rs | 89 +++++++++++++++++++----- actix-http/src/h1/upgrade.rs | 40 +++++++++++ actix-http/src/service.rs | 118 +++++++++++++++++++++++++------- actix-session/src/cookie.rs | 1 - 7 files changed, 254 insertions(+), 83 deletions(-) create mode 100644 actix-http/src/h1/upgrade.rs diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 6d93c156f..7b07d30e3 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -1,13 +1,14 @@ use std::fmt; use std::marker::PhantomData; +use actix_codec::Framed; use actix_server_config::ServerConfig as SrvConfig; use actix_service::{IntoNewService, NewService, Service}; use crate::body::MessageBody; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::Error; -use crate::h1::{ExpectHandler, H1Service}; +use crate::h1::{Codec, ExpectHandler, H1Service, UpgradeHandler}; use crate::h2::H2Service; use crate::request::Request; use crate::response::Response; @@ -17,15 +18,16 @@ use crate::service::HttpService; /// /// This type can be used to construct an instance of `http service` through a /// builder-like pattern. -pub struct HttpServiceBuilder { +pub struct HttpServiceBuilder> { keep_alive: KeepAlive, client_timeout: u64, client_disconnect: u64, expect: X, + upgrade: Option, _t: PhantomData<(T, S)>, } -impl HttpServiceBuilder +impl HttpServiceBuilder> where S: NewService, S::Error: Into, @@ -38,12 +40,13 @@ where client_timeout: 5000, client_disconnect: 0, expect: ExpectHandler, + upgrade: None, _t: PhantomData, } } } -impl HttpServiceBuilder +impl HttpServiceBuilder where S: NewService, S::Error: Into, @@ -51,11 +54,14 @@ where X: NewService, X::Error: Into, X::InitError: fmt::Debug, + U: NewService), Response = ()>, + U::Error: fmt::Display, + U::InitError: fmt::Debug, { /// Set server keep-alive setting. /// /// By default keep alive is set to a 5 seconds. - pub fn keep_alive>(mut self, val: U) -> Self { + pub fn keep_alive>(mut self, val: W) -> Self { self.keep_alive = val.into(); self } @@ -92,43 +98,25 @@ where /// Service get called with request that contains `EXPECT` header. /// Service must return request in case of success, in that case /// request will be forwarded to main service. - pub fn expect(self, expect: F) -> HttpServiceBuilder + pub fn expect(self, expect: F) -> HttpServiceBuilder where - F: IntoNewService, - U: NewService, - U::Error: Into, - U::InitError: fmt::Debug, + F: IntoNewService, + X1: NewService, + X1::Error: Into, + X1::InitError: fmt::Debug, { HttpServiceBuilder { keep_alive: self.keep_alive, client_timeout: self.client_timeout, client_disconnect: self.client_disconnect, expect: expect.into_new_service(), + upgrade: self.upgrade, _t: PhantomData, } } - // #[cfg(feature = "ssl")] - // /// Configure alpn protocols for SslAcceptorBuilder. - // pub fn configure_openssl( - // builder: &mut openssl::ssl::SslAcceptorBuilder, - // ) -> io::Result<()> { - // let protos: &[u8] = b"\x02h2"; - // builder.set_alpn_select_callback(|_, protos| { - // const H2: &[u8] = b"\x02h2"; - // if protos.windows(3).any(|window| window == H2) { - // Ok(b"h2") - // } else { - // Err(openssl::ssl::AlpnError::NOACK) - // } - // }); - // builder.set_alpn_protos(&protos)?; - - // Ok(()) - // } - /// Finish service configuration and create *http service* for HTTP/1 protocol. - pub fn h1(self, service: F) -> H1Service + pub fn h1(self, service: F) -> H1Service where B: MessageBody + 'static, F: IntoNewService, @@ -141,7 +129,9 @@ where self.client_timeout, self.client_disconnect, ); - H1Service::with_config(cfg, service.into_new_service()).expect(self.expect) + H1Service::with_config(cfg, service.into_new_service()) + .expect(self.expect) + .upgrade(self.upgrade) } /// Finish service configuration and create *http service* for HTTP/2 protocol. @@ -163,7 +153,7 @@ where } /// Finish service configuration and create `HttpService` instance. - pub fn finish(self, service: F) -> HttpService + pub fn finish(self, service: F) -> HttpService where B: MessageBody + 'static, F: IntoNewService, @@ -177,6 +167,8 @@ where self.client_timeout, self.client_disconnect, ); - HttpService::with_config(cfg, service.into_new_service()).expect(self.expect) + HttpService::with_config(cfg, service.into_new_service()) + .expect(self.expect) + .upgrade(self.upgrade) } } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index a223161f9..eccf2412b 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -2,7 +2,7 @@ use std::collections::VecDeque; use std::time::Instant; use std::{fmt, io}; -use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder}; +use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed}; use actix_service::Service; use actix_utils::cloneable::CloneableService; use bitflags::bitflags; @@ -39,27 +39,32 @@ bitflags! { } /// Dispatcher for HTTP/1.1 protocol -pub struct Dispatcher +pub struct Dispatcher where S: Service, S::Error: Into, B: MessageBody, X: Service, X::Error: Into, + U: Service), Response = ()>, + U::Error: fmt::Display, { - inner: Option>, + inner: Option>, } -struct InnerDispatcher +struct InnerDispatcher where S: Service, S::Error: Into, B: MessageBody, X: Service, X::Error: Into, + U: Service), Response = ()>, + U::Error: fmt::Display, { service: CloneableService, expect: CloneableService, + upgrade: Option>, flags: Flags, error: Option, @@ -132,7 +137,7 @@ where } } -impl Dispatcher +impl Dispatcher where T: AsyncRead + AsyncWrite, S: Service, @@ -141,6 +146,8 @@ where B: MessageBody, X: Service, X::Error: Into, + U: Service), Response = ()>, + U::Error: fmt::Display, { /// Create http/1 dispatcher. pub fn new( @@ -148,6 +155,7 @@ where config: ServiceConfig, service: CloneableService, expect: CloneableService, + upgrade: Option>, ) -> Self { Dispatcher::with_timeout( stream, @@ -157,6 +165,7 @@ where None, service, expect, + upgrade, ) } @@ -169,6 +178,7 @@ where timeout: Option, service: CloneableService, expect: CloneableService, + upgrade: Option>, ) -> Self { let keepalive = config.keep_alive_enabled(); let flags = if keepalive { @@ -198,6 +208,7 @@ where messages: VecDeque::new(), service, expect, + upgrade, flags, ka_expire, ka_timer, @@ -206,7 +217,7 @@ where } } -impl InnerDispatcher +impl InnerDispatcher where T: AsyncRead + AsyncWrite, S: Service, @@ -215,6 +226,8 @@ where B: MessageBody, X: Service, X::Error: Into, + U: Service), Response = ()>, + U::Error: fmt::Display, { fn can_read(&self) -> bool { if self.flags.contains(Flags::READ_DISCONNECT) { @@ -603,7 +616,7 @@ where } } -impl Future for Dispatcher +impl Future for Dispatcher where T: AsyncRead + AsyncWrite, S: Service, @@ -612,6 +625,8 @@ where B: MessageBody, X: Service, X::Error: Into, + U: Service), Response = ()>, + U::Error: fmt::Display, { type Item = (); type Error = DispatchError; diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs index 79d7cda8b..58712278f 100644 --- a/actix-http/src/h1/mod.rs +++ b/actix-http/src/h1/mod.rs @@ -9,6 +9,7 @@ mod encoder; mod expect; mod payload; mod service; +mod upgrade; pub use self::client::{ClientCodec, ClientPayloadCodec}; pub use self::codec::Codec; @@ -16,6 +17,7 @@ pub use self::dispatcher::Dispatcher; pub use self::expect::ExpectHandler; pub use self::payload::Payload; pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; +pub use self::upgrade::UpgradeHandler; #[derive(Debug)] /// Codec message diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index c3d21b4db..f92fd0c89 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -16,13 +16,14 @@ use crate::response::Response; use super::codec::Codec; use super::dispatcher::Dispatcher; -use super::{ExpectHandler, Message}; +use super::{ExpectHandler, Message, UpgradeHandler}; /// `NewService` implementation for HTTP1 transport -pub struct H1Service { +pub struct H1Service> { srv: S, cfg: ServiceConfig, expect: X, + upgrade: Option, _t: PhantomData<(T, P, B)>, } @@ -42,6 +43,7 @@ where cfg, srv: service.into_new_service(), expect: ExpectHandler, + upgrade: None, _t: PhantomData, } } @@ -55,12 +57,13 @@ where cfg, srv: service.into_new_service(), expect: ExpectHandler, + upgrade: None, _t: PhantomData, } } } -impl H1Service +impl H1Service where S: NewService, S::Error: Into, @@ -68,22 +71,38 @@ where S::InitError: fmt::Debug, B: MessageBody, { - pub fn expect(self, expect: U) -> H1Service + pub fn expect(self, expect: X1) -> H1Service where - U: NewService, - U::Error: Into, - U::InitError: fmt::Debug, + X1: NewService, + X1::Error: Into, + X1::InitError: fmt::Debug, { H1Service { expect, cfg: self.cfg, srv: self.srv, + upgrade: self.upgrade, + _t: PhantomData, + } + } + + pub fn upgrade(self, upgrade: Option) -> H1Service + where + U1: NewService), Response = ()>, + U1::Error: fmt::Display, + U1::InitError: fmt::Debug, + { + H1Service { + upgrade, + cfg: self.cfg, + srv: self.srv, + expect: self.expect, _t: PhantomData, } } } -impl NewService for H1Service +impl NewService for H1Service where T: AsyncRead + AsyncWrite, S: NewService, @@ -94,19 +113,24 @@ where X: NewService, X::Error: Into, X::InitError: fmt::Debug, + U: NewService), Response = ()>, + U::Error: fmt::Display, + U::InitError: fmt::Debug, { type Request = Io; type Response = (); type Error = DispatchError; type InitError = (); - type Service = H1ServiceHandler; - type Future = H1ServiceResponse; + type Service = H1ServiceHandler; + type Future = H1ServiceResponse; fn new_service(&self, cfg: &SrvConfig) -> Self::Future { H1ServiceResponse { fut: self.srv.new_service(cfg).into_future(), fut_ex: Some(self.expect.new_service(&())), + fut_upg: self.upgrade.as_ref().map(|f| f.new_service(&())), expect: None, + upgrade: None, cfg: Some(self.cfg.clone()), _t: PhantomData, } @@ -114,7 +138,7 @@ where } #[doc(hidden)] -pub struct H1ServiceResponse +pub struct H1ServiceResponse where S: NewService, S::Error: Into, @@ -122,15 +146,20 @@ where X: NewService, X::Error: Into, X::InitError: fmt::Debug, + U: NewService), Response = ()>, + U::Error: fmt::Display, + U::InitError: fmt::Debug, { fut: S::Future, fut_ex: Option, + fut_upg: Option, expect: Option, + upgrade: Option, cfg: Option, _t: PhantomData<(T, P, B)>, } -impl Future for H1ServiceResponse +impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite, S: NewService, @@ -141,8 +170,11 @@ where X: NewService, X::Error: Into, X::InitError: fmt::Debug, + U: NewService), Response = ()>, + U::Error: fmt::Display, + U::InitError: fmt::Debug, { - type Item = H1ServiceHandler; + type Item = H1ServiceHandler; type Error = (); fn poll(&mut self) -> Poll { @@ -154,6 +186,14 @@ where self.fut_ex.take(); } + if let Some(ref mut fut) = self.fut_upg { + let upgrade = try_ready!(fut + .poll() + .map_err(|e| log::error!("Init http service error: {:?}", e))); + self.upgrade = Some(upgrade); + self.fut_ex.take(); + } + let service = try_ready!(self .fut .poll() @@ -162,19 +202,21 @@ where self.cfg.take().unwrap(), service, self.expect.take().unwrap(), + self.upgrade.take(), ))) } } /// `Service` implementation for HTTP1 transport -pub struct H1ServiceHandler { +pub struct H1ServiceHandler { srv: CloneableService, expect: CloneableService, + upgrade: Option>, cfg: ServiceConfig, _t: PhantomData<(T, P, B)>, } -impl H1ServiceHandler +impl H1ServiceHandler where S: Service, S::Error: Into, @@ -182,18 +224,26 @@ where B: MessageBody, X: Service, X::Error: Into, + U: Service), Response = ()>, + U::Error: fmt::Display, { - fn new(cfg: ServiceConfig, srv: S, expect: X) -> H1ServiceHandler { + fn new( + cfg: ServiceConfig, + srv: S, + expect: X, + upgrade: Option, + ) -> H1ServiceHandler { H1ServiceHandler { srv: CloneableService::new(srv), expect: CloneableService::new(expect), + upgrade: upgrade.map(|s| CloneableService::new(s)), cfg, _t: PhantomData, } } } -impl Service for H1ServiceHandler +impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, S: Service, @@ -202,11 +252,13 @@ where B: MessageBody, X: Service, X::Error: Into, + U: Service), Response = ()>, + U::Error: fmt::Display, { type Request = Io; type Response = (); type Error = DispatchError; - type Future = Dispatcher; + type Future = Dispatcher; fn poll_ready(&mut self) -> Poll<(), Self::Error> { let ready = self @@ -243,6 +295,7 @@ where self.cfg.clone(), self.srv.clone(), self.expect.clone(), + self.upgrade.clone(), ) } } diff --git a/actix-http/src/h1/upgrade.rs b/actix-http/src/h1/upgrade.rs new file mode 100644 index 000000000..0d0164fe6 --- /dev/null +++ b/actix-http/src/h1/upgrade.rs @@ -0,0 +1,40 @@ +use std::marker::PhantomData; + +use actix_codec::Framed; +use actix_service::{NewService, Service}; +use futures::future::FutureResult; +use futures::{Async, Poll}; + +use crate::error::Error; +use crate::h1::Codec; +use crate::request::Request; + +pub struct UpgradeHandler(PhantomData); + +impl NewService for UpgradeHandler { + type Request = (Request, Framed); + type Response = (); + type Error = Error; + type Service = UpgradeHandler; + type InitError = Error; + type Future = FutureResult; + + fn new_service(&self, _: &()) -> Self::Future { + unimplemented!() + } +} + +impl Service for UpgradeHandler { + type Request = (Request, Framed); + type Response = (); + type Error = Error; + type Future = FutureResult; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, _: Self::Request) -> Self::Future { + unimplemented!() + } +} diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 57ab6ec25..2af1238b1 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use std::{fmt, io}; -use actix_codec::{AsyncRead, AsyncWrite}; +use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_server_config::{Io as ServerIo, Protocol, ServerConfig as SrvConfig}; use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; @@ -18,10 +18,11 @@ use crate::response::Response; use crate::{h1, h2::Dispatcher}; /// `NewService` HTTP1.1/HTTP2 transport implementation -pub struct HttpService { +pub struct HttpService> { srv: S, cfg: ServiceConfig, expect: X, + upgrade: Option, _t: PhantomData<(T, P, B)>, } @@ -57,6 +58,7 @@ where cfg, srv: service.into_new_service(), expect: h1::ExpectHandler, + upgrade: None, _t: PhantomData, } } @@ -70,12 +72,13 @@ where cfg, srv: service.into_new_service(), expect: h1::ExpectHandler, + upgrade: None, _t: PhantomData, } } } -impl HttpService +impl HttpService where S: NewService, S::Error: Into, @@ -88,22 +91,42 @@ where /// Service get called with request that contains `EXPECT` header. /// Service must return request in case of success, in that case /// request will be forwarded to main service. - pub fn expect(self, expect: U) -> HttpService + pub fn expect(self, expect: X1) -> HttpService where - U: NewService, - U::Error: Into, - U::InitError: fmt::Debug, + X1: NewService, + X1::Error: Into, + X1::InitError: fmt::Debug, { HttpService { expect, cfg: self.cfg, srv: self.srv, + upgrade: self.upgrade, + _t: PhantomData, + } + } + + /// Provide service for custom `Connection: UPGRADE` support. + /// + /// If service is provided then normal requests handling get halted + /// and this service get called with original request and framed object. + pub fn upgrade(self, upgrade: Option) -> HttpService + where + U1: NewService), Response = ()>, + U1::Error: fmt::Display, + U1::InitError: fmt::Debug, + { + HttpService { + upgrade, + cfg: self.cfg, + srv: self.srv, + expect: self.expect, _t: PhantomData, } } } -impl NewService for HttpService +impl NewService for HttpService where T: AsyncRead + AsyncWrite, S: NewService, @@ -115,19 +138,24 @@ where X: NewService, X::Error: Into, X::InitError: fmt::Debug, + U: NewService), Response = ()>, + U::Error: fmt::Display, + U::InitError: fmt::Debug, { type Request = ServerIo; type Response = (); type Error = DispatchError; type InitError = (); - type Service = HttpServiceHandler; - type Future = HttpServiceResponse; + type Service = HttpServiceHandler; + type Future = HttpServiceResponse; fn new_service(&self, cfg: &SrvConfig) -> Self::Future { HttpServiceResponse { fut: self.srv.new_service(cfg).into_future(), fut_ex: Some(self.expect.new_service(&())), + fut_upg: self.upgrade.as_ref().map(|f| f.new_service(&())), expect: None, + upgrade: None, cfg: Some(self.cfg.clone()), _t: PhantomData, } @@ -135,15 +163,24 @@ where } #[doc(hidden)] -pub struct HttpServiceResponse, B, X: NewService> { +pub struct HttpServiceResponse< + T, + P, + S: NewService, + B, + X: NewService, + U: NewService, +> { fut: S::Future, fut_ex: Option, + fut_upg: Option, expect: Option, + upgrade: Option, cfg: Option, _t: PhantomData<(T, P, B)>, } -impl Future for HttpServiceResponse +impl Future for HttpServiceResponse where T: AsyncRead + AsyncWrite, S: NewService, @@ -155,8 +192,11 @@ where X: NewService, X::Error: Into, X::InitError: fmt::Debug, + U: NewService), Response = ()>, + U::Error: fmt::Display, + U::InitError: fmt::Debug, { - type Item = HttpServiceHandler; + type Item = HttpServiceHandler; type Error = (); fn poll(&mut self) -> Poll { @@ -168,6 +208,14 @@ where self.fut_ex.take(); } + if let Some(ref mut fut) = self.fut_upg { + let upgrade = try_ready!(fut + .poll() + .map_err(|e| log::error!("Init http service error: {:?}", e))); + self.upgrade = Some(upgrade); + self.fut_ex.take(); + } + let service = try_ready!(self .fut .poll() @@ -176,19 +224,21 @@ where self.cfg.take().unwrap(), service, self.expect.take().unwrap(), + self.upgrade.take(), ))) } } /// `Service` implementation for http transport -pub struct HttpServiceHandler { +pub struct HttpServiceHandler { srv: CloneableService, expect: CloneableService, + upgrade: Option>, cfg: ServiceConfig, _t: PhantomData<(T, P, B, X)>, } -impl HttpServiceHandler +impl HttpServiceHandler where S: Service, S::Error: Into, @@ -197,18 +247,26 @@ where B: MessageBody + 'static, X: Service, X::Error: Into, + U: Service), Response = ()>, + U::Error: fmt::Display, { - fn new(cfg: ServiceConfig, srv: S, expect: X) -> HttpServiceHandler { + fn new( + cfg: ServiceConfig, + srv: S, + expect: X, + upgrade: Option, + ) -> HttpServiceHandler { HttpServiceHandler { cfg, srv: CloneableService::new(srv), expect: CloneableService::new(expect), + upgrade: upgrade.map(|s| CloneableService::new(s)), _t: PhantomData, } } } -impl Service for HttpServiceHandler +impl Service for HttpServiceHandler where T: AsyncRead + AsyncWrite, S: Service, @@ -218,11 +276,13 @@ where B: MessageBody + 'static, X: Service, X::Error: Into, + U: Service), Response = ()>, + U::Error: fmt::Display, { type Request = ServerIo; type Response = (); type Error = DispatchError; - type Future = HttpServiceHandlerResponse; + type Future = HttpServiceHandlerResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { let ready = self @@ -275,6 +335,7 @@ where self.cfg.clone(), self.srv.clone(), self.expect.clone(), + self.upgrade.clone(), )), }, _ => HttpServiceHandlerResponse { @@ -284,13 +345,14 @@ where self.cfg.clone(), self.srv.clone(), self.expect.clone(), + self.upgrade.clone(), ))), }, } } } -enum State +enum State where S: Service, S::Future: 'static, @@ -299,8 +361,10 @@ where B: MessageBody, X: Service, X::Error: Into, + U: Service), Response = ()>, + U::Error: fmt::Display, { - H1(h1::Dispatcher), + H1(h1::Dispatcher), H2(Dispatcher, S, B>), Unknown( Option<( @@ -309,12 +373,13 @@ where ServiceConfig, CloneableService, CloneableService, + Option>, )>, ), Handshake(Option<(Handshake, Bytes>, ServiceConfig, CloneableService)>), } -pub struct HttpServiceHandlerResponse +pub struct HttpServiceHandlerResponse where T: AsyncRead + AsyncWrite, S: Service, @@ -324,13 +389,15 @@ where B: MessageBody + 'static, X: Service, X::Error: Into, + U: Service), Response = ()>, + U::Error: fmt::Display, { - state: State, + state: State, } const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; -impl Future for HttpServiceHandlerResponse +impl Future for HttpServiceHandlerResponse where T: AsyncRead + AsyncWrite, S: Service, @@ -340,6 +407,8 @@ where B: MessageBody, X: Service, X::Error: Into, + U: Service), Response = ()>, + U::Error: fmt::Display, { type Item = (); type Error = DispatchError; @@ -366,7 +435,7 @@ where } else { panic!() } - let (io, buf, cfg, srv, expect) = data.take().unwrap(); + let (io, buf, cfg, srv, expect, upgrade) = data.take().unwrap(); if buf[..14] == HTTP2_PREFACE[..] { let io = Io { inner: io, @@ -383,6 +452,7 @@ where None, srv, expect, + upgrade, )) } self.poll() diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index f7b4ec03a..b44c87e04 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -295,7 +295,6 @@ where type Future = Box>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { - //self.service.poll_ready().map_err(|e| e.into()) self.service.poll_ready() } From 561f83d044510ba693ffc5a292dc59568d7d9289 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Apr 2019 17:49:27 -0700 Subject: [PATCH 040/122] add upgrade service support to h1 dispatcher --- actix-http/CHANGES.md | 9 ++ actix-http/src/builder.rs | 21 +++ actix-http/src/error.rs | 3 + actix-http/src/h1/dispatcher.rs | 259 ++++++++++++++++++++------------ actix-http/tests/test_client.rs | 4 +- actix-http/tests/test_ws.rs | 76 ++++++++++ 6 files changed, 271 insertions(+), 101 deletions(-) create mode 100644 actix-http/tests/test_ws.rs diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 4cc18b479..cca4560c5 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,9 +1,18 @@ # Changes +## [0.1.0-alpha.5] - 2019-04-xx + +### Added + +* Allow to use custom service for upgrade requests + + ## [0.1.0-alpha.4] - 2019-04-08 ### Added +* Allow to use custom `Expect` handler + * Add minimal `std::error::Error` impl for `Error` ### Changed diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 7b07d30e3..56f144bd8 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -115,6 +115,27 @@ where } } + /// Provide service for custom `Connection: UPGRADE` support. + /// + /// If service is provided then normal requests handling get halted + /// and this service get called with original request and framed object. + pub fn upgrade(self, upgrade: F) -> HttpServiceBuilder + where + F: IntoNewService, + U1: NewService), Response = ()>, + U1::Error: fmt::Display, + U1::InitError: fmt::Debug, + { + HttpServiceBuilder { + keep_alive: self.keep_alive, + client_timeout: self.client_timeout, + client_disconnect: self.client_disconnect, + expect: self.expect, + upgrade: Some(upgrade.into_new_service()), + _t: PhantomData, + } + } + /// Finish service configuration and create *http service* for HTTP/1 protocol. pub fn h1(self, service: F) -> H1Service where diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index fc37d3243..6573c8ce6 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -350,6 +350,9 @@ pub enum DispatchError { /// Service error Service(Error), + /// Upgrade service error + Upgrade, + /// An `io::Error` that occurred while trying to read or write to a network /// stream. #[display(fmt = "IO error: {}", _0)] diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index eccf2412b..9014047d7 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -2,7 +2,7 @@ use std::collections::VecDeque; use std::time::Instant; use std::{fmt, io}; -use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed}; +use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed, FramedParts}; use actix_service::Service; use actix_utils::cloneable::CloneableService; use bitflags::bitflags; @@ -34,7 +34,7 @@ bitflags! { const SHUTDOWN = 0b0000_1000; const READ_DISCONNECT = 0b0001_0000; const WRITE_DISCONNECT = 0b0010_0000; - const DROPPING = 0b0100_0000; + const UPGRADE = 0b0100_0000; } } @@ -49,7 +49,22 @@ where U: Service), Response = ()>, U::Error: fmt::Display, { - inner: Option>, + inner: DispatcherState, +} + +enum DispatcherState +where + S: Service, + S::Error: Into, + B: MessageBody, + X: Service, + X::Error: Into, + U: Service), Response = ()>, + U::Error: fmt::Display, +{ + Normal(InnerDispatcher), + Upgrade(U::Future), + None, } struct InnerDispatcher @@ -83,6 +98,7 @@ where enum DispatcherMessage { Item(Request), + Upgrade(Request), Error(Response<()>), } @@ -121,18 +137,24 @@ where } } -impl fmt::Debug for State -where - S: Service, - X: Service, - B: MessageBody, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +enum PollResponse { + Upgrade(Request), + DoNothing, + DrainWriteBuf, +} + +impl PartialEq for PollResponse { + fn eq(&self, other: &PollResponse) -> bool { match self { - State::None => write!(f, "State::None"), - State::ExpectCall(_) => write!(f, "State::ExceptCall"), - State::ServiceCall(_) => write!(f, "State::ServiceCall"), - State::SendPayload(_) => write!(f, "State::SendPayload"), + PollResponse::DrainWriteBuf => match other { + PollResponse::DrainWriteBuf => true, + _ => false, + }, + PollResponse::DoNothing => match other { + PollResponse::DoNothing => true, + _ => false, + }, + _ => false, } } } @@ -197,7 +219,7 @@ where }; Dispatcher { - inner: Some(InnerDispatcher { + inner: DispatcherState::Normal(InnerDispatcher { io, codec, read_buf, @@ -230,7 +252,10 @@ where U::Error: fmt::Display, { fn can_read(&self) -> bool { - if self.flags.contains(Flags::READ_DISCONNECT) { + if self + .flags + .intersects(Flags::READ_DISCONNECT | Flags::UPGRADE) + { false } else if let Some(ref info) = self.payload { info.need_read() == PayloadStatus::Read @@ -315,7 +340,7 @@ where .extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n"); } - fn poll_response(&mut self) -> Result { + fn poll_response(&mut self) -> Result { loop { let state = match self.state { State::None => match self.messages.pop_front() { @@ -325,6 +350,9 @@ where Some(DispatcherMessage::Error(res)) => { Some(self.send_response(res, ResponseBody::Other(Body::Empty))?) } + Some(DispatcherMessage::Upgrade(req)) => { + return Ok(PollResponse::Upgrade(req)); + } None => None, }, State::ExpectCall(ref mut fut) => match fut.poll() { @@ -374,10 +402,10 @@ where )?; self.state = State::None; } - Async::NotReady => return Ok(false), + Async::NotReady => return Ok(PollResponse::DoNothing), } } else { - return Ok(true); + return Ok(PollResponse::DrainWriteBuf); } break; } @@ -405,7 +433,7 @@ where break; } - Ok(false) + Ok(PollResponse::DoNothing) } fn handle_request(&mut self, req: Request) -> Result, DispatchError> { @@ -461,15 +489,18 @@ where match msg { Message::Item(mut req) => { - match self.codec.message_type() { - MessageType::Payload | MessageType::Stream => { - let (ps, pl) = Payload::create(false); - let (req1, _) = - req.replace_payload(crate::Payload::H1(pl)); - req = req1; - self.payload = Some(ps); - } - _ => (), + let pl = self.codec.message_type(); + + if pl == MessageType::Stream && self.upgrade.is_some() { + self.messages.push_back(DispatcherMessage::Upgrade(req)); + break; + } + if pl == MessageType::Payload || pl == MessageType::Stream { + let (ps, pl) = Payload::create(false); + let (req1, _) = + req.replace_payload(crate::Payload::H1(pl)); + req = req1; + self.payload = Some(ps); } // handle request early @@ -633,80 +664,112 @@ where #[inline] fn poll(&mut self) -> Poll { - let inner = self.inner.as_mut().unwrap(); - inner.poll_keepalive()?; + match self.inner { + DispatcherState::Normal(ref mut inner) => { + inner.poll_keepalive()?; - if inner.flags.contains(Flags::SHUTDOWN) { - if inner.flags.contains(Flags::WRITE_DISCONNECT) { - Ok(Async::Ready(())) - } else { - // flush buffer - inner.poll_flush()?; - if !inner.write_buf.is_empty() { - Ok(Async::NotReady) + if inner.flags.contains(Flags::SHUTDOWN) { + if inner.flags.contains(Flags::WRITE_DISCONNECT) { + Ok(Async::Ready(())) + } else { + // flush buffer + inner.poll_flush()?; + if !inner.write_buf.is_empty() { + Ok(Async::NotReady) + } else { + match inner.io.shutdown()? { + Async::Ready(_) => Ok(Async::Ready(())), + Async::NotReady => Ok(Async::NotReady), + } + } + } } else { - match inner.io.shutdown()? { - Async::Ready(_) => Ok(Async::Ready(())), - Async::NotReady => Ok(Async::NotReady), + // read socket into a buf + if !inner.flags.contains(Flags::READ_DISCONNECT) { + if let Some(true) = + read_available(&mut inner.io, &mut inner.read_buf)? + { + inner.flags.insert(Flags::READ_DISCONNECT) + } + } + + inner.poll_request()?; + loop { + if inner.write_buf.remaining_mut() < LW_BUFFER_SIZE { + inner.write_buf.reserve(HW_BUFFER_SIZE); + } + let result = inner.poll_response()?; + let drain = result == PollResponse::DrainWriteBuf; + + // switch to upgrade handler + if let PollResponse::Upgrade(req) = result { + if let DispatcherState::Normal(inner) = + std::mem::replace(&mut self.inner, DispatcherState::None) + { + let mut parts = FramedParts::with_read_buf( + inner.io, + inner.codec, + inner.read_buf, + ); + parts.write_buf = inner.write_buf; + let framed = Framed::from_parts(parts); + self.inner = DispatcherState::Upgrade( + inner.upgrade.unwrap().call((req, framed)), + ); + return self.poll(); + } else { + panic!() + } + } + + // we didnt get WouldBlock from write operation, + // so data get written to kernel completely (OSX) + // and we have to write again otherwise response can get stuck + if inner.poll_flush()? || !drain { + break; + } + } + + // client is gone + if inner.flags.contains(Flags::WRITE_DISCONNECT) { + return Ok(Async::Ready(())); + } + + let is_empty = inner.state.is_empty(); + + // read half is closed and we do not processing any responses + if inner.flags.contains(Flags::READ_DISCONNECT) && is_empty { + inner.flags.insert(Flags::SHUTDOWN); + } + + // keep-alive and stream errors + if is_empty && inner.write_buf.is_empty() { + if let Some(err) = inner.error.take() { + Err(err) + } + // disconnect if keep-alive is not enabled + else if inner.flags.contains(Flags::STARTED) + && !inner.flags.intersects(Flags::KEEPALIVE) + { + inner.flags.insert(Flags::SHUTDOWN); + self.poll() + } + // disconnect if shutdown + else if inner.flags.contains(Flags::SHUTDOWN) { + self.poll() + } else { + Ok(Async::NotReady) + } + } else { + Ok(Async::NotReady) } } } - } else { - // read socket into a buf - if !inner.flags.contains(Flags::READ_DISCONNECT) { - if let Some(true) = read_available(&mut inner.io, &mut inner.read_buf)? { - inner.flags.insert(Flags::READ_DISCONNECT) - } - } - - inner.poll_request()?; - loop { - if inner.write_buf.remaining_mut() < LW_BUFFER_SIZE { - inner.write_buf.reserve(HW_BUFFER_SIZE); - } - let need_write = inner.poll_response()?; - - // we didnt get WouldBlock from write operation, - // so data get written to kernel completely (OSX) - // and we have to write again otherwise response can get stuck - if inner.poll_flush()? || !need_write { - break; - } - } - - // client is gone - if inner.flags.contains(Flags::WRITE_DISCONNECT) { - return Ok(Async::Ready(())); - } - - let is_empty = inner.state.is_empty(); - - // read half is closed and we do not processing any responses - if inner.flags.contains(Flags::READ_DISCONNECT) && is_empty { - inner.flags.insert(Flags::SHUTDOWN); - } - - // keep-alive and stream errors - if is_empty && inner.write_buf.is_empty() { - if let Some(err) = inner.error.take() { - Err(err) - } - // disconnect if keep-alive is not enabled - else if inner.flags.contains(Flags::STARTED) - && !inner.flags.intersects(Flags::KEEPALIVE) - { - inner.flags.insert(Flags::SHUTDOWN); - self.poll() - } - // disconnect if shutdown - else if inner.flags.contains(Flags::SHUTDOWN) { - self.poll() - } else { - Ok(Async::NotReady) - } - } else { - Ok(Async::NotReady) - } + DispatcherState::Upgrade(ref mut fut) => fut.poll().map_err(|e| { + error!("Upgrade handler error: {}", e); + DispatchError::Upgrade + }), + DispatcherState::None => panic!(), } } } diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index cfe0999fd..6d382478f 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -31,9 +31,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ fn test_h1_v2() { env_logger::init(); let mut srv = TestServer::new(move || { - HttpService::build() - .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) - .map(|_| ()) + HttpService::build().finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) }); let response = srv.block_on(srv.get("/").send()).unwrap(); assert!(response.status().is_success()); diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs new file mode 100644 index 000000000..b6be748bd --- /dev/null +++ b/actix-http/tests/test_ws.rs @@ -0,0 +1,76 @@ +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_http::{body, h1, ws, Error, HttpService, Request, Response}; +use actix_http_test::TestServer; +use actix_utils::framed::FramedTransport; +use bytes::{Bytes, BytesMut}; +use futures::future::{self, ok}; +use futures::{Future, Sink, Stream}; + +fn ws_service( + (req, framed): (Request, Framed), +) -> impl Future { + let res = ws::handshake(&req).unwrap().message_body(()); + + framed + .send((res, body::BodySize::None).into()) + .map_err(|_| panic!()) + .and_then(|framed| { + FramedTransport::new(framed.into_framed(ws::Codec::new()), service) + .map_err(|_| panic!()) + }) +} + +fn service(msg: ws::Frame) -> impl Future { + let msg = match msg { + ws::Frame::Ping(msg) => ws::Message::Pong(msg), + ws::Frame::Text(text) => { + ws::Message::Text(String::from_utf8_lossy(&text.unwrap()).to_string()) + } + ws::Frame::Binary(bin) => ws::Message::Binary(bin.unwrap().freeze()), + ws::Frame::Close(reason) => ws::Message::Close(reason), + _ => panic!(), + }; + ok(msg) +} + +#[test] +fn test_simple() { + let mut srv = TestServer::new(|| { + HttpService::build() + .upgrade(ws_service) + .finish(|_| future::ok::<_, ()>(Response::NotFound())) + }); + + // client service + let framed = srv.ws().unwrap(); + let framed = srv + .block_on(framed.send(ws::Message::Text("text".to_string()))) + .unwrap(); + let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text"))))); + + let framed = srv + .block_on(framed.send(ws::Message::Binary("text".into()))) + .unwrap(); + let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!( + item, + Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into()))) + ); + + let framed = srv + .block_on(framed.send(ws::Message::Ping("text".into()))) + .unwrap(); + let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into()))); + + let framed = srv + .block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))) + .unwrap(); + + let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!( + item, + Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into()))) + ); +} From 9c9940d88d3d1d38a9f413749f77edc07b118eb5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Apr 2019 17:53:19 -0700 Subject: [PATCH 041/122] update readme --- actix-http/Cargo.toml | 6 +----- actix-http/README.md | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 967a224e2..a9ab320ea 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -6,7 +6,7 @@ description = "Actix http primitives" readme = "README.md" keywords = ["actix", "http", "framework", "async", "futures"] homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-http.git" +repository = "https://github.com/actix/actix-web.git" documentation = "https://docs.rs/actix-http/" categories = ["network-programming", "asynchronous", "web-programming::http-server", @@ -18,10 +18,6 @@ workspace = ".." [package.metadata.docs.rs] features = ["ssl", "fail", "brotli", "flate2-zlib", "secure-cookies"] -[badges] -travis-ci = { repository = "actix/actix-web", branch = "master" } -codecov = { repository = "actix/actix-web", branch = "master", service = "github" } - [lib] name = "actix_http" path = "src/lib.rs" diff --git a/actix-http/README.md b/actix-http/README.md index 467e67a9d..d75e822ba 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -1,4 +1,4 @@ -# Actix http [![Build Status](https://travis-ci.org/actix/actix-http.svg?branch=master)](https://travis-ci.org/actix/actix-http) [![codecov](https://codecov.io/gh/actix/actix-http/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-http) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Actix http [![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-http)](https://crates.io/crates/actix-http) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Actix http @@ -8,7 +8,7 @@ Actix http * [API Documentation](https://docs.rs/actix-http/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-http](https://crates.io/crates/actix-http) -* Minimum supported Rust version: 1.26 or later +* Minimum supported Rust version: 1.31 or later ## Example From c22a3a71f2b366bf7af6fd0e00e5f150835645c0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Apr 2019 19:07:11 -0700 Subject: [PATCH 042/122] fix test --- actix-http/src/h1/dispatcher.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 9014047d7..8c0af007e 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -824,7 +824,7 @@ mod tests { use super::*; use crate::error::Error; - use crate::h1::ExpectHandler; + use crate::h1::{ExpectHandler, UpgradeHandler}; struct Buffer { buf: Bytes, @@ -884,25 +884,21 @@ mod tests { let _ = sys.block_on(lazy(|| { let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); - let mut h1 = Dispatcher::new( + let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, ServiceConfig::default(), CloneableService::new( (|_| ok::<_, Error>(Response::Ok().finish())).into_service(), ), CloneableService::new(ExpectHandler), + None, ); assert!(h1.poll().is_err()); - assert!(h1 - .inner - .as_ref() - .unwrap() - .flags - .contains(Flags::READ_DISCONNECT)); - assert_eq!( - &h1.inner.as_ref().unwrap().io.write_buf[..26], - b"HTTP/1.1 400 Bad Request\r\n" - ); + + if let DispatcherState::Normal(ref inner) = h1.inner { + assert!(inner.flags.contains(Flags::READ_DISCONNECT)); + assert_eq!(&inner.io.write_buf[..26], b"HTTP/1.1 400 Bad Request\r\n"); + } ok::<_, ()>(()) })); } From 046b7a142595af3714c2b8221a6eec69361e46fc Mon Sep 17 00:00:00 2001 From: Douman Date: Sun, 31 Mar 2019 10:23:15 +0300 Subject: [PATCH 043/122] Expand codegen to allow specify guards and async --- actix-web-codegen/Cargo.toml | 3 +- actix-web-codegen/src/lib.rs | 170 +++++++++++--------------- actix-web-codegen/src/route.rs | 159 ++++++++++++++++++++++++ actix-web-codegen/tests/test_macro.rs | 33 ++++- 4 files changed, 266 insertions(+), 99 deletions(-) create mode 100644 actix-web-codegen/src/route.rs diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index da2640760..4d8c0910e 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -18,4 +18,5 @@ syn = { version = "0.15", features = ["full", "parsing"] } [dev-dependencies] actix-web = { version = "1.0.0-alpha.2" } actix-http = { version = "0.1.0-alpha.2", features=["ssl"] } -actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] } \ No newline at end of file +actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] } +futures = { version = "0.1" } diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 16123930a..70cde90e4 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -1,118 +1,94 @@ #![recursion_limit = "512"] +//! Actix-web codegen module +//! +//! Generators for routes and scopes +//! +//! ## Route +//! +//! Macros: +//! +//! - [get](attr.get.html) +//! - [post](attr.post.html) +//! - [put](attr.put.html) +//! - [delete](attr.delete.html) +//! +//! ### Attributes: +//! +//! - `"path"` - Raw literal string with path for which to register handle. Mandatory. +//! - `guard="function_name"` - Registers function as guard using `actix_web::guard::fn_guard` +//! +//! ## Notes +//! +//! Function name can be specified as any expression that is going to be accessible to the generate +//! code (e.g `my_guard` or `my_module::my_guard`) +//! +//! ## Example: +//! +//! ```rust +//! use actix_web::HttpResponse; +//! use actix_web_codegen::get; +//! use futures::{future, Future}; +//! +//! #[get("/test")] +//! fn async_test() -> impl Future { +//! future::ok(HttpResponse::Ok().finish()) +//! } +//! ``` extern crate proc_macro; +mod route; + use proc_macro::TokenStream; -use quote::quote; use syn::parse_macro_input; -/// #[get("path")] attribute +/// Creates route handler with `GET` method guard. +/// +/// Syntax: `#[get("path"[, attributes])]` +/// +/// ## Attributes: +/// +/// - `"path"` - Raw literal string with path for which to register handler. Mandatory. +/// - `guard="function_name"` - Registers function as guard using `actix_web::guard::fn_guard` #[proc_macro_attribute] pub fn get(args: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args as syn::AttributeArgs); - if args.is_empty() { - panic!("invalid server definition, expected: #[get(\"some path\")]"); - } - - // path - let path = match args[0] { - syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => { - let fname = quote!(#fname).to_string(); - fname.as_str()[1..fname.len() - 1].to_owned() - } - _ => panic!("resource path"), - }; - - let ast: syn::ItemFn = syn::parse(input).unwrap(); - let name = ast.ident.clone(); - - (quote! { - #[allow(non_camel_case_types)] - struct #name; - - impl actix_web::dev::HttpServiceFactory

for #name { - fn register(self, config: &mut actix_web::dev::ServiceConfig

) { - #ast - actix_web::dev::HttpServiceFactory::register( - actix_web::Resource::new(#path) - .guard(actix_web::guard::Get()) - .to(#name), config); - } - } - }) - .into() + let gen = route::Args::new(&args, input, route::GuardType::Get); + gen.generate() } -/// #[post("path")] attribute +/// Creates route handler with `POST` method guard. +/// +/// Syntax: `#[post("path"[, attributes])]` +/// +/// Attributes are the same as in [get](attr.get.html) #[proc_macro_attribute] pub fn post(args: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args as syn::AttributeArgs); - if args.is_empty() { - panic!("invalid server definition, expected: #[post(\"some path\")]"); - } - - // path - let path = match args[0] { - syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => { - let fname = quote!(#fname).to_string(); - fname.as_str()[1..fname.len() - 1].to_owned() - } - _ => panic!("resource path"), - }; - - let ast: syn::ItemFn = syn::parse(input).unwrap(); - let name = ast.ident.clone(); - - (quote! { - #[allow(non_camel_case_types)] - struct #name; - - impl actix_web::dev::HttpServiceFactory

for #name { - fn register(self, config: &mut actix_web::dev::ServiceConfig

) { - #ast - actix_web::dev::HttpServiceFactory::register( - actix_web::Resource::new(#path) - .guard(actix_web::guard::Post()) - .to(#name), config); - } - } - }) - .into() + let gen = route::Args::new(&args, input, route::GuardType::Post); + gen.generate() } -/// #[put("path")] attribute +/// Creates route handler with `PUT` method guard. +/// +/// Syntax: `#[put("path"[, attributes])]` +/// +/// Attributes are the same as in [get](attr.get.html) #[proc_macro_attribute] pub fn put(args: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args as syn::AttributeArgs); - if args.is_empty() { - panic!("invalid server definition, expected: #[put(\"some path\")]"); - } - - // path - let path = match args[0] { - syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => { - let fname = quote!(#fname).to_string(); - fname.as_str()[1..fname.len() - 1].to_owned() - } - _ => panic!("resource path"), - }; - - let ast: syn::ItemFn = syn::parse(input).unwrap(); - let name = ast.ident.clone(); - - (quote! { - #[allow(non_camel_case_types)] - struct #name; - - impl actix_web::dev::HttpServiceFactory

for #name { - fn register(self, config: &mut actix_web::dev::ServiceConfig

) { - #ast - actix_web::dev::HttpServiceFactory::register( - actix_web::Resource::new(#path) - .guard(actix_web::guard::Put()) - .to(#name), config); - } - } - }) - .into() + let gen = route::Args::new(&args, input, route::GuardType::Put); + gen.generate() +} + +/// Creates route handler with `DELETE` method guard. +/// +/// Syntax: `#[delete("path"[, attributes])]` +/// +/// Attributes are the same as in [get](attr.get.html) +#[proc_macro_attribute] +pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as syn::AttributeArgs); + let gen = route::Args::new(&args, input, route::GuardType::Delete); + gen.generate() } diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs new file mode 100644 index 000000000..588debf80 --- /dev/null +++ b/actix-web-codegen/src/route.rs @@ -0,0 +1,159 @@ +extern crate proc_macro; + +use std::fmt; + +use proc_macro::TokenStream; +use quote::{quote}; + +enum ResourceType { + Async, + Sync, +} + +impl fmt::Display for ResourceType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + &ResourceType::Async => write!(f, "to_async"), + &ResourceType::Sync => write!(f, "to"), + } + } +} + +#[derive(PartialEq)] +pub enum GuardType { + Get, + Post, + Put, + Delete, +} + +impl fmt::Display for GuardType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + &GuardType::Get => write!(f, "Get"), + &GuardType::Post => write!(f, "Post"), + &GuardType::Put => write!(f, "Put"), + &GuardType::Delete => write!(f, "Delete"), + } + } +} + +pub struct Args { + name: syn::Ident, + path: String, + ast: syn::ItemFn, + resource_type: ResourceType, + pub guard: GuardType, + pub extra_guards: Vec, +} + +impl fmt::Display for Args { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let ast = &self.ast; + let guards = format!(".guard(actix_web::guard::{}())", self.guard); + let guards = self.extra_guards.iter().fold(guards, |acc, val| format!("{}.guard(actix_web::guard::fn_guard({}))", acc, val)); + + write!(f, " +#[allow(non_camel_case_types)] +pub struct {name}; + +impl actix_web::dev::HttpServiceFactory

for {name} {{ + fn register(self, config: &mut actix_web::dev::ServiceConfig

) {{ + {ast} + + let resource = actix_web::Resource::new(\"{path}\"){guards}.{to}({name}); + + actix_web::dev::HttpServiceFactory::register(resource, config) + }} +}}", name=self.name, ast=quote!(#ast), path=self.path, guards=guards, to=self.resource_type) + } +} + +fn guess_resource_type(typ: &syn::Type) -> ResourceType { + let mut guess = ResourceType::Sync; + + match typ { + syn::Type::ImplTrait(typ) => for bound in typ.bounds.iter() { + match bound { + syn::TypeParamBound::Trait(bound) => { + for bound in bound.path.segments.iter() { + if bound.ident == "Future" { + guess = ResourceType::Async; + break; + } else if bound.ident == "Responder" { + guess = ResourceType::Sync; + break; + } + } + }, + _ => (), + } + }, + _ => (), + } + + guess + +} + +impl Args { + pub fn new(args: &Vec, input: TokenStream, guard: GuardType) -> Self { + if args.is_empty() { + panic!("invalid server definition, expected: #[{}(\"some path\")]", guard); + } + + let ast: syn::ItemFn = syn::parse(input).expect("Parse input as function"); + let name = ast.ident.clone(); + + let mut extra_guards = Vec::new(); + let mut path = None; + for arg in args { + match arg { + syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => { + if path.is_some() { + panic!("Multiple paths specified! Should be only one!") + } + let fname = quote!(#fname).to_string(); + path = Some(fname.as_str()[1..fname.len() - 1].to_owned()) + }, + syn::NestedMeta::Meta(syn::Meta::NameValue(ident)) => match ident.ident.to_string().to_lowercase().as_str() { + "guard" => match ident.lit { + syn::Lit::Str(ref text) => extra_guards.push(text.value()), + _ => panic!("Attribute guard expects literal string!"), + }, + attr => panic!("Unknown attribute key is specified: {}. Allowed: guard", attr) + }, + attr => panic!("Unknown attribute{:?}", attr) + } + } + + let resource_type = if ast.asyncness.is_some() { + ResourceType::Async + } else { + match ast.decl.output { + syn::ReturnType::Default => panic!("Function {} has no return type. Cannot be used as handler"), + syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()), + } + }; + + let path = path.unwrap(); + + Self { + name, + path, + ast, + resource_type, + guard, + extra_guards, + } + } + + pub fn generate(&self) -> TokenStream { + let text = self.to_string(); + + match text.parse() { + Ok(res) => res, + Err(error) => panic!("Error: {:?}\nGenerated code: {}", error, text) + } + } +} diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 8bf2c88be..b028f0123 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -1,15 +1,46 @@ use actix_http::HttpService; use actix_http_test::TestServer; -use actix_web::{get, http, App, HttpResponse, Responder}; +use actix_web_codegen::get; +use actix_web::{http, App, HttpResponse, Responder}; +use futures::{Future, future}; +//fn guard_head(head: &actix_web::dev::RequestHead) -> bool { +// true +//} + +//#[get("/test", guard="guard_head")] #[get("/test")] fn test() -> impl Responder { HttpResponse::Ok() } +#[get("/test")] +fn auto_async() -> impl Future { + future::ok(HttpResponse::Ok().finish()) +} + +#[get("/test")] +fn auto_sync() -> impl Future { + future::ok(HttpResponse::Ok().finish()) +} + + #[test] fn test_body() { let mut srv = TestServer::new(|| HttpService::new(App::new().service(test))); + let request = srv.request(http::Method::GET, srv.url("/test")); + let response = srv.block_on(request.send()).unwrap(); + assert!(response.status().is_success()); + + let mut srv = TestServer::new(|| HttpService::new(App::new().service(auto_sync))); + let request = srv.request(http::Method::GET, srv.url("/test")); + let response = srv.block_on(request.send()).unwrap(); + assert!(response.status().is_success()); +} + +#[test] +fn test_auto_async() { + let mut srv = TestServer::new(|| HttpService::new(App::new().service(auto_async))); let request = srv.request(http::Method::GET, srv.url("/test")); let response = srv.block_on(request.send()).unwrap(); From 9bb40c249fc18fbeb2b72e012aa3b411893a7fa5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 10 Apr 2019 12:24:17 -0700 Subject: [PATCH 044/122] add h1::SendResponse future; renamed to MessageBody::size --- Cargo.toml | 2 +- actix-http/CHANGES.md | 6 ++ actix-http/src/body.rs | 142 +++++++++++++++++++------- actix-http/src/client/h1proto.rs | 4 +- actix-http/src/client/h2proto.rs | 4 +- actix-http/src/encoding/encoder.rs | 8 +- actix-http/src/h1/dispatcher.rs | 4 +- actix-http/src/h1/mod.rs | 2 + actix-http/src/h1/utils.rs | 92 +++++++++++++++++ actix-http/src/h2/dispatcher.rs | 32 +++--- actix-http/src/response.rs | 14 ++- actix-web-codegen/src/route.rs | 87 ++++++++++------ actix-web-codegen/tests/test_macro.rs | 9 +- src/middleware/logger.rs | 4 +- src/service.rs | 2 +- 15 files changed, 308 insertions(+), 104 deletions(-) create mode 100644 actix-http/src/h1/utils.rs diff --git a/Cargo.toml b/Cargo.toml index 22b7b13a4..0c4d31374 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,7 +67,7 @@ ssl = ["openssl", "actix-server/ssl", "awc/ssl"] rust-tls = ["rustls", "actix-server/rust-tls"] [dependencies] -actix-codec = "0.1.1" +actix-codec = "0.1.2" actix-service = "0.3.6" actix-utils = "0.3.4" actix-router = "0.1.2" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index cca4560c5..f5988afa4 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -6,6 +6,12 @@ * Allow to use custom service for upgrade requests +* Added `h1::SendResponse` future. + +### Changed + +* MessageBody::length() renamed to MessageBody::size() for consistency + ## [0.1.0-alpha.4] - 2019-04-08 diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index 88b6c492f..0652dd274 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -30,13 +30,13 @@ impl BodySize { /// Type that provides this trait can be streamed to a peer. pub trait MessageBody { - fn length(&self) -> BodySize; + fn size(&self) -> BodySize; fn poll_next(&mut self) -> Poll, Error>; } impl MessageBody for () { - fn length(&self) -> BodySize { + fn size(&self) -> BodySize { BodySize::Empty } @@ -46,8 +46,8 @@ impl MessageBody for () { } impl MessageBody for Box { - fn length(&self) -> BodySize { - self.as_ref().length() + fn size(&self) -> BodySize { + self.as_ref().size() } fn poll_next(&mut self) -> Poll, Error> { @@ -86,10 +86,10 @@ impl ResponseBody { } impl MessageBody for ResponseBody { - fn length(&self) -> BodySize { + fn size(&self) -> BodySize { match self { - ResponseBody::Body(ref body) => body.length(), - ResponseBody::Other(ref body) => body.length(), + ResponseBody::Body(ref body) => body.size(), + ResponseBody::Other(ref body) => body.size(), } } @@ -135,12 +135,12 @@ impl Body { } impl MessageBody for Body { - fn length(&self) -> BodySize { + fn size(&self) -> BodySize { match self { Body::None => BodySize::None, Body::Empty => BodySize::Empty, Body::Bytes(ref bin) => BodySize::Sized(bin.len()), - Body::Message(ref body) => body.length(), + Body::Message(ref body) => body.size(), } } @@ -185,7 +185,7 @@ impl fmt::Debug for Body { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Body::None => write!(f, "Body::None"), - Body::Empty => write!(f, "Body::Zero"), + Body::Empty => write!(f, "Body::Empty"), Body::Bytes(ref b) => write!(f, "Body::Bytes({:?})", b), Body::Message(_) => write!(f, "Body::Message(_)"), } @@ -235,7 +235,7 @@ impl From for Body { } impl MessageBody for Bytes { - fn length(&self) -> BodySize { + fn size(&self) -> BodySize { BodySize::Sized(self.len()) } @@ -249,7 +249,7 @@ impl MessageBody for Bytes { } impl MessageBody for BytesMut { - fn length(&self) -> BodySize { + fn size(&self) -> BodySize { BodySize::Sized(self.len()) } @@ -265,7 +265,7 @@ impl MessageBody for BytesMut { } impl MessageBody for &'static str { - fn length(&self) -> BodySize { + fn size(&self) -> BodySize { BodySize::Sized(self.len()) } @@ -281,7 +281,7 @@ impl MessageBody for &'static str { } impl MessageBody for &'static [u8] { - fn length(&self) -> BodySize { + fn size(&self) -> BodySize { BodySize::Sized(self.len()) } @@ -297,7 +297,7 @@ impl MessageBody for &'static [u8] { } impl MessageBody for Vec { - fn length(&self) -> BodySize { + fn size(&self) -> BodySize { BodySize::Sized(self.len()) } @@ -314,7 +314,7 @@ impl MessageBody for Vec { } impl MessageBody for String { - fn length(&self) -> BodySize { + fn size(&self) -> BodySize { BodySize::Sized(self.len()) } @@ -354,7 +354,7 @@ where S: Stream, E: Into, { - fn length(&self) -> BodySize { + fn size(&self) -> BodySize { BodySize::Stream } @@ -383,7 +383,7 @@ impl MessageBody for SizedStream where S: Stream, { - fn length(&self) -> BodySize { + fn size(&self) -> BodySize { BodySize::Sized(self.size) } @@ -416,47 +416,117 @@ mod tests { #[test] fn test_static_str() { - assert_eq!(Body::from("").length(), BodySize::Sized(0)); - assert_eq!(Body::from("test").length(), BodySize::Sized(4)); + assert_eq!(Body::from("").size(), BodySize::Sized(0)); + assert_eq!(Body::from("test").size(), BodySize::Sized(4)); assert_eq!(Body::from("test").get_ref(), b"test"); + + assert_eq!("test".size(), BodySize::Sized(4)); + assert_eq!( + "test".poll_next().unwrap(), + Async::Ready(Some(Bytes::from("test"))) + ); } #[test] fn test_static_bytes() { - assert_eq!(Body::from(b"test".as_ref()).length(), BodySize::Sized(4)); + assert_eq!(Body::from(b"test".as_ref()).size(), BodySize::Sized(4)); assert_eq!(Body::from(b"test".as_ref()).get_ref(), b"test"); assert_eq!( - Body::from_slice(b"test".as_ref()).length(), + Body::from_slice(b"test".as_ref()).size(), BodySize::Sized(4) ); assert_eq!(Body::from_slice(b"test".as_ref()).get_ref(), b"test"); + + assert_eq!((&b"test"[..]).size(), BodySize::Sized(4)); + assert_eq!( + (&b"test"[..]).poll_next().unwrap(), + Async::Ready(Some(Bytes::from("test"))) + ); } #[test] fn test_vec() { - assert_eq!(Body::from(Vec::from("test")).length(), BodySize::Sized(4)); + assert_eq!(Body::from(Vec::from("test")).size(), BodySize::Sized(4)); assert_eq!(Body::from(Vec::from("test")).get_ref(), b"test"); + + assert_eq!(Vec::from("test").size(), BodySize::Sized(4)); + assert_eq!( + Vec::from("test").poll_next().unwrap(), + Async::Ready(Some(Bytes::from("test"))) + ); } #[test] fn test_bytes() { - assert_eq!(Body::from(Bytes::from("test")).length(), BodySize::Sized(4)); - assert_eq!(Body::from(Bytes::from("test")).get_ref(), b"test"); - } - - #[test] - fn test_string() { - let b = "test".to_owned(); - assert_eq!(Body::from(b.clone()).length(), BodySize::Sized(4)); + let mut b = Bytes::from("test"); + assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); assert_eq!(Body::from(b.clone()).get_ref(), b"test"); - assert_eq!(Body::from(&b).length(), BodySize::Sized(4)); - assert_eq!(Body::from(&b).get_ref(), b"test"); + + assert_eq!(b.size(), BodySize::Sized(4)); + assert_eq!( + b.poll_next().unwrap(), + Async::Ready(Some(Bytes::from("test"))) + ); } #[test] fn test_bytes_mut() { - let b = BytesMut::from("test"); - assert_eq!(Body::from(b.clone()).length(), BodySize::Sized(4)); - assert_eq!(Body::from(b).get_ref(), b"test"); + let mut b = BytesMut::from("test"); + assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); + assert_eq!(Body::from(b.clone()).get_ref(), b"test"); + + assert_eq!(b.size(), BodySize::Sized(4)); + assert_eq!( + b.poll_next().unwrap(), + Async::Ready(Some(Bytes::from("test"))) + ); + } + + #[test] + fn test_string() { + let mut b = "test".to_owned(); + assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); + assert_eq!(Body::from(b.clone()).get_ref(), b"test"); + assert_eq!(Body::from(&b).size(), BodySize::Sized(4)); + assert_eq!(Body::from(&b).get_ref(), b"test"); + + assert_eq!(b.size(), BodySize::Sized(4)); + assert_eq!( + b.poll_next().unwrap(), + Async::Ready(Some(Bytes::from("test"))) + ); + } + + #[test] + fn test_unit() { + assert_eq!(().size(), BodySize::Empty); + assert_eq!(().poll_next().unwrap(), Async::Ready(None)); + } + + #[test] + fn test_box() { + let mut val = Box::new(()); + assert_eq!(val.size(), BodySize::Empty); + assert_eq!(val.poll_next().unwrap(), Async::Ready(None)); + } + + #[test] + fn test_body_eq() { + assert!(Body::None == Body::None); + assert!(Body::None != Body::Empty); + assert!(Body::Empty == Body::Empty); + assert!(Body::Empty != Body::None); + assert!( + Body::Bytes(Bytes::from_static(b"1")) + == Body::Bytes(Bytes::from_static(b"1")) + ); + assert!(Body::Bytes(Bytes::from_static(b"1")) != Body::None); + } + + #[test] + fn test_body_debug() { + assert!(format!("{:?}", Body::None).contains("Body::None")); + assert!(format!("{:?}", Body::Empty).contains("Body::Empty")); + assert!(format!("{:?}", Body::Bytes(Bytes::from_static(b"1"))).contains("1")); } } diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 3a8b119d3..becc07528 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -55,14 +55,14 @@ where io: Some(io), }; - let len = body.length(); + let len = body.size(); // create Framed and send reqest Framed::new(io, h1::ClientCodec::default()) .send((head, len).into()) .from_err() // send request body - .and_then(move |framed| match body.length() { + .and_then(move |framed| match body.size() { BodySize::None | BodySize::Empty | BodySize::Sized(0) => { Either::A(ok(framed)) } diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index ec5beee6b..91240268e 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -27,9 +27,9 @@ where T: AsyncRead + AsyncWrite + 'static, B: MessageBody, { - trace!("Sending client request: {:?} {:?}", head, body.length()); + trace!("Sending client request: {:?} {:?}", head, body.size()); let head_req = head.method == Method::HEAD; - let length = body.length(); + let length = body.size(); let eof = match length { BodySize::None | BodySize::Empty | BodySize::Sized(0) => true, _ => false, diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 6537379f5..aabce292a 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -79,12 +79,12 @@ enum EncoderBody { } impl MessageBody for Encoder { - fn length(&self) -> BodySize { + fn size(&self) -> BodySize { if self.encoder.is_none() { match self.body { - EncoderBody::Bytes(ref b) => b.length(), - EncoderBody::Stream(ref b) => b.length(), - EncoderBody::BoxedStream(ref b) => b.length(), + EncoderBody::Bytes(ref b) => b.size(), + EncoderBody::Stream(ref b) => b.size(), + EncoderBody::BoxedStream(ref b) => b.size(), } } else { BodySize::Stream diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 8c0af007e..cca181c99 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -320,7 +320,7 @@ where body: ResponseBody, ) -> Result, DispatchError> { self.codec - .encode(Message::Item((message, body.length())), &mut self.write_buf) + .encode(Message::Item((message, body.size())), &mut self.write_buf) .map_err(|err| { if let Some(mut payload) = self.payload.take() { payload.set_error(PayloadError::Incomplete(None)); @@ -329,7 +329,7 @@ where })?; self.flags.set(Flags::KEEPALIVE, self.codec.keepalive()); - match body.length() { + match body.size() { BodySize::None | BodySize::Empty => Ok(State::None), _ => Ok(State::SendPayload(body)), } diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs index 58712278f..0c85f076a 100644 --- a/actix-http/src/h1/mod.rs +++ b/actix-http/src/h1/mod.rs @@ -10,6 +10,7 @@ mod expect; mod payload; mod service; mod upgrade; +mod utils; pub use self::client::{ClientCodec, ClientPayloadCodec}; pub use self::codec::Codec; @@ -18,6 +19,7 @@ pub use self::expect::ExpectHandler; pub use self::payload::Payload; pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; pub use self::upgrade::UpgradeHandler; +pub use self::utils::SendResponse; #[derive(Debug)] /// Codec message diff --git a/actix-http/src/h1/utils.rs b/actix-http/src/h1/utils.rs new file mode 100644 index 000000000..fdc4cf0bc --- /dev/null +++ b/actix-http/src/h1/utils.rs @@ -0,0 +1,92 @@ +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use futures::{Async, Future, Poll, Sink}; + +use crate::body::{BodySize, MessageBody, ResponseBody}; +use crate::error::Error; +use crate::h1::{Codec, Message}; +use crate::response::Response; + +/// Send http/1 response +pub struct SendResponse { + res: Option, BodySize)>>, + body: Option>, + framed: Option>, +} + +impl SendResponse +where + B: MessageBody, +{ + pub fn new(framed: Framed, response: Response) -> Self { + let (res, body) = response.into_parts(); + + SendResponse { + res: Some((res, body.size()).into()), + body: Some(body), + framed: Some(framed), + } + } +} + +impl Future for SendResponse +where + T: AsyncRead + AsyncWrite, + B: MessageBody, +{ + type Item = Framed; + type Error = Error; + + fn poll(&mut self) -> Poll { + loop { + let mut body_ready = self.body.is_some(); + let framed = self.framed.as_mut().unwrap(); + + // send body + if self.res.is_none() && self.body.is_some() { + while body_ready && self.body.is_some() && !framed.is_write_buf_full() { + match self.body.as_mut().unwrap().poll_next()? { + Async::Ready(item) => { + // body is done + if item.is_none() { + let _ = self.body.take(); + } + framed.force_send(Message::Chunk(item))?; + } + Async::NotReady => body_ready = false, + } + } + } + + // flush write buffer + if !framed.is_write_buf_empty() { + match framed.poll_complete()? { + Async::Ready(_) => { + if body_ready { + continue; + } else { + return Ok(Async::NotReady); + } + } + Async::NotReady => return Ok(Async::NotReady), + } + } + + // send response + if let Some(res) = self.res.take() { + framed.force_send(res)?; + continue; + } + + if self.body.is_some() { + if body_ready { + continue; + } else { + return Ok(Async::NotReady); + } + } else { + break; + } + } + Ok(Async::Ready(self.framed.take().unwrap())) + } +} diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index cbb74f609..de0b761f5 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -153,10 +153,10 @@ where fn prepare_response( &self, head: &ResponseHead, - length: &mut BodySize, + size: &mut BodySize, ) -> http::Response<()> { let mut has_date = false; - let mut skip_len = length != &BodySize::Stream; + let mut skip_len = size != &BodySize::Stream; let mut res = http::Response::new(()); *res.status_mut() = head.status; @@ -166,14 +166,14 @@ where match head.status { http::StatusCode::NO_CONTENT | http::StatusCode::CONTINUE - | http::StatusCode::PROCESSING => *length = BodySize::None, + | http::StatusCode::PROCESSING => *size = BodySize::None, http::StatusCode::SWITCHING_PROTOCOLS => { skip_len = true; - *length = BodySize::Stream; + *size = BodySize::Stream; } _ => (), } - let _ = match length { + let _ = match size { BodySize::None | BodySize::Stream => None, BodySize::Empty => res .headers_mut() @@ -229,16 +229,15 @@ where let (res, body) = res.into().replace_body(()); let mut send = send.take().unwrap(); - let mut length = body.length(); - let h2_res = self.prepare_response(res.head(), &mut length); + let mut size = body.size(); + let h2_res = self.prepare_response(res.head(), &mut size); - let stream = send - .send_response(h2_res, length.is_eof()) - .map_err(|e| { + let stream = + send.send_response(h2_res, size.is_eof()).map_err(|e| { trace!("Error sending h2 response: {:?}", e); })?; - if length.is_eof() { + if size.is_eof() { Ok(Async::Ready(())) } else { self.state = ServiceResponseState::SendPayload(stream, body); @@ -251,16 +250,15 @@ where let (res, body) = res.replace_body(()); let mut send = send.take().unwrap(); - let mut length = body.length(); - let h2_res = self.prepare_response(res.head(), &mut length); + let mut size = body.size(); + let h2_res = self.prepare_response(res.head(), &mut size); - let stream = send - .send_response(h2_res, length.is_eof()) - .map_err(|e| { + let stream = + send.send_response(h2_res, size.is_eof()).map_err(|e| { trace!("Error sending h2 response: {:?}", e); })?; - if length.is_eof() { + if size.is_eof() { Ok(Async::Ready(())) } else { self.state = ServiceResponseState::SendPayload( diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 330d33a45..95e4e789b 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -210,6 +210,18 @@ impl Response { } } + /// Split response and body + pub fn into_parts(self) -> (Response<()>, ResponseBody) { + ( + Response { + head: self.head, + body: ResponseBody::Body(()), + error: self.error, + }, + self.body, + ) + } + /// Drop request's body pub fn drop_body(self) -> Response<()> { Response { @@ -264,7 +276,7 @@ impl fmt::Debug for Response { for (key, val) in self.head.headers.iter() { let _ = writeln!(f, " {:?}: {:?}", key, val); } - let _ = writeln!(f, " body: {:?}", self.body.length()); + let _ = writeln!(f, " body: {:?}", self.body.size()); res } } diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index 588debf80..e1a870dbc 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -3,7 +3,7 @@ extern crate proc_macro; use std::fmt; use proc_macro::TokenStream; -use quote::{quote}; +use quote::quote; enum ResourceType { Async, @@ -51,9 +51,13 @@ impl fmt::Display for Args { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let ast = &self.ast; let guards = format!(".guard(actix_web::guard::{}())", self.guard); - let guards = self.extra_guards.iter().fold(guards, |acc, val| format!("{}.guard(actix_web::guard::fn_guard({}))", acc, val)); + let guards = self.extra_guards.iter().fold(guards, |acc, val| { + format!("{}.guard(actix_web::guard::fn_guard({}))", acc, val) + }); - write!(f, " + write!( + f, + " #[allow(non_camel_case_types)] pub struct {name}; @@ -65,7 +69,13 @@ impl actix_web::dev::HttpServiceFactory

for {name} {{ actix_web::dev::HttpServiceFactory::register(resource, config) }} -}}", name=self.name, ast=quote!(#ast), path=self.path, guards=guards, to=self.resource_type) +}}", + name = self.name, + ast = quote!(#ast), + path = self.path, + guards = guards, + to = self.resource_type + ) } } @@ -73,33 +83,41 @@ fn guess_resource_type(typ: &syn::Type) -> ResourceType { let mut guess = ResourceType::Sync; match typ { - syn::Type::ImplTrait(typ) => for bound in typ.bounds.iter() { - match bound { - syn::TypeParamBound::Trait(bound) => { - for bound in bound.path.segments.iter() { - if bound.ident == "Future" { - guess = ResourceType::Async; - break; - } else if bound.ident == "Responder" { - guess = ResourceType::Sync; - break; + syn::Type::ImplTrait(typ) => { + for bound in typ.bounds.iter() { + match bound { + syn::TypeParamBound::Trait(bound) => { + for bound in bound.path.segments.iter() { + if bound.ident == "Future" { + guess = ResourceType::Async; + break; + } else if bound.ident == "Responder" { + guess = ResourceType::Sync; + break; + } } } - }, - _ => (), + _ => (), + } } - }, + } _ => (), } guess - } impl Args { - pub fn new(args: &Vec, input: TokenStream, guard: GuardType) -> Self { + pub fn new( + args: &Vec, + input: TokenStream, + guard: GuardType, + ) -> Self { if args.is_empty() { - panic!("invalid server definition, expected: #[{}(\"some path\")]", guard); + panic!( + "invalid server definition, expected: #[{}(\"some path\")]", + guard + ); } let ast: syn::ItemFn = syn::parse(input).expect("Parse input as function"); @@ -115,15 +133,20 @@ impl Args { } let fname = quote!(#fname).to_string(); path = Some(fname.as_str()[1..fname.len() - 1].to_owned()) - }, - syn::NestedMeta::Meta(syn::Meta::NameValue(ident)) => match ident.ident.to_string().to_lowercase().as_str() { - "guard" => match ident.lit { - syn::Lit::Str(ref text) => extra_guards.push(text.value()), - _ => panic!("Attribute guard expects literal string!"), - }, - attr => panic!("Unknown attribute key is specified: {}. Allowed: guard", attr) - }, - attr => panic!("Unknown attribute{:?}", attr) + } + syn::NestedMeta::Meta(syn::Meta::NameValue(ident)) => { + match ident.ident.to_string().to_lowercase().as_str() { + "guard" => match ident.lit { + syn::Lit::Str(ref text) => extra_guards.push(text.value()), + _ => panic!("Attribute guard expects literal string!"), + }, + attr => panic!( + "Unknown attribute key is specified: {}. Allowed: guard", + attr + ), + } + } + attr => panic!("Unknown attribute{:?}", attr), } } @@ -131,7 +154,9 @@ impl Args { ResourceType::Async } else { match ast.decl.output { - syn::ReturnType::Default => panic!("Function {} has no return type. Cannot be used as handler"), + syn::ReturnType::Default => { + panic!("Function {} has no return type. Cannot be used as handler") + } syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()), } }; @@ -153,7 +178,7 @@ impl Args { match text.parse() { Ok(res) => res, - Err(error) => panic!("Error: {:?}\nGenerated code: {}", error, text) + Err(error) => panic!("Error: {:?}\nGenerated code: {}", error, text), } } } diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index b028f0123..c27eefdef 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -1,8 +1,8 @@ use actix_http::HttpService; use actix_http_test::TestServer; -use actix_web_codegen::get; use actix_web::{http, App, HttpResponse, Responder}; -use futures::{Future, future}; +use actix_web_codegen::get; +use futures::{future, Future}; //fn guard_head(head: &actix_web::dev::RequestHead) -> bool { // true @@ -15,16 +15,15 @@ fn test() -> impl Responder { } #[get("/test")] -fn auto_async() -> impl Future { +fn auto_async() -> impl Future { future::ok(HttpResponse::Ok().finish()) } #[get("/test")] -fn auto_sync() -> impl Future { +fn auto_sync() -> impl Future { future::ok(HttpResponse::Ok().finish()) } - #[test] fn test_body() { let mut srv = TestServer::new(|| HttpService::new(App::new().service(test))); diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index aaf381386..66ca150b1 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -241,8 +241,8 @@ impl Drop for StreamLog { } impl MessageBody for StreamLog { - fn length(&self) -> BodySize { - self.body.length() + fn size(&self) -> BodySize { + self.body.size() } fn poll_next(&mut self) -> Poll, Error> { diff --git a/src/service.rs b/src/service.rs index f0ff02158..01875854e 100644 --- a/src/service.rs +++ b/src/service.rs @@ -360,7 +360,7 @@ impl fmt::Debug for ServiceResponse { for (key, val) in self.response.head().headers.iter() { let _ = writeln!(f, " {:?}: {:?}", key, val); } - let _ = writeln!(f, " body: {:?}", self.response.body().length()); + let _ = writeln!(f, " body: {:?}", self.response.body().size()); res } } From 9d82d4dfb9f8e5174c46bcc68662b0e1ef0bbb4e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 10 Apr 2019 12:43:31 -0700 Subject: [PATCH 045/122] Fix body propagation in Response::from_error. #760 --- CHANGES.md | 4 ++++ Cargo.toml | 1 + actix-http/src/error.rs | 32 +++++++++++++++++++++++++++++++- actix-http/src/helpers.rs | 15 ++++++++++++++- actix-http/src/response.rs | 22 +++------------------- src/app.rs | 2 +- src/data.rs | 2 +- src/error.rs | 4 ++-- src/types/json.rs | 35 ++++++++++++++++++++++++++++++++++- test-server/src/lib.rs | 2 +- 10 files changed, 92 insertions(+), 27 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f05137ab9..607e9d4f5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -18,6 +18,10 @@ * Move multipart support to actix-multipart crate +### Fixed + +* Fix body propagation in Response::from_error. #760 + ## [1.0.0-alpha.3] - 2019-04-02 diff --git a/Cargo.toml b/Cargo.toml index 0c4d31374..609b2ff3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ members = [ "awc", "actix-http", "actix-files", + "actix-framed", "actix-session", "actix-multipart", "actix-web-actors", diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 6573c8ce6..92a046846 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -1,11 +1,13 @@ //! Error and Result module use std::cell::RefCell; +use std::io::Write; use std::str::Utf8Error; use std::string::FromUtf8Error; use std::{fmt, io, result}; pub use actix_threadpool::BlockingError; use actix_utils::timeout::TimeoutError; +use bytes::BytesMut; use derive_more::{Display, From}; use futures::Canceled; use http::uri::InvalidUri; @@ -17,7 +19,9 @@ use serde_urlencoded::ser::Error as FormError; use tokio_timer::Error as TimerError; // re-export for convinience +use crate::body::Body; pub use crate::cookie::ParseError as CookieParseError; +use crate::helpers::Writer; use crate::response::Response; /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) @@ -57,6 +61,18 @@ pub trait ResponseError: fmt::Debug + fmt::Display { fn error_response(&self) -> Response { Response::new(StatusCode::INTERNAL_SERVER_ERROR) } + + /// Constructs an error response + fn render_response(&self) -> Response { + let mut resp = self.error_response(); + let mut buf = BytesMut::new(); + let _ = write!(Writer(&mut buf), "{}", self); + resp.headers_mut().insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/plain"), + ); + resp.set_body(Body::from(buf)) + } } impl fmt::Display for Error { @@ -477,7 +493,16 @@ where { fn error_response(&self) -> Response { match self.status { - InternalErrorType::Status(st) => Response::new(st), + InternalErrorType::Status(st) => { + let mut res = Response::new(st); + let mut buf = BytesMut::new(); + let _ = write!(Writer(&mut buf), "{}", self); + res.headers_mut().insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/plain"), + ); + res.set_body(Body::from(buf)) + } InternalErrorType::Response(ref resp) => { if let Some(resp) = resp.borrow_mut().take() { resp @@ -487,6 +512,11 @@ where } } } + + /// Constructs an error response + fn render_response(&self) -> Response { + self.error_response() + } } /// Convert Response to a Error diff --git a/actix-http/src/helpers.rs b/actix-http/src/helpers.rs index e4ccd8aef..e8dbcd82a 100644 --- a/actix-http/src/helpers.rs +++ b/actix-http/src/helpers.rs @@ -1,6 +1,7 @@ +use std::{io, mem, ptr, slice}; + use bytes::{BufMut, BytesMut}; use http::Version; -use std::{mem, ptr, slice}; const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\ 2021222324252627282930313233343536373839\ @@ -167,6 +168,18 @@ pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) { } } +pub(crate) struct Writer<'a>(pub &'a mut BytesMut); + +impl<'a> io::Write for Writer<'a> { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.extend_from_slice(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 95e4e789b..6125ae1cb 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -1,7 +1,7 @@ //! Http response use std::cell::{Ref, RefMut}; use std::io::Write; -use std::{fmt, io, str}; +use std::{fmt, str}; use bytes::{BufMut, Bytes, BytesMut}; use futures::future::{ok, FutureResult, IntoFuture}; @@ -51,13 +51,9 @@ impl Response { /// Constructs an error response #[inline] pub fn from_error(error: Error) -> Response { - let mut resp = error.as_response_error().error_response(); - let mut buf = BytesMut::new(); - let _ = write!(Writer(&mut buf), "{}", error); - resp.headers_mut() - .insert(header::CONTENT_TYPE, HeaderValue::from_static("text/plain")); + let mut resp = error.as_response_error().render_response(); resp.error = Some(error); - resp.set_body(Body::from(buf)) + resp } /// Convert response to response with body @@ -309,18 +305,6 @@ impl<'a> Iterator for CookieIter<'a> { } } -pub struct Writer<'a>(pub &'a mut BytesMut); - -impl<'a> io::Write for Writer<'a> { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - /// An HTTP response builder /// /// This type can be used to construct an instance of `Response` through a diff --git a/src/app.rs b/src/app.rs index 802569458..f378572b2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -64,7 +64,7 @@ where InitError = (), >, { - /// Set application data. Applicatin data could be accessed + /// Set application data. Application data could be accessed /// by using `Data` extractor where `T` is data type. /// /// **Note**: http server accepts an application factory rather than diff --git a/src/data.rs b/src/data.rs index 502dd6be8..7fb382f82 100644 --- a/src/data.rs +++ b/src/data.rs @@ -25,7 +25,7 @@ pub(crate) trait DataFactoryResult { /// during application configuration process /// with `App::data()` method. /// -/// Applicatin data could be accessed by using `Data` +/// Application data could be accessed by using `Data` /// extractor where `T` is data type. /// /// **Note**: http server accepts an application factory rather than diff --git a/src/error.rs b/src/error.rs index 74b890f0a..e9e225f22 100644 --- a/src/error.rs +++ b/src/error.rs @@ -31,7 +31,7 @@ pub enum UrlencodedError { #[display(fmt = "Can not decode chunked transfer encoding")] Chunked, /// Payload size is bigger than allowed. (default: 256kB) - #[display(fmt = "Urlencoded payload size is bigger than allowed. (default: 256kB)")] + #[display(fmt = "Urlencoded payload size is bigger than allowed (default: 256kB)")] Overflow, /// Payload size is now known #[display(fmt = "Payload size is now known")] @@ -66,7 +66,7 @@ impl ResponseError for UrlencodedError { #[derive(Debug, Display, From)] pub enum JsonPayloadError { /// Payload size is bigger than allowed. (default: 32kB) - #[display(fmt = "Json payload size is bigger than allowed.")] + #[display(fmt = "Json payload size is bigger than allowed")] Overflow, /// Content type error #[display(fmt = "Content type error")] diff --git a/src/types/json.rs b/src/types/json.rs index f001ee1f1..912561510 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -365,8 +365,10 @@ mod tests { use serde_derive::{Deserialize, Serialize}; use super::*; + use crate::error::InternalError; use crate::http::header; use crate::test::{block_on, TestRequest}; + use crate::HttpResponse; #[derive(Serialize, Deserialize, PartialEq, Debug)] struct MyObject { @@ -405,6 +407,37 @@ mod tests { assert_eq!(resp.body().bin_ref(), b"{\"name\":\"test\"}"); } + #[test] + fn test_custom_error_responder() { + let (req, mut pl) = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .route_data(JsonConfig::default().limit(10).error_handler(|err, _| { + let msg = MyObject { + name: "invalid request".to_string(), + }; + let resp = HttpResponse::BadRequest() + .body(serde_json::to_string(&msg).unwrap()); + InternalError::from_response(err, resp).into() + })) + .to_http_parts(); + + let s = block_on(Json::::from_request(&req, &mut pl)); + let mut resp = Response::from_error(s.err().unwrap().into()); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + + let body = block_on(resp.take_body().concat2()).unwrap(); + let msg: MyObject = serde_json::from_slice(&body).unwrap(); + assert_eq!(msg.name, "invalid request"); + } + #[test] fn test_extract() { let (req, mut pl) = TestRequest::default() @@ -443,7 +476,7 @@ mod tests { let s = block_on(Json::::from_request(&req, &mut pl)); assert!(format!("{}", s.err().unwrap()) - .contains("Json payload size is bigger than allowed.")); + .contains("Json payload size is bigger than allowed")); let (req, mut pl) = TestRequest::default() .header( diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 3f77f3786..d83432df9 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -107,7 +107,7 @@ impl TestServer { TestServerRuntime { addr, rt, client } } - /// Get firat available unused address + /// Get first available unused address pub fn unused_addr() -> net::SocketAddr { let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); let socket = TcpBuilder::new_v4().unwrap(); From 6ab98389777abe2f9c3402706cded793e59275ff Mon Sep 17 00:00:00 2001 From: Darin Date: Wed, 10 Apr 2019 15:45:13 -0400 Subject: [PATCH 046/122] added some error logging for extractors: Data, Json, Query, and Path (#765) * added some error logging for extractors * changed log::error to log::debug and fixed position of log for path * added request path to debug logs --- src/data.rs | 3 +++ src/types/json.rs | 4 ++++ src/types/query.rs | 6 +++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/data.rs b/src/data.rs index 7fb382f82..edaf32c8e 100644 --- a/src/data.rs +++ b/src/data.rs @@ -96,6 +96,8 @@ impl FromRequest

for Data { if let Some(st) = req.app_config().extensions().get::>() { Ok(st.clone()) } else { + log::debug!("Failed to construct App-level Data extractor. \ + Request path: {:?}", req.path()); Err(ErrorInternalServerError( "App data is not configured, to configure use App::data()", )) @@ -235,6 +237,7 @@ impl FromRequest

for RouteData { if let Some(st) = req.route_data::() { Ok(st.clone()) } else { + log::debug!("Failed to construct Route-level Data extractor"); Err(ErrorInternalServerError( "Route data is not configured, to configure use Route::data()", )) diff --git a/src/types/json.rs b/src/types/json.rs index 912561510..99fd5b417 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -179,10 +179,14 @@ where .map(|c| (c.limit, c.ehandler.clone())) .unwrap_or((32768, None)); + let path = req.path().to_string(); + Box::new( JsonBody::new(req, payload) .limit(limit) .map_err(move |e| { + log::debug!("Failed to deserialize Json from payload. \ + Request path: {:?}", path); if let Some(err) = err { (*err)(e, &req2) } else { diff --git a/src/types/query.rs b/src/types/query.rs index 3bbb465c9..363d56199 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -122,6 +122,10 @@ where fn from_request(req: &HttpRequest, _: &mut Payload

) -> Self::Future { serde_urlencoded::from_str::(req.query_string()) .map(|val| Ok(Query(val))) - .unwrap_or_else(|e| Err(e.into())) + .unwrap_or_else(|e| { + log::debug!("Failed during Query extractor deserialization. \ + Request path: {:?}", req.path()); + Err(e.into()) + }) } } From 6b42b2aaee6e845ba9d38c1d2103c45e7f3ebdd8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 10 Apr 2019 12:55:56 -0700 Subject: [PATCH 047/122] remove framed for now --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 609b2ff3f..0c4d31374 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,6 @@ members = [ "awc", "actix-http", "actix-files", - "actix-framed", "actix-session", "actix-multipart", "actix-web-actors", From 52aebb3bca4c6d38ce02a0c8ac60a5b29b58e1ef Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 10 Apr 2019 15:05:03 -0700 Subject: [PATCH 048/122] fmt --- Cargo.toml | 1 + src/data.rs | 7 +++++-- src/types/json.rs | 7 +++++-- src/types/query.rs | 7 +++++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0c4d31374..609b2ff3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ members = [ "awc", "actix-http", "actix-files", + "actix-framed", "actix-session", "actix-multipart", "actix-web-actors", diff --git a/src/data.rs b/src/data.rs index edaf32c8e..c697bac5e 100644 --- a/src/data.rs +++ b/src/data.rs @@ -96,8 +96,11 @@ impl FromRequest

for Data { if let Some(st) = req.app_config().extensions().get::>() { Ok(st.clone()) } else { - log::debug!("Failed to construct App-level Data extractor. \ - Request path: {:?}", req.path()); + log::debug!( + "Failed to construct App-level Data extractor. \ + Request path: {:?}", + req.path() + ); Err(ErrorInternalServerError( "App data is not configured, to configure use App::data()", )) diff --git a/src/types/json.rs b/src/types/json.rs index 99fd5b417..5044cf70c 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -185,8 +185,11 @@ where JsonBody::new(req, payload) .limit(limit) .map_err(move |e| { - log::debug!("Failed to deserialize Json from payload. \ - Request path: {:?}", path); + log::debug!( + "Failed to deserialize Json from payload. \ + Request path: {:?}", + path + ); if let Some(err) = err { (*err)(e, &req2) } else { diff --git a/src/types/query.rs b/src/types/query.rs index 363d56199..0d37c45f3 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -123,8 +123,11 @@ where serde_urlencoded::from_str::(req.query_string()) .map(|val| Ok(Query(val))) .unwrap_or_else(|e| { - log::debug!("Failed during Query extractor deserialization. \ - Request path: {:?}", req.path()); + log::debug!( + "Failed during Query extractor deserialization. \ + Request path: {:?}", + req.path() + ); Err(e.into()) }) } From 8dc4a88aa6ee4bf215c810bd5a11452e1755e1a3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 10 Apr 2019 15:06:27 -0700 Subject: [PATCH 049/122] add actix-framed --- actix-framed/Cargo.toml | 37 +++++ actix-framed/LICENSE-APACHE | 201 ++++++++++++++++++++++++++++ actix-framed/LICENSE-MIT | 25 ++++ actix-framed/README.md | 1 + actix-framed/src/app.rs | 215 ++++++++++++++++++++++++++++++ actix-framed/src/helpers.rs | 88 ++++++++++++ actix-framed/src/lib.rs | 13 ++ actix-framed/src/request.rs | 30 +++++ actix-framed/src/route.rs | 189 ++++++++++++++++++++++++++ actix-framed/src/state.rs | 29 ++++ actix-framed/tests/test_server.rs | 81 +++++++++++ 11 files changed, 909 insertions(+) create mode 100644 actix-framed/Cargo.toml create mode 100644 actix-framed/LICENSE-APACHE create mode 100644 actix-framed/LICENSE-MIT create mode 100644 actix-framed/README.md create mode 100644 actix-framed/src/app.rs create mode 100644 actix-framed/src/helpers.rs create mode 100644 actix-framed/src/lib.rs create mode 100644 actix-framed/src/request.rs create mode 100644 actix-framed/src/route.rs create mode 100644 actix-framed/src/state.rs create mode 100644 actix-framed/tests/test_server.rs diff --git a/actix-framed/Cargo.toml b/actix-framed/Cargo.toml new file mode 100644 index 000000000..a2919bcec --- /dev/null +++ b/actix-framed/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "actix-framed" +version = "0.1.0" +authors = ["Nikolay Kim "] +description = "Actix framed app server" +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-framed/" +categories = ["network-programming", "asynchronous", + "web-programming::http-server", + "web-programming::websocket"] +license = "MIT/Apache-2.0" +edition = "2018" +workspace =".." + +[lib] +name = "actix_framed" +path = "src/lib.rs" + +[dependencies] +actix-codec = "0.1.2" +actix-service = "0.3.6" +actix-utils = "0.3.4" +actix-router = "0.1.2" +actix-http = { path = "../actix-http" } + +bytes = "0.4" +futures = "0.1.25" +log = "0.4" + +[dev-dependencies] +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.3", features=["ssl"] } diff --git a/actix-framed/LICENSE-APACHE b/actix-framed/LICENSE-APACHE new file mode 100644 index 000000000..6cdf2d16c --- /dev/null +++ b/actix-framed/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2017-NOW Nikolay Kim + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/actix-framed/LICENSE-MIT b/actix-framed/LICENSE-MIT new file mode 100644 index 000000000..0f80296ae --- /dev/null +++ b/actix-framed/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2017 Nikolay Kim + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/actix-framed/README.md b/actix-framed/README.md new file mode 100644 index 000000000..f56ae145c --- /dev/null +++ b/actix-framed/README.md @@ -0,0 +1 @@ +# Framed app for actix web [![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-framed)](https://crates.io/crates/actix-framed) [![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-framed/src/app.rs b/actix-framed/src/app.rs new file mode 100644 index 000000000..35486e5c0 --- /dev/null +++ b/actix-framed/src/app.rs @@ -0,0 +1,215 @@ +use std::rc::Rc; + +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_http::h1::{Codec, SendResponse}; +use actix_http::{Error, Request, Response}; +use actix_router::{Path, Router, Url}; +use actix_service::{IntoNewService, NewService, Service}; +use actix_utils::cloneable::CloneableService; +use futures::{Async, Future, Poll}; + +use crate::helpers::{BoxedHttpNewService, BoxedHttpService, HttpNewService}; +use crate::request::FramedRequest; +use crate::state::State; + +type BoxedResponse = Box>; + +pub trait HttpServiceFactory { + type Factory: NewService; + + fn path(&self) -> &str; + + fn create(self) -> Self::Factory; +} + +/// Application builder +pub struct App { + state: State, + services: Vec<(String, BoxedHttpNewService>)>, +} + +impl App { + pub fn new() -> Self { + App { + state: State::new(()), + services: Vec::new(), + } + } +} + +impl App { + pub fn with(state: S) -> App { + App { + services: Vec::new(), + state: State::new(state), + } + } + + pub fn service(mut self, factory: U) -> Self + where + U: HttpServiceFactory, + U::Factory: NewService< + Request = FramedRequest, + Response = (), + Error = Error, + InitError = (), + > + 'static, + ::Future: 'static, + ::Service: Service< + Request = FramedRequest, + Response = (), + Error = Error, + Future = Box>, + >, + { + let path = factory.path().to_string(); + self.services + .push((path, Box::new(HttpNewService::new(factory.create())))); + self + } +} + +impl IntoNewService> for App +where + T: AsyncRead + AsyncWrite + 'static, + S: 'static, +{ + fn into_new_service(self) -> AppFactory { + AppFactory { + state: self.state, + services: Rc::new(self.services), + } + } +} + +#[derive(Clone)] +pub struct AppFactory { + state: State, + services: Rc>)>>, +} + +impl NewService for AppFactory +where + T: AsyncRead + AsyncWrite + 'static, + S: 'static, +{ + type Request = (Request, Framed); + type Response = (); + type Error = Error; + type InitError = (); + type Service = CloneableService>; + type Future = CreateService; + + fn new_service(&self, _: &()) -> Self::Future { + CreateService { + fut: self + .services + .iter() + .map(|(path, service)| { + CreateServiceItem::Future( + Some(path.clone()), + service.new_service(&()), + ) + }) + .collect(), + state: self.state.clone(), + } + } +} + +#[doc(hidden)] +pub struct CreateService { + fut: Vec>, + state: State, +} + +enum CreateServiceItem { + Future( + Option, + Box>, Error = ()>>, + ), + Service(String, BoxedHttpService>), +} + +impl Future for CreateService +where + T: AsyncRead + AsyncWrite, +{ + type Item = CloneableService>; + type Error = (); + + fn poll(&mut self) -> Poll { + let mut done = true; + + // poll http services + for item in &mut self.fut { + let res = match item { + CreateServiceItem::Future(ref mut path, ref mut fut) => { + match fut.poll()? { + Async::Ready(service) => Some((path.take().unwrap(), service)), + Async::NotReady => { + done = false; + None + } + } + } + CreateServiceItem::Service(_, _) => continue, + }; + + if let Some((path, service)) = res { + *item = CreateServiceItem::Service(path, service); + } + } + + if done { + let router = self + .fut + .drain(..) + .fold(Router::build(), |mut router, item| { + match item { + CreateServiceItem::Service(path, service) => { + router.path(&path, service); + } + CreateServiceItem::Future(_, _) => unreachable!(), + } + router + }); + Ok(Async::Ready(CloneableService::new(AppService { + router: router.finish(), + state: self.state.clone(), + }))) + } else { + Ok(Async::NotReady) + } + } +} + +pub struct AppService { + state: State, + router: Router>>, +} + +impl Service for AppService +where + T: AsyncRead + AsyncWrite, +{ + type Request = (Request, Framed); + type Response = (); + type Error = Error; + type Future = BoxedResponse; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, (req, framed): (Request, Framed)) -> Self::Future { + let mut path = Path::new(Url::new(req.uri().clone())); + + if let Some((srv, _info)) = self.router.recognize_mut(&mut path) { + return srv.call(FramedRequest::new(req, framed, self.state.clone())); + } + Box::new( + SendResponse::new(framed, Response::NotFound().finish()).then(|_| Ok(())), + ) + } +} diff --git a/actix-framed/src/helpers.rs b/actix-framed/src/helpers.rs new file mode 100644 index 000000000..c2c7dbd8b --- /dev/null +++ b/actix-framed/src/helpers.rs @@ -0,0 +1,88 @@ +use actix_http::Error; +use actix_service::{NewService, Service}; +use futures::{Future, Poll}; + +pub(crate) type BoxedHttpService = Box< + Service< + Request = Req, + Response = (), + Error = Error, + Future = Box>, + >, +>; + +pub(crate) type BoxedHttpNewService = Box< + NewService< + Request = Req, + Response = (), + Error = Error, + InitError = (), + Service = BoxedHttpService, + Future = Box, Error = ()>>, + >, +>; + +pub(crate) struct HttpNewService(T); + +impl HttpNewService +where + T: NewService, + T::Response: 'static, + T::Future: 'static, + T::Service: Service>> + 'static, + ::Future: 'static, +{ + pub fn new(service: T) -> Self { + HttpNewService(service) + } +} + +impl NewService for HttpNewService +where + T: NewService, + T::Request: 'static, + T::Future: 'static, + T::Service: Service>> + 'static, + ::Future: 'static, +{ + type Request = T::Request; + type Response = (); + type Error = Error; + type InitError = (); + type Service = BoxedHttpService; + type Future = Box>; + + fn new_service(&self, _: &()) -> Self::Future { + Box::new(self.0.new_service(&()).map_err(|_| ()).and_then(|service| { + let service: BoxedHttpService<_> = Box::new(HttpServiceWrapper { service }); + Ok(service) + })) + } +} + +struct HttpServiceWrapper { + service: T, +} + +impl Service for HttpServiceWrapper +where + T: Service< + Response = (), + Future = Box>, + Error = Error, + >, + T::Request: 'static, +{ + type Request = T::Request; + type Response = (); + type Error = Error; + type Future = Box>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.service.poll_ready() + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + self.service.call(req) + } +} diff --git a/actix-framed/src/lib.rs b/actix-framed/src/lib.rs new file mode 100644 index 000000000..6cc364461 --- /dev/null +++ b/actix-framed/src/lib.rs @@ -0,0 +1,13 @@ +mod app; +mod helpers; +mod request; +mod route; +mod state; + +// re-export for convinience +pub use actix_http::{http, Error, HttpMessage, Response, ResponseError}; + +pub use self::app::{App, AppService}; +pub use self::request::FramedRequest; +pub use self::route::FramedRoute; +pub use self::state::State; diff --git a/actix-framed/src/request.rs b/actix-framed/src/request.rs new file mode 100644 index 000000000..4bc2932cd --- /dev/null +++ b/actix-framed/src/request.rs @@ -0,0 +1,30 @@ +use actix_codec::Framed; +use actix_http::{h1::Codec, Request}; + +use crate::state::State; + +pub struct FramedRequest { + req: Request, + framed: Framed, + state: State, +} + +impl FramedRequest { + pub fn new(req: Request, framed: Framed, state: State) -> Self { + Self { req, framed, state } + } +} + +impl FramedRequest { + pub fn request(&self) -> &Request { + &self.req + } + + pub fn request_mut(&mut self) -> &mut Request { + &mut self.req + } + + pub fn into_parts(self) -> (Request, Framed, State) { + (self.req, self.framed, self.state) + } +} diff --git a/actix-framed/src/route.rs b/actix-framed/src/route.rs new file mode 100644 index 000000000..4f5c4e69e --- /dev/null +++ b/actix-framed/src/route.rs @@ -0,0 +1,189 @@ +use std::fmt; +use std::marker::PhantomData; + +use actix_codec::{AsyncRead, AsyncWrite}; +use actix_http::{http::Method, Error}; +use actix_service::{NewService, Service}; +use futures::future::{ok, FutureResult}; +use futures::{Async, Future, IntoFuture, Poll}; +use log::error; + +use crate::app::HttpServiceFactory; +use crate::request::FramedRequest; + +/// Resource route definition +/// +/// Route uses builder-like pattern for configuration. +/// If handler is not explicitly set, default *404 Not Found* handler is used. +pub struct FramedRoute { + handler: F, + pattern: String, + methods: Vec, + state: PhantomData<(Io, S, R)>, +} + +impl FramedRoute { + pub fn build(path: &str) -> FramedRouteBuilder { + FramedRouteBuilder::new(path) + } + + pub fn get(path: &str) -> FramedRouteBuilder { + FramedRouteBuilder::new(path).method(Method::GET) + } + + pub fn post(path: &str) -> FramedRouteBuilder { + FramedRouteBuilder::new(path).method(Method::POST) + } + + pub fn put(path: &str) -> FramedRouteBuilder { + FramedRouteBuilder::new(path).method(Method::PUT) + } + + pub fn delete(path: &str) -> FramedRouteBuilder { + FramedRouteBuilder::new(path).method(Method::DELETE) + } +} + +impl FramedRoute +where + F: FnMut(FramedRequest) -> R + Clone, + R: IntoFuture, + R::Future: 'static, + R::Error: fmt::Display, +{ + pub fn new(pattern: &str, handler: F) -> Self { + FramedRoute { + handler, + pattern: pattern.to_string(), + methods: Vec::new(), + state: PhantomData, + } + } + + pub fn method(mut self, method: Method) -> Self { + self.methods.push(method); + self + } +} + +impl HttpServiceFactory for FramedRoute +where + Io: AsyncRead + AsyncWrite + 'static, + F: FnMut(FramedRequest) -> R + Clone, + R: IntoFuture, + R::Future: 'static, + R::Error: fmt::Display, +{ + type Factory = FramedRouteFactory; + + fn path(&self) -> &str { + &self.pattern + } + + fn create(self) -> Self::Factory { + FramedRouteFactory { + handler: self.handler, + methods: self.methods, + _t: PhantomData, + } + } +} + +pub struct FramedRouteFactory { + handler: F, + methods: Vec, + _t: PhantomData<(Io, S, R)>, +} + +impl NewService for FramedRouteFactory +where + Io: AsyncRead + AsyncWrite + 'static, + F: FnMut(FramedRequest) -> R + Clone, + R: IntoFuture, + R::Future: 'static, + R::Error: fmt::Display, +{ + type Request = FramedRequest; + type Response = (); + type Error = Error; + type InitError = (); + type Service = FramedRouteService; + type Future = FutureResult; + + fn new_service(&self, _: &()) -> Self::Future { + ok(FramedRouteService { + handler: self.handler.clone(), + methods: self.methods.clone(), + _t: PhantomData, + }) + } +} + +pub struct FramedRouteService { + handler: F, + methods: Vec, + _t: PhantomData<(Io, S, R)>, +} + +impl Service for FramedRouteService +where + Io: AsyncRead + AsyncWrite + 'static, + F: FnMut(FramedRequest) -> R + Clone, + R: IntoFuture, + R::Future: 'static, + R::Error: fmt::Display, +{ + type Request = FramedRequest; + type Response = (); + type Error = Error; + type Future = Box>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, req: FramedRequest) -> Self::Future { + Box::new((self.handler)(req).into_future().then(|res| { + if let Err(e) = res { + error!("Error in request handler: {}", e); + } + Ok(()) + })) + } +} + +pub struct FramedRouteBuilder { + pattern: String, + methods: Vec, + state: PhantomData<(Io, S)>, +} + +impl FramedRouteBuilder { + fn new(path: &str) -> FramedRouteBuilder { + FramedRouteBuilder { + pattern: path.to_string(), + methods: Vec::new(), + state: PhantomData, + } + } + + pub fn method(mut self, method: Method) -> Self { + self.methods.push(method); + self + } + + pub fn to(self, handler: F) -> FramedRoute + where + F: FnMut(FramedRequest) -> R, + R: IntoFuture, + R::Future: 'static, + R::Error: fmt::Debug, + { + FramedRoute { + handler, + pattern: self.pattern, + methods: self.methods, + state: PhantomData, + } + } +} diff --git a/actix-framed/src/state.rs b/actix-framed/src/state.rs new file mode 100644 index 000000000..600a639ca --- /dev/null +++ b/actix-framed/src/state.rs @@ -0,0 +1,29 @@ +use std::ops::Deref; +use std::sync::Arc; + +/// Application state +pub struct State(Arc); + +impl State { + pub fn new(state: S) -> State { + State(Arc::new(state)) + } + + pub fn get_ref(&self) -> &S { + self.0.as_ref() + } +} + +impl Deref for State { + type Target = S; + + fn deref(&self) -> &S { + self.0.as_ref() + } +} + +impl Clone for State { + fn clone(&self) -> State { + State(self.0.clone()) + } +} diff --git a/actix-framed/tests/test_server.rs b/actix-framed/tests/test_server.rs new file mode 100644 index 000000000..6a21b3fcf --- /dev/null +++ b/actix-framed/tests/test_server.rs @@ -0,0 +1,81 @@ +use actix_codec::{AsyncRead, AsyncWrite}; +use actix_http::{body, ws, Error, HttpService, Response}; +use actix_http_test::TestServer; +use actix_utils::framed::FramedTransport; +use bytes::{Bytes, BytesMut}; +use futures::future::{self, ok}; +use futures::{Future, Sink, Stream}; + +use actix_framed::{App, FramedRequest, FramedRoute}; + +fn ws_service( + req: FramedRequest, +) -> impl Future { + let (req, framed, _) = req.into_parts(); + let res = ws::handshake(&req).unwrap().message_body(()); + + framed + .send((res, body::BodySize::None).into()) + .map_err(|_| panic!()) + .and_then(|framed| { + FramedTransport::new(framed.into_framed(ws::Codec::new()), service) + .map_err(|_| panic!()) + }) +} + +fn service(msg: ws::Frame) -> impl Future { + let msg = match msg { + ws::Frame::Ping(msg) => ws::Message::Pong(msg), + ws::Frame::Text(text) => { + ws::Message::Text(String::from_utf8_lossy(&text.unwrap()).to_string()) + } + ws::Frame::Binary(bin) => ws::Message::Binary(bin.unwrap().freeze()), + ws::Frame::Close(reason) => ws::Message::Close(reason), + _ => panic!(), + }; + ok(msg) +} + +#[test] +fn test_simple() { + let mut srv = TestServer::new(|| { + HttpService::build() + .upgrade(App::new().service(FramedRoute::get("/index.html").to(ws_service))) + .finish(|_| future::ok::<_, Error>(Response::NotFound())) + }); + + assert!(srv.ws_at("/test").is_err()); + + // client service + let framed = srv.ws_at("/index.html").unwrap(); + let framed = srv + .block_on(framed.send(ws::Message::Text("text".to_string()))) + .unwrap(); + let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text"))))); + + let framed = srv + .block_on(framed.send(ws::Message::Binary("text".into()))) + .unwrap(); + let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!( + item, + Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into()))) + ); + + let framed = srv + .block_on(framed.send(ws::Message::Ping("text".into()))) + .unwrap(); + let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into()))); + + let framed = srv + .block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))) + .unwrap(); + + let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!( + item, + Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into()))) + ); +} From 7cd59c38d386c407e45b4bdc1a9ad652ad9aab5f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 10 Apr 2019 18:08:28 -0700 Subject: [PATCH 050/122] rename framed App --- actix-framed/src/app.rs | 32 +++++----- actix-framed/src/lib.rs | 2 +- actix-framed/src/route.rs | 103 ++++++++++-------------------- actix-framed/tests/test_server.rs | 6 +- 4 files changed, 56 insertions(+), 87 deletions(-) diff --git a/actix-framed/src/app.rs b/actix-framed/src/app.rs index 35486e5c0..d8a273d72 100644 --- a/actix-framed/src/app.rs +++ b/actix-framed/src/app.rs @@ -23,23 +23,23 @@ pub trait HttpServiceFactory { } /// Application builder -pub struct App { +pub struct FramedApp { state: State, services: Vec<(String, BoxedHttpNewService>)>, } -impl App { +impl FramedApp { pub fn new() -> Self { - App { + FramedApp { state: State::new(()), services: Vec::new(), } } } -impl App { - pub fn with(state: S) -> App { - App { +impl FramedApp { + pub fn with(state: S) -> FramedApp { + FramedApp { services: Vec::new(), state: State::new(state), } @@ -69,13 +69,13 @@ impl App { } } -impl IntoNewService> for App +impl IntoNewService> for FramedApp where T: AsyncRead + AsyncWrite + 'static, S: 'static, { - fn into_new_service(self) -> AppFactory { - AppFactory { + fn into_new_service(self) -> FramedAppFactory { + FramedAppFactory { state: self.state, services: Rc::new(self.services), } @@ -83,12 +83,12 @@ where } #[derive(Clone)] -pub struct AppFactory { +pub struct FramedAppFactory { state: State, services: Rc>)>>, } -impl NewService for AppFactory +impl NewService for FramedAppFactory where T: AsyncRead + AsyncWrite + 'static, S: 'static, @@ -97,7 +97,7 @@ where type Response = (); type Error = Error; type InitError = (); - type Service = CloneableService>; + type Service = CloneableService>; type Future = CreateService; fn new_service(&self, _: &()) -> Self::Future { @@ -135,7 +135,7 @@ impl Future for CreateService where T: AsyncRead + AsyncWrite, { - type Item = CloneableService>; + type Item = CloneableService>; type Error = (); fn poll(&mut self) -> Poll { @@ -174,7 +174,7 @@ where } router }); - Ok(Async::Ready(CloneableService::new(AppService { + Ok(Async::Ready(CloneableService::new(FramedAppService { router: router.finish(), state: self.state.clone(), }))) @@ -184,12 +184,12 @@ where } } -pub struct AppService { +pub struct FramedAppService { state: State, router: Router>>, } -impl Service for AppService +impl Service for FramedAppService where T: AsyncRead + AsyncWrite, { diff --git a/actix-framed/src/lib.rs b/actix-framed/src/lib.rs index 6cc364461..a67b82e43 100644 --- a/actix-framed/src/lib.rs +++ b/actix-framed/src/lib.rs @@ -7,7 +7,7 @@ mod state; // re-export for convinience pub use actix_http::{http, Error, HttpMessage, Response, ResponseError}; -pub use self::app::{App, AppService}; +pub use self::app::{FramedApp, FramedAppService}; pub use self::request::FramedRequest; pub use self::route::FramedRoute; pub use self::state::State; diff --git a/actix-framed/src/route.rs b/actix-framed/src/route.rs index 4f5c4e69e..c8d9d4326 100644 --- a/actix-framed/src/route.rs +++ b/actix-framed/src/route.rs @@ -15,55 +15,58 @@ use crate::request::FramedRequest; /// /// Route uses builder-like pattern for configuration. /// If handler is not explicitly set, default *404 Not Found* handler is used. -pub struct FramedRoute { +pub struct FramedRoute { handler: F, pattern: String, methods: Vec, state: PhantomData<(Io, S, R)>, } -impl FramedRoute { - pub fn build(path: &str) -> FramedRouteBuilder { - FramedRouteBuilder::new(path) - } - - pub fn get(path: &str) -> FramedRouteBuilder { - FramedRouteBuilder::new(path).method(Method::GET) - } - - pub fn post(path: &str) -> FramedRouteBuilder { - FramedRouteBuilder::new(path).method(Method::POST) - } - - pub fn put(path: &str) -> FramedRouteBuilder { - FramedRouteBuilder::new(path).method(Method::PUT) - } - - pub fn delete(path: &str) -> FramedRouteBuilder { - FramedRouteBuilder::new(path).method(Method::DELETE) - } -} - -impl FramedRoute -where - F: FnMut(FramedRequest) -> R + Clone, - R: IntoFuture, - R::Future: 'static, - R::Error: fmt::Display, -{ - pub fn new(pattern: &str, handler: F) -> Self { +impl FramedRoute { + pub fn new(pattern: &str) -> Self { FramedRoute { - handler, + handler: (), pattern: pattern.to_string(), methods: Vec::new(), state: PhantomData, } } + pub fn get(path: &str) -> FramedRoute { + FramedRoute::new(path).method(Method::GET) + } + + pub fn post(path: &str) -> FramedRoute { + FramedRoute::new(path).method(Method::POST) + } + + pub fn put(path: &str) -> FramedRoute { + FramedRoute::new(path).method(Method::PUT) + } + + pub fn delete(path: &str) -> FramedRoute { + FramedRoute::new(path).method(Method::DELETE) + } + pub fn method(mut self, method: Method) -> Self { self.methods.push(method); self } + + pub fn to(self, handler: F) -> FramedRoute + where + F: FnMut(FramedRequest) -> R, + R: IntoFuture, + R::Future: 'static, + R::Error: fmt::Debug, + { + FramedRoute { + handler, + pattern: self.pattern, + methods: self.methods, + state: PhantomData, + } + } } impl HttpServiceFactory for FramedRoute @@ -151,39 +154,3 @@ where })) } } - -pub struct FramedRouteBuilder { - pattern: String, - methods: Vec, - state: PhantomData<(Io, S)>, -} - -impl FramedRouteBuilder { - fn new(path: &str) -> FramedRouteBuilder { - FramedRouteBuilder { - pattern: path.to_string(), - methods: Vec::new(), - state: PhantomData, - } - } - - pub fn method(mut self, method: Method) -> Self { - self.methods.push(method); - self - } - - pub fn to(self, handler: F) -> FramedRoute - where - F: FnMut(FramedRequest) -> R, - R: IntoFuture, - R::Future: 'static, - R::Error: fmt::Debug, - { - FramedRoute { - handler, - pattern: self.pattern, - methods: self.methods, - state: PhantomData, - } - } -} diff --git a/actix-framed/tests/test_server.rs b/actix-framed/tests/test_server.rs index 6a21b3fcf..09d2c03cc 100644 --- a/actix-framed/tests/test_server.rs +++ b/actix-framed/tests/test_server.rs @@ -6,7 +6,7 @@ use bytes::{Bytes, BytesMut}; use futures::future::{self, ok}; use futures::{Future, Sink, Stream}; -use actix_framed::{App, FramedRequest, FramedRoute}; +use actix_framed::{FramedApp, FramedRequest, FramedRoute}; fn ws_service( req: FramedRequest, @@ -40,7 +40,9 @@ fn service(msg: ws::Frame) -> impl Future { fn test_simple() { let mut srv = TestServer::new(|| { HttpService::build() - .upgrade(App::new().service(FramedRoute::get("/index.html").to(ws_service))) + .upgrade( + FramedApp::new().service(FramedRoute::get("/index.html").to(ws_service)), + ) .finish(|_| future::ok::<_, Error>(Response::NotFound())) }); From 12e1dad42e8e14bbef0c75dba34d629387a504bb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 10 Apr 2019 19:43:09 -0700 Subject: [PATCH 051/122] export TestBuffer --- actix-http/src/h1/dispatcher.rs | 61 ++--------------------------- actix-http/src/test.rs | 69 ++++++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 59 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index cca181c99..cf39b8232 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -815,76 +815,21 @@ where #[cfg(test)] mod tests { - use std::{cmp, io}; - - use actix_codec::{AsyncRead, AsyncWrite}; use actix_service::IntoService; - use bytes::{Buf, Bytes, BytesMut}; use futures::future::{lazy, ok}; use super::*; use crate::error::Error; use crate::h1::{ExpectHandler, UpgradeHandler}; - - struct Buffer { - buf: Bytes, - write_buf: BytesMut, - err: Option, - } - - impl Buffer { - fn new(data: &'static str) -> Buffer { - Buffer { - buf: Bytes::from(data), - write_buf: BytesMut::new(), - err: None, - } - } - } - - impl AsyncRead for Buffer {} - impl io::Read for Buffer { - fn read(&mut self, dst: &mut [u8]) -> Result { - if self.buf.is_empty() { - if self.err.is_some() { - Err(self.err.take().unwrap()) - } else { - Err(io::Error::new(io::ErrorKind::WouldBlock, "")) - } - } else { - let size = cmp::min(self.buf.len(), dst.len()); - let b = self.buf.split_to(size); - dst[..size].copy_from_slice(&b); - Ok(size) - } - } - } - - impl io::Write for Buffer { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.write_buf.extend(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } - } - impl AsyncWrite for Buffer { - fn shutdown(&mut self) -> Poll<(), io::Error> { - Ok(Async::Ready(())) - } - fn write_buf(&mut self, _: &mut B) -> Poll { - Ok(Async::NotReady) - } - } + use crate::test::TestBuffer; #[test] fn test_req_parse_err() { let mut sys = actix_rt::System::new("test"); let _ = sys.block_on(lazy(|| { - let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); + let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n"); - let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, ServiceConfig::default(), CloneableService::new( diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 2c5dc502b..3d948ebd4 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -1,8 +1,11 @@ //! Test Various helpers for Actix applications to use during testing. use std::fmt::Write as FmtWrite; +use std::io; use std::str::FromStr; -use bytes::Bytes; +use actix_codec::{AsyncRead, AsyncWrite}; +use bytes::{Buf, Bytes, BytesMut}; +use futures::{Async, Poll}; use http::header::{self, HeaderName, HeaderValue}; use http::{HttpTryFrom, Method, Uri, Version}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; @@ -181,3 +184,67 @@ impl TestRequest { fn parts(parts: &mut Option) -> &mut Inner { parts.as_mut().expect("cannot reuse test request builder") } + +/// Async io buffer +pub struct TestBuffer { + pub read_buf: BytesMut, + pub write_buf: BytesMut, + pub err: Option, +} + +impl TestBuffer { + /// Create new TestBuffer instance + pub fn new(data: T) -> TestBuffer + where + BytesMut: From, + { + TestBuffer { + read_buf: BytesMut::from(data), + write_buf: BytesMut::new(), + err: None, + } + } + + /// Add extra data to read buffer. + pub fn extend_read_buf>(&mut self, data: T) { + self.read_buf.extend_from_slice(data.as_ref()) + } +} + +impl io::Read for TestBuffer { + fn read(&mut self, dst: &mut [u8]) -> Result { + if self.read_buf.is_empty() { + if self.err.is_some() { + Err(self.err.take().unwrap()) + } else { + Err(io::Error::new(io::ErrorKind::WouldBlock, "")) + } + } else { + let size = std::cmp::min(self.read_buf.len(), dst.len()); + let b = self.read_buf.split_to(size); + dst[..size].copy_from_slice(&b); + Ok(size) + } + } +} + +impl io::Write for TestBuffer { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.write_buf.extend(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl AsyncRead for TestBuffer {} + +impl AsyncWrite for TestBuffer { + fn shutdown(&mut self) -> Poll<(), io::Error> { + Ok(Async::Ready(())) + } + fn write_buf(&mut self, _: &mut B) -> Poll { + Ok(Async::NotReady) + } +} From e55be4dba66f4e60ea3b0b633049037960db749e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 10 Apr 2019 19:57:34 -0700 Subject: [PATCH 052/122] add FramedRequest helper methods --- actix-framed/src/app.rs | 2 +- actix-framed/src/request.rs | 152 +++++++++++++++++++++++++++++++++--- actix-http/src/test.rs | 5 ++ 3 files changed, 147 insertions(+), 12 deletions(-) diff --git a/actix-framed/src/app.rs b/actix-framed/src/app.rs index d8a273d72..cce618bb9 100644 --- a/actix-framed/src/app.rs +++ b/actix-framed/src/app.rs @@ -206,7 +206,7 @@ where let mut path = Path::new(Url::new(req.uri().clone())); if let Some((srv, _info)) = self.router.recognize_mut(&mut path) { - return srv.call(FramedRequest::new(req, framed, self.state.clone())); + return srv.call(FramedRequest::new(req, framed, path, self.state.clone())); } Box::new( SendResponse::new(framed, Response::NotFound().finish()).then(|_| Ok(())), diff --git a/actix-framed/src/request.rs b/actix-framed/src/request.rs index 4bc2932cd..eab28a9ef 100644 --- a/actix-framed/src/request.rs +++ b/actix-framed/src/request.rs @@ -1,5 +1,9 @@ +use std::cell::{Ref, RefMut}; + use actix_codec::Framed; -use actix_http::{h1::Codec, Request}; +use actix_http::http::{HeaderMap, Method, Uri, Version}; +use actix_http::{h1::Codec, Extensions, Request, RequestHead}; +use actix_router::{Path, Url}; use crate::state::State; @@ -7,24 +11,150 @@ pub struct FramedRequest { req: Request, framed: Framed, state: State, + pub(crate) path: Path, } impl FramedRequest { - pub fn new(req: Request, framed: Framed, state: State) -> Self { - Self { req, framed, state } + pub fn new( + req: Request, + framed: Framed, + path: Path, + state: State, + ) -> Self { + Self { + req, + framed, + state, + path, + } } } impl FramedRequest { - pub fn request(&self) -> &Request { - &self.req - } - - pub fn request_mut(&mut self) -> &mut Request { - &mut self.req - } - + /// Split request into a parts pub fn into_parts(self) -> (Request, Framed, State) { (self.req, self.framed, self.state) } + + /// This method returns reference to the request head + #[inline] + pub fn head(&self) -> &RequestHead { + self.req.head() + } + + /// This method returns muttable reference to the request head. + /// panics if multiple references of http request exists. + #[inline] + pub fn head_mut(&mut self) -> &mut RequestHead { + self.req.head_mut() + } + + /// Shared application state + #[inline] + pub fn state(&self) -> &S { + self.state.get_ref() + } + + /// Request's uri. + #[inline] + pub fn uri(&self) -> &Uri { + &self.head().uri + } + + /// Read the Request method. + #[inline] + pub fn method(&self) -> &Method { + &self.head().method + } + + /// Read the Request Version. + #[inline] + pub fn version(&self) -> Version { + 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 { + self.head().uri.path() + } + + /// The query string in the URL. + /// + /// E.g., id=10 + #[inline] + pub fn query_string(&self) -> &str { + if let Some(query) = self.uri().query().as_ref() { + query + } else { + "" + } + } + + /// Get a reference to the Path parameters. + /// + /// Params is a container for url parameters. + /// A variable segment is specified in the form `{identifier}`, + /// where the identifier can be used later in a request handler to + /// access the matched value for that segment. + #[inline] + pub fn match_info(&self) -> &Path { + &self.path + } + + /// 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() + } +} + +#[cfg(test)] +mod tests { + use actix_http::test::{TestBuffer, TestRequest}; + + use super::*; + + #[test] + fn test_reqest() { + let buf = TestBuffer::empty(); + let framed = Framed::new(buf, Codec::default()); + let req = TestRequest::with_uri("/index.html?q=1") + .header("content-type", "test") + .finish(); + let path = Path::new(Url::new(req.uri().clone())); + + let freq = FramedRequest::new(req, framed, path, State::new(10u8)); + assert_eq!(*freq.state(), 10); + assert_eq!(freq.version(), Version::HTTP_11); + assert_eq!(freq.method(), Method::GET); + assert_eq!(freq.path(), "/index.html"); + assert_eq!(freq.query_string(), "q=1"); + assert_eq!( + freq.headers() + .get("content-type") + .unwrap() + .to_str() + .unwrap(), + "test" + ); + + freq.extensions_mut().insert(100usize); + assert_eq!(*freq.extensions().get::().unwrap(), 100usize); + + let (_, _, state) = freq.into_parts(); + assert_eq!(*state, 10); + } } diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 3d948ebd4..ce55912f7 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -205,6 +205,11 @@ impl TestBuffer { } } + /// Create new empty TestBuffer instance + pub fn empty() -> TestBuffer { + TestBuffer::new("") + } + /// Add extra data to read buffer. pub fn extend_read_buf>(&mut self, data: T) { self.read_buf.extend_from_slice(data.as_ref()) From 7801fcb9930b57e55cbd6b94401355636075ce3a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 10 Apr 2019 20:47:28 -0700 Subject: [PATCH 053/122] update migration --- CHANGES.md | 5 +++ MIGRATION.md | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/data.rs | 5 ++- 3 files changed, 120 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 607e9d4f5..162f410fd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +### Added + +* Added async io `TestBuffer` for testing. + + ## [1.0.0-alpha.4] - 2019-04-08 ### Added diff --git a/MIGRATION.md b/MIGRATION.md index 372d68930..21be95c93 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,5 +1,65 @@ ## 1.0 +* Resource registration. 1.0 version uses generalized resource +registration via `.service()` method. + + instead of + + ```rust + App.new().resource("/welcome", |r| r.f(welcome)) + ``` + + use App's or Scope's `.service()` method. `.service()` method accepts + object that implements `HttpServiceFactory` trait. By default + actix-web provides `Resource` and `Scope` services. + + ```rust + App.new().service( + web::resource("/welcome") + .route(web::get().to(welcome)) + .route(web::post().to(post_handler)) + ``` + +* Scope registration. + + instead of + + ```rust + let app = App::new().scope("/{project_id}", |scope| { + scope + .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) + .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) + .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) + }); + ``` + + use `.service()` for registration and `web::scope()` as scope object factory. + + ```rust + let app = App::new().service( + web::scope("/{project_id}") + .service(web::resource("/path1").to(|| HttpResponse::Ok())) + .service(web::resource("/path2").to(|| HttpResponse::Ok())) + .service(web::resource("/path3").to(|| HttpResponse::MethodNotAllowed())) + ); + ``` + +* `.f()`, `.a()` and `.h()` handler registration methods have been removed. +Use `.to()` for handlers and `.to_async()` for async handlers. Handler function +must use extractors. + + instead of + + ```rust + App.new().resource("/welcome", |r| r.f(welcome)) + ``` + + use App's `to()` or `to_async()` methods + + ```rust + App.new().service(web::resource("/welcome").to(welcome)) + ``` + * `State` is now `Data`. You register Data during the App initialization process and then access it from handlers either using a Data extractor or using HttpRequest's api. @@ -52,6 +112,58 @@ HttpRequest's api. .. simply omit AsyncResponder and the corresponding responder() finish method +* Middleware + + instead of + + ```rust + let app = App::new() + .middleware(middleware::Logger::default()) + ``` + + use `.wrap()` method + + ```rust + let app = App::new() + .wrap(middleware::Logger::default()) + .route("/index.html", web::get().to(index)); + ``` + +* `HttpRequest::body()`, `HttpRequest::urlencoded()`, `HttpRequest::json()`, `HttpRequest::multipart()` + method have been removed. Use `Bytes`, `String`, `Form`, `Json`, `Multipart` extractors instead. + + instead if + + ```rust + fn index(req: &HttpRequest) -> Responder { + req.body() + .and_then(|body| { + ... + }) + } + + ```rust + fn index(body: Bytes) -> Responder { + ... + } + ``` + +* StaticFiles and NamedFile has been move to separate create. + + instead of `use actix_web::fs::StaticFile` + + use `use actix_files::Files` + + instead of `use actix_web::fs::Namedfile` + + use `use actix_files::NamedFile` + +* Multipart has been move to separate create. + + instead of `use actix_web::multipart::Multipart` + + use `use actix_multipart::Multipart` + ## 0.7.15 diff --git a/src/data.rs b/src/data.rs index c697bac5e..d06eb6468 100644 --- a/src/data.rs +++ b/src/data.rs @@ -32,8 +32,9 @@ pub(crate) trait DataFactoryResult { /// an application instance. Http server constructs an application /// instance for each thread, thus application data must be constructed /// multiple times. If you want to share data between different -/// threads, a shared object should be used, e.g. `Arc`. Application -/// data does not need to be `Send` or `Sync`. +/// threads, a shareable object should be used, e.g. `Send + Sync`. Application +/// data does not need to be `Send` or `Sync`. Internally `Data` instance +/// uses `Arc`. /// /// If route data is not set for a handler, using `Data` extractor would /// cause *Internal Server Error* response. From 0eed9e525775d97ace7ed2eca49c29b41e15e898 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 10 Apr 2019 20:51:57 -0700 Subject: [PATCH 054/122] add more migration --- MIGRATION.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/MIGRATION.md b/MIGRATION.md index 21be95c93..1a8683cda 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -96,7 +96,7 @@ HttpRequest's api. ``` -* AsyncResponder is deprecated. +* AsyncResponder is removed. instead of @@ -142,6 +142,8 @@ HttpRequest's api. }) } + use + ```rust fn index(body: Bytes) -> Responder { ... @@ -164,6 +166,13 @@ HttpRequest's api. use `use actix_multipart::Multipart` +* Request/response compression/decompression is not enabled by default. + To enable use `App::enable_encoding()` method. + +* `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type + +* Actors support have been moved to `actix-web-actors` crate + ## 0.7.15 From 6420a2fe1f14b8fc6de12f9f3b19679dd11340e2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 10 Apr 2019 20:57:18 -0700 Subject: [PATCH 055/122] update client example --- examples/client.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/examples/client.rs b/examples/client.rs index c4df6f7d6..8a75fd306 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -1,7 +1,6 @@ use actix_http::Error; use actix_rt::System; -use bytes::BytesMut; -use futures::{future::lazy, Future, Stream}; +use futures::{future::lazy, Future}; fn main() -> Result<(), Error> { std::env::set_var("RUST_LOG", "actix_http=trace"); @@ -13,17 +12,14 @@ fn main() -> Result<(), Error> { .header("User-Agent", "Actix-web") .send() // <- Send http request .from_err() - .and_then(|response| { + .and_then(|mut response| { // <- server http response println!("Response: {:?}", response); // read response body response + .body() .from_err() - .fold(BytesMut::new(), move |mut acc, chunk| { - acc.extend_from_slice(&chunk); - Ok::<_, Error>(acc) - }) .map(|body| println!("Downloaded: {:?} bytes", body.len())) }) })) From d115b3b3edb0fdca11368f13277fd5b9023a91af Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 11 Apr 2019 14:00:32 -0700 Subject: [PATCH 056/122] ws verifyciation takes RequestHead; add SendError utility service --- actix-framed/src/lib.rs | 2 + actix-framed/src/service.rs | 112 ++++++++++++++++++++++++++++++ actix-framed/tests/test_server.rs | 2 +- actix-http/CHANGES.md | 2 + actix-http/Cargo.toml | 2 +- actix-http/src/h1/utils.rs | 38 +++++++--- actix-http/src/ws/mod.rs | 29 ++++---- actix-http/src/ws/service.rs | 52 -------------- actix-http/tests/test_ws.rs | 2 +- awc/tests/test_ws.rs | 82 +++++++++++----------- 10 files changed, 204 insertions(+), 119 deletions(-) create mode 100644 actix-framed/src/service.rs delete mode 100644 actix-http/src/ws/service.rs diff --git a/actix-framed/src/lib.rs b/actix-framed/src/lib.rs index a67b82e43..62b0cf1ec 100644 --- a/actix-framed/src/lib.rs +++ b/actix-framed/src/lib.rs @@ -2,6 +2,7 @@ mod app; mod helpers; mod request; mod route; +mod service; mod state; // re-export for convinience @@ -10,4 +11,5 @@ pub use actix_http::{http, Error, HttpMessage, Response, ResponseError}; pub use self::app::{FramedApp, FramedAppService}; pub use self::request::FramedRequest; pub use self::route::FramedRoute; +pub use self::service::{SendError, VerifyWebSockets}; pub use self::state::State; diff --git a/actix-framed/src/service.rs b/actix-framed/src/service.rs new file mode 100644 index 000000000..bc730074c --- /dev/null +++ b/actix-framed/src/service.rs @@ -0,0 +1,112 @@ +use std::marker::PhantomData; + +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_http::error::{Error, ResponseError}; +use actix_http::ws::{verify_handshake, HandshakeError}; +use actix_http::{h1, Request}; +use actix_service::{NewService, Service}; +use futures::future::{ok, Either, FutureResult}; +use futures::{Async, Future, IntoFuture, Poll}; + +/// Service that verifies incoming request if it is valid websocket +/// upgrade request. In case of error returns `HandshakeError` +pub struct VerifyWebSockets { + _t: PhantomData, +} + +impl Default for VerifyWebSockets { + fn default() -> Self { + VerifyWebSockets { _t: PhantomData } + } +} + +impl NewService for VerifyWebSockets { + type Request = (Request, Framed); + type Response = (Request, Framed); + type Error = (HandshakeError, Framed); + type InitError = (); + type Service = VerifyWebSockets; + type Future = FutureResult; + + fn new_service(&self, _: &()) -> Self::Future { + ok(VerifyWebSockets { _t: PhantomData }) + } +} + +impl Service for VerifyWebSockets { + type Request = (Request, Framed); + type Response = (Request, Framed); + type Error = (HandshakeError, Framed); + type Future = FutureResult; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, (req, framed): (Request, Framed)) -> Self::Future { + match verify_handshake(req.head()) { + Err(e) => Err((e, framed)).into_future(), + Ok(_) => Ok((req, framed)).into_future(), + } + } +} + +/// Send http/1 error response +pub struct SendError(PhantomData<(T, R, E)>); + +impl Default for SendError +where + T: AsyncRead + AsyncWrite, + E: ResponseError, +{ + fn default() -> Self { + SendError(PhantomData) + } +} + +impl NewService for SendError +where + T: AsyncRead + AsyncWrite + 'static, + R: 'static, + E: ResponseError + 'static, +{ + type Request = Result)>; + type Response = R; + type Error = Error; + type InitError = (); + type Service = SendError; + type Future = FutureResult; + + fn new_service(&self, _: &()) -> Self::Future { + ok(SendError(PhantomData)) + } +} + +impl Service for SendError +where + T: AsyncRead + AsyncWrite + 'static, + R: 'static, + E: ResponseError + 'static, +{ + type Request = Result)>; + type Response = R; + type Error = Error; + type Future = Either, Box>>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, req: Result)>) -> Self::Future { + match req { + Ok(r) => Either::A(ok(r)), + Err((e, framed)) => { + let res = e.render_response(); + let e = Error::from(e); + Either::B(Box::new( + h1::SendResponse::new(framed, res).then(move |_| Err(e)), + )) + } + } + } +} diff --git a/actix-framed/tests/test_server.rs b/actix-framed/tests/test_server.rs index 09d2c03cc..5b85f3128 100644 --- a/actix-framed/tests/test_server.rs +++ b/actix-framed/tests/test_server.rs @@ -12,7 +12,7 @@ fn ws_service( req: FramedRequest, ) -> impl Future { let (req, framed, _) = req.into_parts(); - let res = ws::handshake(&req).unwrap().message_body(()); + let res = ws::handshake(req.head()).unwrap().message_body(()); framed .send((res, body::BodySize::None).into()) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index f5988afa4..ae9bb81c4 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -12,6 +12,8 @@ * MessageBody::length() renamed to MessageBody::size() for consistency +* ws handshake verification functions take RequestHead instead of Request + ## [0.1.0-alpha.4] - 2019-04-08 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index a9ab320ea..b1aefdb1d 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -46,7 +46,7 @@ secure-cookies = ["ring"] [dependencies] actix-service = "0.3.6" actix-codec = "0.1.2" -actix-connect = "0.1.2" +actix-connect = "0.1.3" actix-utils = "0.3.5" actix-server-config = "0.1.0" actix-threadpool = "0.1.0" diff --git a/actix-http/src/h1/utils.rs b/actix-http/src/h1/utils.rs index fdc4cf0bc..c7b7ccec3 100644 --- a/actix-http/src/h1/utils.rs +++ b/actix-http/src/h1/utils.rs @@ -34,23 +34,35 @@ where B: MessageBody, { type Item = Framed; - type Error = Error; + type Error = (Error, Framed); fn poll(&mut self) -> Poll { loop { let mut body_ready = self.body.is_some(); - let framed = self.framed.as_mut().unwrap(); // send body if self.res.is_none() && self.body.is_some() { - while body_ready && self.body.is_some() && !framed.is_write_buf_full() { - match self.body.as_mut().unwrap().poll_next()? { + while body_ready + && self.body.is_some() + && !self.framed.as_ref().unwrap().is_write_buf_full() + { + match self + .body + .as_mut() + .unwrap() + .poll_next() + .map_err(|e| (e, self.framed.take().unwrap()))? + { Async::Ready(item) => { // body is done if item.is_none() { let _ = self.body.take(); } - framed.force_send(Message::Chunk(item))?; + self.framed + .as_mut() + .unwrap() + .force_send(Message::Chunk(item)) + .map_err(|e| (e.into(), self.framed.take().unwrap()))?; } Async::NotReady => body_ready = false, } @@ -58,8 +70,14 @@ where } // flush write buffer - if !framed.is_write_buf_empty() { - match framed.poll_complete()? { + if !self.framed.as_ref().unwrap().is_write_buf_empty() { + match self + .framed + .as_mut() + .unwrap() + .poll_complete() + .map_err(|e| (e.into(), self.framed.take().unwrap()))? + { Async::Ready(_) => { if body_ready { continue; @@ -73,7 +91,11 @@ where // send response if let Some(res) = self.res.take() { - framed.force_send(res)?; + self.framed + .as_mut() + .unwrap() + .force_send(res) + .map_err(|e| (e.into(), self.framed.take().unwrap()))?; continue; } diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index 065c34d93..891d5110d 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -9,21 +9,18 @@ use derive_more::{Display, From}; use http::{header, Method, StatusCode}; use crate::error::ResponseError; -use crate::httpmessage::HttpMessage; -use crate::request::Request; +use crate::message::RequestHead; use crate::response::{Response, ResponseBuilder}; mod codec; mod frame; mod mask; mod proto; -mod service; mod transport; pub use self::codec::{Codec, Frame, Message}; pub use self::frame::Parser; pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode}; -pub use self::service::VerifyWebSockets; pub use self::transport::Transport; /// Websocket protocol errors @@ -112,7 +109,7 @@ impl ResponseError for HandshakeError { // /// `protocols` is a sequence of known protocols. On successful handshake, // /// the returned response headers contain the first protocol in this list // /// which the server also knows. -pub fn handshake(req: &Request) -> Result { +pub fn handshake(req: &RequestHead) -> Result { verify_handshake(req)?; Ok(handshake_response(req)) } @@ -121,9 +118,9 @@ pub fn handshake(req: &Request) -> Result { // /// `protocols` is a sequence of known protocols. On successful handshake, // /// the returned response headers contain the first protocol in this list // /// which the server also knows. -pub fn verify_handshake(req: &Request) -> Result<(), HandshakeError> { +pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> { // WebSocket accepts only GET - if *req.method() != Method::GET { + if req.method != Method::GET { return Err(HandshakeError::GetMethodRequired); } @@ -171,7 +168,7 @@ pub fn verify_handshake(req: &Request) -> Result<(), HandshakeError> { /// Create websocket's handshake response /// /// This function returns handshake `Response`, ready to send to peer. -pub fn handshake_response(req: &Request) -> ResponseBuilder { +pub fn handshake_response(req: &RequestHead) -> ResponseBuilder { let key = { let key = req.headers().get(header::SEC_WEBSOCKET_KEY).unwrap(); proto::hash_key(key.as_ref()) @@ -195,13 +192,13 @@ mod tests { let req = TestRequest::default().method(Method::POST).finish(); assert_eq!( HandshakeError::GetMethodRequired, - verify_handshake(&req).err().unwrap() + verify_handshake(req.head()).err().unwrap() ); let req = TestRequest::default().finish(); assert_eq!( HandshakeError::NoWebsocketUpgrade, - verify_handshake(&req).err().unwrap() + verify_handshake(req.head()).err().unwrap() ); let req = TestRequest::default() @@ -209,7 +206,7 @@ mod tests { .finish(); assert_eq!( HandshakeError::NoWebsocketUpgrade, - verify_handshake(&req).err().unwrap() + verify_handshake(req.head()).err().unwrap() ); let req = TestRequest::default() @@ -220,7 +217,7 @@ mod tests { .finish(); assert_eq!( HandshakeError::NoConnectionUpgrade, - verify_handshake(&req).err().unwrap() + verify_handshake(req.head()).err().unwrap() ); let req = TestRequest::default() @@ -235,7 +232,7 @@ mod tests { .finish(); assert_eq!( HandshakeError::NoVersionHeader, - verify_handshake(&req).err().unwrap() + verify_handshake(req.head()).err().unwrap() ); let req = TestRequest::default() @@ -254,7 +251,7 @@ mod tests { .finish(); assert_eq!( HandshakeError::UnsupportedVersion, - verify_handshake(&req).err().unwrap() + verify_handshake(req.head()).err().unwrap() ); let req = TestRequest::default() @@ -273,7 +270,7 @@ mod tests { .finish(); assert_eq!( HandshakeError::BadWebsocketKey, - verify_handshake(&req).err().unwrap() + verify_handshake(req.head()).err().unwrap() ); let req = TestRequest::default() @@ -296,7 +293,7 @@ mod tests { .finish(); assert_eq!( StatusCode::SWITCHING_PROTOCOLS, - handshake_response(&req).finish().status() + handshake_response(req.head()).finish().status() ); } diff --git a/actix-http/src/ws/service.rs b/actix-http/src/ws/service.rs deleted file mode 100644 index f3b066053..000000000 --- a/actix-http/src/ws/service.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::marker::PhantomData; - -use actix_codec::Framed; -use actix_service::{NewService, Service}; -use futures::future::{ok, FutureResult}; -use futures::{Async, IntoFuture, Poll}; - -use crate::h1::Codec; -use crate::request::Request; - -use super::{verify_handshake, HandshakeError}; - -pub struct VerifyWebSockets { - _t: PhantomData, -} - -impl Default for VerifyWebSockets { - fn default() -> Self { - VerifyWebSockets { _t: PhantomData } - } -} - -impl NewService for VerifyWebSockets { - type Request = (Request, Framed); - type Response = (Request, Framed); - type Error = (HandshakeError, Framed); - type InitError = (); - type Service = VerifyWebSockets; - type Future = FutureResult; - - fn new_service(&self, _: &()) -> Self::Future { - ok(VerifyWebSockets { _t: PhantomData }) - } -} - -impl Service for VerifyWebSockets { - type Request = (Request, Framed); - type Response = (Request, Framed); - type Error = (HandshakeError, Framed); - type Future = FutureResult; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, (req, framed): (Request, Framed)) -> Self::Future { - match verify_handshake(&req) { - Err(e) => Err((e, framed)).into_future(), - Ok(_) => Ok((req, framed)).into_future(), - } - } -} diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index b6be748bd..65a4d094d 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -9,7 +9,7 @@ use futures::{Future, Sink, Stream}; fn ws_service( (req, framed): (Request, Framed), ) -> impl Future { - let res = ws::handshake(&req).unwrap().message_body(()); + let res = ws::handshake(req.head()).unwrap().message_body(()); framed .send((res, body::BodySize::None).into()) diff --git a/awc/tests/test_ws.rs b/awc/tests/test_ws.rs index 04a6a110a..4fc9f4bdc 100644 --- a/awc/tests/test_ws.rs +++ b/awc/tests/test_ws.rs @@ -11,7 +11,7 @@ use futures::future::{ok, Either}; use futures::{Future, Sink, Stream}; use tokio_tcp::TcpStream; -use actix_http::{body::BodySize, h1, ws, ResponseError, ServiceConfig}; +use actix_http::{body::BodySize, h1, ws, Request, ResponseError, ServiceConfig}; fn ws_service(req: ws::Frame) -> impl Future { match req { @@ -40,47 +40,49 @@ fn test_simple() { fn_service(|io: Io| Ok(io.into_parts().0)) .and_then(IntoFramed::new(|| h1::Codec::new(ServiceConfig::default()))) .and_then(TakeItem::new().map_err(|_| ())) - .and_then(|(req, framed): (_, Framed<_, _>)| { - // validate request - if let Some(h1::Message::Item(req)) = req { - match ws::verify_handshake(&req) { - Err(e) => { - // validation failed - let res = e.error_response(); - Either::A( - framed - .send(h1::Message::Item(( - res.drop_body(), - BodySize::Empty, - ))) - .map_err(|_| ()) - .map(|_| ()), - ) - } - Ok(_) => { - let res = ws::handshake_response(&req).finish(); - Either::B( - // send handshake response - framed - .send(h1::Message::Item(( - res.drop_body(), - BodySize::None, - ))) - .map_err(|_| ()) - .and_then(|framed| { - // start websocket service - let framed = - framed.into_framed(ws::Codec::new()); - ws::Transport::with(framed, ws_service) - .map_err(|_| ()) - }), - ) + .and_then( + |(req, framed): (Option>, Framed<_, _>)| { + // validate request + if let Some(h1::Message::Item(req)) = req { + match ws::verify_handshake(req.head()) { + Err(e) => { + // validation failed + let res = e.error_response(); + Either::A( + framed + .send(h1::Message::Item(( + res.drop_body(), + BodySize::Empty, + ))) + .map_err(|_| ()) + .map(|_| ()), + ) + } + Ok(_) => { + let res = ws::handshake_response(req.head()).finish(); + Either::B( + // send handshake response + framed + .send(h1::Message::Item(( + res.drop_body(), + BodySize::None, + ))) + .map_err(|_| ()) + .and_then(|framed| { + // start websocket service + let framed = + framed.into_framed(ws::Codec::new()); + ws::Transport::with(framed, ws_service) + .map_err(|_| ()) + }), + ) + } } + } else { + panic!() } - } else { - panic!() - } - }) + }, + ) }); // client service From d86567fbdc533b6f9375f23a0b6476da23574d7b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 11 Apr 2019 14:18:58 -0700 Subject: [PATCH 057/122] revert SendResponse::Error type --- actix-framed/src/service.rs | 135 ++++++++++++++++++++++++++++++++---- actix-http/src/h1/utils.rs | 38 +++------- 2 files changed, 130 insertions(+), 43 deletions(-) diff --git a/actix-framed/src/service.rs b/actix-framed/src/service.rs index bc730074c..5fb74fa1f 100644 --- a/actix-framed/src/service.rs +++ b/actix-framed/src/service.rs @@ -1,12 +1,14 @@ use std::marker::PhantomData; use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_http::body::{BodySize, MessageBody, ResponseBody}; use actix_http::error::{Error, ResponseError}; +use actix_http::h1::{Codec, Message}; use actix_http::ws::{verify_handshake, HandshakeError}; -use actix_http::{h1, Request}; +use actix_http::{Request, Response}; use actix_service::{NewService, Service}; use futures::future::{ok, Either, FutureResult}; -use futures::{Async, Future, IntoFuture, Poll}; +use futures::{Async, Future, IntoFuture, Poll, Sink}; /// Service that verifies incoming request if it is valid websocket /// upgrade request. In case of error returns `HandshakeError` @@ -21,9 +23,9 @@ impl Default for VerifyWebSockets { } impl NewService for VerifyWebSockets { - type Request = (Request, Framed); - type Response = (Request, Framed); - type Error = (HandshakeError, Framed); + type Request = (Request, Framed); + type Response = (Request, Framed); + type Error = (HandshakeError, Framed); type InitError = (); type Service = VerifyWebSockets; type Future = FutureResult; @@ -34,16 +36,16 @@ impl NewService for VerifyWebSockets { } impl Service for VerifyWebSockets { - type Request = (Request, Framed); - type Response = (Request, Framed); - type Error = (HandshakeError, Framed); + type Request = (Request, Framed); + type Response = (Request, Framed); + type Error = (HandshakeError, Framed); type Future = FutureResult; fn poll_ready(&mut self) -> Poll<(), Self::Error> { Ok(Async::Ready(())) } - fn call(&mut self, (req, framed): (Request, Framed)) -> Self::Future { + fn call(&mut self, (req, framed): (Request, Framed)) -> Self::Future { match verify_handshake(req.head()) { Err(e) => Err((e, framed)).into_future(), Ok(_) => Ok((req, framed)).into_future(), @@ -70,7 +72,7 @@ where R: 'static, E: ResponseError + 'static, { - type Request = Result)>; + type Request = Result)>; type Response = R; type Error = Error; type InitError = (); @@ -88,7 +90,7 @@ where R: 'static, E: ResponseError + 'static, { - type Request = Result)>; + type Request = Result)>; type Response = R; type Error = Error; type Future = Either, Box>>; @@ -97,16 +99,123 @@ where Ok(Async::Ready(())) } - fn call(&mut self, req: Result)>) -> Self::Future { + fn call(&mut self, req: Result)>) -> Self::Future { match req { Ok(r) => Either::A(ok(r)), Err((e, framed)) => { let res = e.render_response(); let e = Error::from(e); Either::B(Box::new( - h1::SendResponse::new(framed, res).then(move |_| Err(e)), + SendResponse::new(framed, res).then(move |_| Err(e)), )) } } } } + +/// Send http/1 response +pub struct SendResponse { + res: Option, BodySize)>>, + body: Option>, + framed: Option>, +} + +impl SendResponse +where + B: MessageBody, +{ + pub fn new(framed: Framed, response: Response) -> Self { + let (res, body) = response.into_parts(); + + SendResponse { + res: Some((res, body.size()).into()), + body: Some(body), + framed: Some(framed), + } + } +} + +impl Future for SendResponse +where + T: AsyncRead + AsyncWrite, + B: MessageBody, +{ + type Item = Framed; + type Error = (Error, Framed); + + fn poll(&mut self) -> Poll { + loop { + let mut body_ready = self.body.is_some(); + + // send body + if self.res.is_none() && self.body.is_some() { + while body_ready + && self.body.is_some() + && !self.framed.as_ref().unwrap().is_write_buf_full() + { + match self + .body + .as_mut() + .unwrap() + .poll_next() + .map_err(|e| (e, self.framed.take().unwrap()))? + { + Async::Ready(item) => { + // body is done + if item.is_none() { + let _ = self.body.take(); + } + self.framed + .as_mut() + .unwrap() + .force_send(Message::Chunk(item)) + .map_err(|e| (e.into(), self.framed.take().unwrap()))?; + } + Async::NotReady => body_ready = false, + } + } + } + + // flush write buffer + if !self.framed.as_ref().unwrap().is_write_buf_empty() { + match self + .framed + .as_mut() + .unwrap() + .poll_complete() + .map_err(|e| (e.into(), self.framed.take().unwrap()))? + { + Async::Ready(_) => { + if body_ready { + continue; + } else { + return Ok(Async::NotReady); + } + } + Async::NotReady => return Ok(Async::NotReady), + } + } + + // send response + if let Some(res) = self.res.take() { + self.framed + .as_mut() + .unwrap() + .force_send(res) + .map_err(|e| (e.into(), self.framed.take().unwrap()))?; + continue; + } + + if self.body.is_some() { + if body_ready { + continue; + } else { + return Ok(Async::NotReady); + } + } else { + break; + } + } + Ok(Async::Ready(self.framed.take().unwrap())) + } +} diff --git a/actix-http/src/h1/utils.rs b/actix-http/src/h1/utils.rs index c7b7ccec3..fdc4cf0bc 100644 --- a/actix-http/src/h1/utils.rs +++ b/actix-http/src/h1/utils.rs @@ -34,35 +34,23 @@ where B: MessageBody, { type Item = Framed; - type Error = (Error, Framed); + type Error = Error; fn poll(&mut self) -> Poll { loop { let mut body_ready = self.body.is_some(); + let framed = self.framed.as_mut().unwrap(); // send body if self.res.is_none() && self.body.is_some() { - while body_ready - && self.body.is_some() - && !self.framed.as_ref().unwrap().is_write_buf_full() - { - match self - .body - .as_mut() - .unwrap() - .poll_next() - .map_err(|e| (e, self.framed.take().unwrap()))? - { + while body_ready && self.body.is_some() && !framed.is_write_buf_full() { + match self.body.as_mut().unwrap().poll_next()? { Async::Ready(item) => { // body is done if item.is_none() { let _ = self.body.take(); } - self.framed - .as_mut() - .unwrap() - .force_send(Message::Chunk(item)) - .map_err(|e| (e.into(), self.framed.take().unwrap()))?; + framed.force_send(Message::Chunk(item))?; } Async::NotReady => body_ready = false, } @@ -70,14 +58,8 @@ where } // flush write buffer - if !self.framed.as_ref().unwrap().is_write_buf_empty() { - match self - .framed - .as_mut() - .unwrap() - .poll_complete() - .map_err(|e| (e.into(), self.framed.take().unwrap()))? - { + if !framed.is_write_buf_empty() { + match framed.poll_complete()? { Async::Ready(_) => { if body_ready { continue; @@ -91,11 +73,7 @@ where // send response if let Some(res) = self.res.take() { - self.framed - .as_mut() - .unwrap() - .force_send(res) - .map_err(|e| (e.into(), self.framed.take().unwrap()))?; + framed.force_send(res)?; continue; } From 94d7a7f8733b690612a4844d1c06881b87b45762 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 11 Apr 2019 15:12:23 -0700 Subject: [PATCH 058/122] custom future for SendError service --- actix-framed/src/app.rs | 4 +- actix-framed/src/service.rs | 140 +++++++++--------------------------- actix-http/src/error.rs | 4 ++ 3 files changed, 39 insertions(+), 109 deletions(-) diff --git a/actix-framed/src/app.rs b/actix-framed/src/app.rs index cce618bb9..20bc2f771 100644 --- a/actix-framed/src/app.rs +++ b/actix-framed/src/app.rs @@ -88,7 +88,7 @@ pub struct FramedAppFactory { services: Rc>)>>, } -impl NewService for FramedAppFactory +impl NewService for FramedAppFactory where T: AsyncRead + AsyncWrite + 'static, S: 'static, @@ -100,7 +100,7 @@ where type Service = CloneableService>; type Future = CreateService; - fn new_service(&self, _: &()) -> Self::Future { + fn new_service(&self, _: &C) -> Self::Future { CreateService { fut: self .services diff --git a/actix-framed/src/service.rs b/actix-framed/src/service.rs index 5fb74fa1f..6e5c7a543 100644 --- a/actix-framed/src/service.rs +++ b/actix-framed/src/service.rs @@ -1,8 +1,8 @@ use std::marker::PhantomData; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_http::body::{BodySize, MessageBody, ResponseBody}; -use actix_http::error::{Error, ResponseError}; +use actix_http::body::BodySize; +use actix_http::error::ResponseError; use actix_http::h1::{Codec, Message}; use actix_http::ws::{verify_handshake, HandshakeError}; use actix_http::{Request, Response}; @@ -22,7 +22,7 @@ impl Default for VerifyWebSockets { } } -impl NewService for VerifyWebSockets { +impl NewService for VerifyWebSockets { type Request = (Request, Framed); type Response = (Request, Framed); type Error = (HandshakeError, Framed); @@ -30,7 +30,7 @@ impl NewService for VerifyWebSockets { type Service = VerifyWebSockets; type Future = FutureResult; - fn new_service(&self, _: &()) -> Self::Future { + fn new_service(&self, _: &C) -> Self::Future { ok(VerifyWebSockets { _t: PhantomData }) } } @@ -66,7 +66,7 @@ where } } -impl NewService for SendError +impl NewService for SendError where T: AsyncRead + AsyncWrite + 'static, R: 'static, @@ -74,12 +74,12 @@ where { type Request = Result)>; type Response = R; - type Error = Error; + type Error = (E, Framed); type InitError = (); type Service = SendError; type Future = FutureResult; - fn new_service(&self, _: &()) -> Self::Future { + fn new_service(&self, _: &C) -> Self::Future { ok(SendError(PhantomData)) } } @@ -92,8 +92,8 @@ where { type Request = Result)>; type Response = R; - type Error = Error; - type Future = Either, Box>>; + type Error = (E, Framed); + type Future = Either)>, SendErrorFut>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { Ok(Async::Ready(())) @@ -103,119 +103,45 @@ where match req { Ok(r) => Either::A(ok(r)), Err((e, framed)) => { - let res = e.render_response(); - let e = Error::from(e); - Either::B(Box::new( - SendResponse::new(framed, res).then(move |_| Err(e)), - )) + let res = e.error_response().drop_body(); + Either::B(SendErrorFut { + framed: Some(framed), + res: Some((res, BodySize::Empty).into()), + err: Some(e), + _t: PhantomData, + }) } } } } -/// Send http/1 response -pub struct SendResponse { +pub struct SendErrorFut { res: Option, BodySize)>>, - body: Option>, framed: Option>, + err: Option, + _t: PhantomData, } -impl SendResponse -where - B: MessageBody, -{ - pub fn new(framed: Framed, response: Response) -> Self { - let (res, body) = response.into_parts(); - - SendResponse { - res: Some((res, body.size()).into()), - body: Some(body), - framed: Some(framed), - } - } -} - -impl Future for SendResponse +impl Future for SendErrorFut where + E: ResponseError, T: AsyncRead + AsyncWrite, - B: MessageBody, { - type Item = Framed; - type Error = (Error, Framed); + type Item = R; + type Error = (E, Framed); fn poll(&mut self) -> Poll { - loop { - let mut body_ready = self.body.is_some(); - - // send body - if self.res.is_none() && self.body.is_some() { - while body_ready - && self.body.is_some() - && !self.framed.as_ref().unwrap().is_write_buf_full() - { - match self - .body - .as_mut() - .unwrap() - .poll_next() - .map_err(|e| (e, self.framed.take().unwrap()))? - { - Async::Ready(item) => { - // body is done - if item.is_none() { - let _ = self.body.take(); - } - self.framed - .as_mut() - .unwrap() - .force_send(Message::Chunk(item)) - .map_err(|e| (e.into(), self.framed.take().unwrap()))?; - } - Async::NotReady => body_ready = false, - } - } - } - - // flush write buffer - if !self.framed.as_ref().unwrap().is_write_buf_empty() { - match self - .framed - .as_mut() - .unwrap() - .poll_complete() - .map_err(|e| (e.into(), self.framed.take().unwrap()))? - { - Async::Ready(_) => { - if body_ready { - continue; - } else { - return Ok(Async::NotReady); - } - } - Async::NotReady => return Ok(Async::NotReady), - } - } - - // send response - if let Some(res) = self.res.take() { - self.framed - .as_mut() - .unwrap() - .force_send(res) - .map_err(|e| (e.into(), self.framed.take().unwrap()))?; - continue; - } - - if self.body.is_some() { - if body_ready { - continue; - } else { - return Ok(Async::NotReady); - } - } else { - break; + if let Some(res) = self.res.take() { + if self.framed.as_mut().unwrap().force_send(res).is_err() { + return Err((self.err.take().unwrap(), self.framed.take().unwrap())); } } - Ok(Async::Ready(self.framed.take().unwrap())) + match self.framed.as_mut().unwrap().poll_complete() { + Ok(Async::Ready(_)) => { + Err((self.err.take().unwrap(), self.framed.take().unwrap())) + } + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(_) => Err((self.err.take().unwrap(), self.framed.take().unwrap())), + } } } diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 92a046846..1768c9543 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -153,6 +153,10 @@ impl ResponseError for TimerError {} /// `InternalServerError` for `SslError` impl ResponseError for openssl::ssl::Error {} +#[cfg(feature = "ssl")] +/// `InternalServerError` for `SslError` +impl ResponseError for openssl::ssl::HandshakeError {} + /// Return `BAD_REQUEST` for `de::value::Error` impl ResponseError for DeError { fn error_response(&self) -> Response { From 67c34a59378745d38ef9e23763c7096da2203443 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 11 Apr 2019 16:01:54 -0700 Subject: [PATCH 059/122] Add Debug impl for BoxedSocket --- awc/CHANGES.md | 4 ++++ awc/src/connect.rs | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 767761cef..4eeed7a1f 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -6,6 +6,10 @@ * Do not set any default headers +### Added + +* Add Debug impl for BoxedSocket + ## [0.1.0-alpha.4] - 2019-04-08 diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 77cd1fbff..bfc9da05f 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -1,4 +1,4 @@ -use std::io; +use std::{fmt, io}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::body::Body; @@ -102,6 +102,12 @@ impl AsyncSocket for Socket { pub struct BoxedSocket(Box); +impl fmt::Debug for BoxedSocket { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "BoxedSocket") + } +} + impl io::Read for BoxedSocket { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.as_read_mut().read(buf) From 5cfba5ff165bf5452e36afbbd49725943bcc4c60 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 12 Apr 2019 11:15:58 -0700 Subject: [PATCH 060/122] add FramedRequest builder for testing --- actix-framed/src/lib.rs | 1 + actix-framed/src/request.rs | 12 ++- actix-framed/src/test.rs | 133 ++++++++++++++++++++++++++++++ actix-framed/tests/test_server.rs | 55 +++++++++++- src/test.rs | 35 ++------ test-server/src/lib.rs | 36 ++++++++ 6 files changed, 240 insertions(+), 32 deletions(-) create mode 100644 actix-framed/src/test.rs diff --git a/actix-framed/src/lib.rs b/actix-framed/src/lib.rs index 62b0cf1ec..c6405e20b 100644 --- a/actix-framed/src/lib.rs +++ b/actix-framed/src/lib.rs @@ -4,6 +4,7 @@ mod request; mod route; mod service; mod state; +pub mod test; // re-export for convinience pub use actix_http::{http, Error, HttpMessage, Response, ResponseError}; diff --git a/actix-framed/src/request.rs b/actix-framed/src/request.rs index eab28a9ef..bdcdd7026 100644 --- a/actix-framed/src/request.rs +++ b/actix-framed/src/request.rs @@ -123,6 +123,7 @@ impl FramedRequest { #[cfg(test)] mod tests { + use actix_http::http::{HeaderName, HeaderValue, HttpTryFrom}; use actix_http::test::{TestBuffer, TestRequest}; use super::*; @@ -136,7 +137,7 @@ mod tests { .finish(); let path = Path::new(Url::new(req.uri().clone())); - let freq = FramedRequest::new(req, framed, path, State::new(10u8)); + let mut freq = FramedRequest::new(req, framed, path, State::new(10u8)); assert_eq!(*freq.state(), 10); assert_eq!(freq.version(), Version::HTTP_11); assert_eq!(freq.method(), Method::GET); @@ -151,6 +152,15 @@ mod tests { "test" ); + freq.head_mut().headers.insert( + HeaderName::try_from("x-hdr").unwrap(), + HeaderValue::from_static("test"), + ); + assert_eq!( + freq.headers().get("x-hdr").unwrap().to_str().unwrap(), + "test" + ); + freq.extensions_mut().insert(100usize); assert_eq!(*freq.extensions().get::().unwrap(), 100usize); diff --git a/actix-framed/src/test.rs b/actix-framed/src/test.rs new file mode 100644 index 000000000..c890662ef --- /dev/null +++ b/actix-framed/src/test.rs @@ -0,0 +1,133 @@ +//! Various helpers for Actix applications to use during testing. +use actix_codec::Framed; +use actix_http::h1::Codec; +use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; +use actix_http::http::{HttpTryFrom, Method, Uri, Version}; +use actix_http::test::{TestBuffer, TestRequest as HttpTestRequest}; +use actix_router::{Path, Url}; + +use crate::{FramedRequest, State}; + +/// Test `Request` builder. +pub struct TestRequest { + req: HttpTestRequest, + path: Path, +} + +impl Default for TestRequest { + fn default() -> TestRequest { + TestRequest { + req: HttpTestRequest::default(), + path: Path::new(Url::new(Uri::default())), + } + } +} + +#[allow(clippy::wrong_self_convention)] +impl TestRequest { + /// Create TestRequest and set request uri + pub fn with_uri(path: &str) -> TestRequest { + Self::get().uri(path) + } + + /// Create TestRequest and set header + pub fn with_hdr(hdr: H) -> TestRequest { + Self::default().set(hdr) + } + + /// Create TestRequest and set header + pub fn with_header(key: K, value: V) -> TestRequest + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + Self::default().header(key, value) + } + + /// Create TestRequest and set method to `Method::GET` + pub fn get() -> TestRequest { + Self::default().method(Method::GET) + } + + /// Create TestRequest and set method to `Method::POST` + pub fn post() -> TestRequest { + Self::default().method(Method::POST) + } + + /// Set HTTP version of this request + pub fn version(mut self, ver: Version) -> Self { + self.req.version(ver); + self + } + + /// Set HTTP method of this request + pub fn method(mut self, meth: Method) -> Self { + self.req.method(meth); + self + } + + /// Set HTTP Uri of this request + pub fn uri(mut self, path: &str) -> Self { + self.req.uri(path); + self + } + + /// Set a header + pub fn set(mut self, hdr: H) -> Self { + self.req.set(hdr); + self + } + + /// Set a header + pub fn header(mut self, key: K, value: V) -> Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + self.req.header(key, value); + self + } + + /// Set request path pattern parameter + pub fn param(mut self, name: &'static str, value: &'static str) -> Self { + self.path.add_static(name, value); + self + } + + /// Complete request creation and generate `Request` instance + pub fn finish(self) -> FramedRequest { + self.finish_with_state(()) + } + + /// Complete request creation and generate `Request` instance + pub fn finish_with_state(mut self, state: S) -> FramedRequest { + let req = self.req.finish(); + self.path.get_mut().update(req.uri()); + let framed = Framed::new(TestBuffer::empty(), Codec::default()); + FramedRequest::new(req, framed, self.path, State::new(state)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test() { + let req = TestRequest::with_uri("/index.html") + .header("x-test", "test") + .param("test", "123") + .finish(); + + assert_eq!(*req.state(), ()); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(req.method(), Method::GET); + assert_eq!(req.path(), "/index.html"); + assert_eq!(req.query_string(), ""); + assert_eq!( + req.headers().get("x-test").unwrap().to_str().unwrap(), + "test" + ); + assert_eq!(&req.match_info()["test"], "123"); + } +} diff --git a/actix-framed/tests/test_server.rs b/actix-framed/tests/test_server.rs index 5b85f3128..964403e15 100644 --- a/actix-framed/tests/test_server.rs +++ b/actix-framed/tests/test_server.rs @@ -1,12 +1,13 @@ use actix_codec::{AsyncRead, AsyncWrite}; use actix_http::{body, ws, Error, HttpService, Response}; use actix_http_test::TestServer; +use actix_service::{IntoNewService, NewService}; use actix_utils::framed::FramedTransport; use bytes::{Bytes, BytesMut}; use futures::future::{self, ok}; use futures::{Future, Sink, Stream}; -use actix_framed::{FramedApp, FramedRequest, FramedRoute}; +use actix_framed::{FramedApp, FramedRequest, FramedRoute, SendError, VerifyWebSockets}; fn ws_service( req: FramedRequest, @@ -81,3 +82,55 @@ fn test_simple() { Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into()))) ); } + +#[test] +fn test_service() { + let mut srv = TestServer::new(|| { + actix_http::h1::OneRequest::new().map_err(|_| ()).and_then( + VerifyWebSockets::default() + .then(SendError::default()) + .map_err(|_| ()) + .and_then( + FramedApp::new() + .service(FramedRoute::get("/index.html").to(ws_service)) + .into_new_service() + .map_err(|_| ()), + ), + ) + }); + + assert!(srv.ws_at("/test").is_err()); + + // client service + let framed = srv.ws_at("/index.html").unwrap(); + let framed = srv + .block_on(framed.send(ws::Message::Text("text".to_string()))) + .unwrap(); + let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text"))))); + + let framed = srv + .block_on(framed.send(ws::Message::Binary("text".into()))) + .unwrap(); + let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!( + item, + Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into()))) + ); + + let framed = srv + .block_on(framed.send(ws::Message::Ping("text".into()))) + .unwrap(); + let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into()))); + + let framed = srv + .block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))) + .unwrap(); + + let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!( + item, + Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into()))) + ); +} diff --git a/src/test.rs b/src/test.rs index 5b44d1c79..affd80bb7 100644 --- a/src/test.rs +++ b/src/test.rs @@ -201,22 +201,12 @@ impl Default for TestRequest { impl TestRequest { /// Create TestRequest and set request uri pub fn with_uri(path: &str) -> TestRequest { - TestRequest { - req: HttpTestRequest::default().uri(path).take(), - rmap: ResourceMap::new(ResourceDef::new("")), - config: AppConfigInner::default(), - route_data: Extensions::new(), - } + TestRequest::default().uri(path) } /// Create TestRequest and set header pub fn with_hdr(hdr: H) -> TestRequest { - TestRequest { - req: HttpTestRequest::default().set(hdr).take(), - config: AppConfigInner::default(), - rmap: ResourceMap::new(ResourceDef::new("")), - route_data: Extensions::new(), - } + TestRequest::default().set(hdr) } /// Create TestRequest and set header @@ -225,32 +215,17 @@ impl TestRequest { HeaderName: HttpTryFrom, V: IntoHeaderValue, { - TestRequest { - req: HttpTestRequest::default().header(key, value).take(), - config: AppConfigInner::default(), - rmap: ResourceMap::new(ResourceDef::new("")), - route_data: Extensions::new(), - } + TestRequest::default().header(key, value) } /// Create TestRequest and set method to `Method::GET` pub fn get() -> TestRequest { - TestRequest { - req: HttpTestRequest::default().method(Method::GET).take(), - config: AppConfigInner::default(), - rmap: ResourceMap::new(ResourceDef::new("")), - route_data: Extensions::new(), - } + TestRequest::default().method(Method::GET) } /// Create TestRequest and set method to `Method::POST` pub fn post() -> TestRequest { - TestRequest { - req: HttpTestRequest::default().method(Method::POST).take(), - config: AppConfigInner::default(), - rmap: ResourceMap::new(ResourceDef::new("")), - route_data: Extensions::new(), - } + TestRequest::default().method(Method::POST) } /// Set HTTP version of this request diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index d83432df9..37abe1292 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -1,4 +1,5 @@ //! Various helpers for Actix applications to use during testing. +use std::cell::RefCell; use std::sync::mpsc; use std::{net, thread, time}; @@ -12,6 +13,41 @@ use futures::{Future, Stream}; use http::Method; use net2::TcpBuilder; +thread_local! { + static RT: RefCell = { + RefCell::new(Runtime::new().unwrap()) + }; +} + +/// Runs the provided future, blocking the current thread until the future +/// completes. +/// +/// This function can be used to synchronously block the current thread +/// until the provided `future` has resolved either successfully or with an +/// error. The result of the future is then returned from this function +/// call. +/// +/// Note that this function is intended to be used only for testing purpose. +/// This function panics on nested call. +pub fn block_on(f: F) -> Result +where + F: Future, +{ + RT.with(move |rt| rt.borrow_mut().block_on(f)) +} + +/// Runs the provided function, with runtime enabled. +/// +/// Note that this function is intended to be used only for testing purpose. +/// This function panics on nested call. +pub fn run_on(f: F) -> R +where + F: Fn() -> R, +{ + RT.with(move |rt| rt.borrow_mut().block_on(lazy(|| Ok::<_, ()>(f())))) + .unwrap() +} + /// The `TestServer` type. /// /// `TestServer` is very simple test server that simplify process of writing From 3fb7343e73240ae17e98ee249c93635169295b63 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 12 Apr 2019 11:22:18 -0700 Subject: [PATCH 061/122] provide during test request construction --- actix-framed/src/test.rs | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/actix-framed/src/test.rs b/actix-framed/src/test.rs index c890662ef..34a157279 100644 --- a/actix-framed/src/test.rs +++ b/actix-framed/src/test.rs @@ -9,34 +9,35 @@ use actix_router::{Path, Url}; use crate::{FramedRequest, State}; /// Test `Request` builder. -pub struct TestRequest { +pub struct TestRequest { req: HttpTestRequest, path: Path, + state: State, } -impl Default for TestRequest { +impl Default for TestRequest<()> { fn default() -> TestRequest { TestRequest { req: HttpTestRequest::default(), path: Path::new(Url::new(Uri::default())), + state: State::new(()), } } } -#[allow(clippy::wrong_self_convention)] -impl TestRequest { +impl TestRequest<()> { /// Create TestRequest and set request uri - pub fn with_uri(path: &str) -> TestRequest { + pub fn with_uri(path: &str) -> Self { Self::get().uri(path) } /// Create TestRequest and set header - pub fn with_hdr(hdr: H) -> TestRequest { + pub fn with_hdr(hdr: H) -> Self { Self::default().set(hdr) } /// Create TestRequest and set header - pub fn with_header(key: K, value: V) -> TestRequest + pub fn with_header(key: K, value: V) -> Self where HeaderName: HttpTryFrom, V: IntoHeaderValue, @@ -45,14 +46,26 @@ impl TestRequest { } /// Create TestRequest and set method to `Method::GET` - pub fn get() -> TestRequest { + pub fn get() -> Self { Self::default().method(Method::GET) } /// Create TestRequest and set method to `Method::POST` - pub fn post() -> TestRequest { + pub fn post() -> Self { Self::default().method(Method::POST) } +} + +impl TestRequest { + /// Create TestRequest and set request uri + pub fn with_state(state: S) -> TestRequest { + let req = TestRequest::get(); + TestRequest { + state: State::new(state), + req: req.req, + path: req.path, + } + } /// Set HTTP version of this request pub fn version(mut self, ver: Version) -> Self { @@ -95,16 +108,11 @@ impl TestRequest { } /// Complete request creation and generate `Request` instance - pub fn finish(self) -> FramedRequest { - self.finish_with_state(()) - } - - /// Complete request creation and generate `Request` instance - pub fn finish_with_state(mut self, state: S) -> FramedRequest { + pub fn finish(mut self) -> FramedRequest { let req = self.req.finish(); self.path.get_mut().update(req.uri()); let framed = Framed::new(TestBuffer::empty(), Codec::default()); - FramedRequest::new(req, framed, self.path, State::new(state)) + FramedRequest::new(req, framed, self.path, self.state) } } From b4768a8f81dad8717362656d9f2962b1ea9a1448 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 12 Apr 2019 11:28:57 -0700 Subject: [PATCH 062/122] add TestRequest::run(), allows to run async functions --- actix-framed/Cargo.toml | 2 +- actix-framed/src/test.rs | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/actix-framed/Cargo.toml b/actix-framed/Cargo.toml index a2919bcec..bbd7dd698 100644 --- a/actix-framed/Cargo.toml +++ b/actix-framed/Cargo.toml @@ -24,6 +24,7 @@ actix-codec = "0.1.2" actix-service = "0.3.6" actix-utils = "0.3.4" actix-router = "0.1.2" +actix-rt = "0.2.2" actix-http = { path = "../actix-http" } bytes = "0.4" @@ -31,7 +32,6 @@ futures = "0.1.25" log = "0.4" [dev-dependencies] -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.3", features=["ssl"] } diff --git a/actix-framed/src/test.rs b/actix-framed/src/test.rs index 34a157279..3bc828df4 100644 --- a/actix-framed/src/test.rs +++ b/actix-framed/src/test.rs @@ -5,6 +5,8 @@ use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; use actix_http::http::{HttpTryFrom, Method, Uri, Version}; use actix_http::test::{TestBuffer, TestRequest as HttpTestRequest}; use actix_router::{Path, Url}; +use actix_rt::Runtime; +use futures::IntoFuture; use crate::{FramedRequest, State}; @@ -114,6 +116,16 @@ impl TestRequest { let framed = Framed::new(TestBuffer::empty(), Codec::default()); FramedRequest::new(req, framed, self.path, self.state) } + + /// This method generates `FramedRequest` instance and executes async handler + pub fn run(self, f: F) -> Result + where + F: FnOnce(FramedRequest) -> R, + R: IntoFuture, + { + let mut rt = Runtime::new().unwrap(); + rt.block_on(f(self.finish()).into_future()) + } } #[cfg(test)] From 87167f6581523b37513a2df48ff35e3076da5f2a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 12 Apr 2019 12:33:11 -0700 Subject: [PATCH 063/122] update actix-connect --- actix-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index b1aefdb1d..f0ff2058f 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -46,7 +46,7 @@ secure-cookies = ["ring"] [dependencies] actix-service = "0.3.6" actix-codec = "0.1.2" -actix-connect = "0.1.3" +actix-connect = "0.1.4" actix-utils = "0.3.5" actix-server-config = "0.1.0" actix-threadpool = "0.1.0" From 1f2b15397df6d19e475d5c5bb6d310fa521afcce Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 12 Apr 2019 14:00:45 -0700 Subject: [PATCH 064/122] prepare alpha5 release --- CHANGES.md | 6 ++++++ Cargo.toml | 17 ++++++----------- actix-files/Cargo.toml | 4 ++-- actix-framed/Cargo.toml | 6 +++--- actix-framed/changes.md | 5 +++++ actix-http/CHANGES.md | 2 +- actix-http/Cargo.toml | 14 +++++++------- actix-session/Cargo.toml | 4 ++-- actix-web-actors/Cargo.toml | 6 +++--- actix-web-codegen/Cargo.toml | 6 +++--- awc/CHANGES.md | 2 +- awc/Cargo.toml | 12 ++++++------ src/lib.rs | 1 - src/test.rs | 2 ++ test-server/Cargo.toml | 16 ++++++++-------- 15 files changed, 55 insertions(+), 48 deletions(-) create mode 100644 actix-framed/changes.md diff --git a/CHANGES.md b/CHANGES.md index 162f410fd..bf60787f9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,15 @@ # Changes +## [1.0.0-alpha.5] - 2019-04-12 + ### Added * Added async io `TestBuffer` for testing. +### Deleted + +* Removed native-tls support + ## [1.0.0-alpha.4] - 2019-04-08 diff --git a/Cargo.toml b/Cargo.toml index 609b2ff3f..442914f07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.0-alpha.4" +version = "1.0.0-alpha.5" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -38,7 +38,7 @@ members = [ ] [package.metadata.docs.rs] -features = ["ssl", "tls", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls"] +features = ["ssl", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls"] [features] default = ["brotli", "flate2-zlib", "secure-cookies", "client"] @@ -58,9 +58,6 @@ flate2-rust = ["actix-http/flate2-rust"] # sessions feature, session require "ring" crate and c compiler secure-cookies = ["actix-http/secure-cookies"] -# tls -tls = ["native-tls", "actix-server/ssl"] - # openssl ssl = ["openssl", "actix-server/ssl", "awc/ssl"] @@ -74,11 +71,11 @@ actix-utils = "0.3.4" actix-router = "0.1.2" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-alpha.1" -actix-http = { version = "0.1.0-alpha.4", features=["fail"] } +actix-http = { version = "0.1.0-alpha.5", features=["fail"] } actix-server = "0.4.2" actix-server-config = "0.1.0" actix-threadpool = "0.1.0" -awc = { version = "0.1.0-alpha.4", optional = true } +awc = { version = "0.1.0-alpha.5", optional = true } bytes = "0.4" derive_more = "0.14" @@ -92,17 +89,16 @@ parking_lot = "0.7" regex = "1.0" serde = { version = "1.0", features=["derive"] } serde_json = "1.0" -serde_urlencoded = "^0.5.3" +serde_urlencoded = "0.5.3" time = "0.1" url = { version="1.7", features=["query_encoding"] } # ssl support -native-tls = { version="0.2", optional = true } openssl = { version="0.10", optional = true } rustls = { version = "^0.15", optional = true } [dev-dependencies] -actix-http = { version = "0.1.0-alpha.4", features=["ssl", "brotli", "flate2-zlib"] } +actix-http = { version = "0.1.0-alpha.5", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } rand = "0.6" env_logger = "0.6" @@ -117,7 +113,6 @@ opt-level = 3 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" } diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index e017b1326..6cc9c711c 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.4" +actix-web = "1.0.0-alpha.5" 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.4", features=["ssl"] } +actix-web = { version = "1.0.0-alpha.5", features=["ssl"] } diff --git a/actix-framed/Cargo.toml b/actix-framed/Cargo.toml index bbd7dd698..ba433e17e 100644 --- a/actix-framed/Cargo.toml +++ b/actix-framed/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-framed" -version = "0.1.0" +version = "0.1.0-alpha.1" authors = ["Nikolay Kim "] description = "Actix framed app server" readme = "README.md" @@ -25,7 +25,7 @@ actix-service = "0.3.6" actix-utils = "0.3.4" actix-router = "0.1.2" actix-rt = "0.2.2" -actix-http = { path = "../actix-http" } +actix-http = "0.1.0-alpha.5" bytes = "0.4" futures = "0.1.25" @@ -33,5 +33,5 @@ log = "0.4" [dev-dependencies] actix-server = { version = "0.4.1", features=["ssl"] } -actix-connect = { version = "0.1.0", features=["ssl"] } +actix-connect = { version = "0.1.4", features=["ssl"] } actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } diff --git a/actix-framed/changes.md b/actix-framed/changes.md new file mode 100644 index 000000000..125e22bcd --- /dev/null +++ b/actix-framed/changes.md @@ -0,0 +1,5 @@ +# Changes + +## [0.1.0-alpha.1] - 2019-04-12 + +* Initial release diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index ae9bb81c4..98078d5b3 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.1.0-alpha.5] - 2019-04-xx +## [0.1.0-alpha.5] - 2019-04-12 ### Added diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index f0ff2058f..cf82a733d 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.1.0-alpha.4" +version = "0.1.0-alpha.5" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" @@ -60,9 +60,9 @@ derive_more = "0.14" either = "1.5.2" encoding = "0.2" futures = "0.1" -hashbrown = "0.1.8" +hashbrown = "0.2.0" h2 = "0.1.16" -http = "0.1.16" +http = "0.1.17" httparse = "1.3" indexmap = "1.0" lazy_static = "1.0" @@ -81,14 +81,14 @@ time = "0.1" tokio-tcp = "0.1.3" tokio-timer = "0.2" tokio-current-thread = "0.1" -trust-dns-resolver = { version="0.11.0-alpha.2", default-features = false } +trust-dns-resolver = { version="0.11.0-alpha.3", default-features = false } # for secure cookie ring = { version = "0.14.6", optional = true } # compression -brotli2 = { version="^0.3.2", optional = true } -flate2 = { version="^1.0.2", optional = true, default-features = false } +brotli2 = { version="0.3.2", optional = true } +flate2 = { version="1.0.7", optional = true, default-features = false } # optional deps failure = { version = "0.1.5", optional = true } @@ -97,7 +97,7 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.2" actix-server = { version = "0.4.1", features=["ssl"] } -actix-connect = { version = "0.1.0", features=["ssl"] } +actix-connect = { version = "0.1.4", features=["ssl"] } actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index 00a4c5244..9f1a97096 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -24,12 +24,12 @@ default = ["cookie-session"] cookie-session = ["actix-web/secure-cookies"] [dependencies] -actix-web = "1.0.0-alpha.4" +actix-web = "1.0.0-alpha.5" actix-service = "0.3.4" bytes = "0.4" derive_more = "0.14" futures = "0.1.25" -hashbrown = "0.1.8" +hashbrown = "0.2.0" serde = "1.0" serde_json = "1.0" time = "0.1" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 598d39459..084598a13 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -18,9 +18,9 @@ name = "actix_web_actors" path = "src/lib.rs" [dependencies] -actix = "0.8.0-alpha.2" -actix-web = "1.0.0-alpha.3" -actix-http = "0.1.0-alpha.3" +actix = "0.8.0-alpha.3" +actix-web = "1.0.0-alpha.5" +actix-http = "0.1.0-alpha.5" actix-codec = "0.1.2" bytes = "0.4" futures = "0.1.25" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 4d8c0910e..f4027fbdf 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -16,7 +16,7 @@ quote = "0.6" syn = { version = "0.15", features = ["full", "parsing"] } [dev-dependencies] -actix-web = { version = "1.0.0-alpha.2" } -actix-http = { version = "0.1.0-alpha.2", features=["ssl"] } -actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] } +actix-web = { version = "1.0.0-alpha.5" } +actix-http = { version = "0.1.0-alpha.5", features=["ssl"] } +actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } futures = { version = "0.1" } diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 4eeed7a1f..4558867fc 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.1.0-alpha.5] - 2019-04-xx +## [0.1.0-alpha.5] - 2019-04-12 ### Changed diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 17a5d18fe..bddf63b82 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "0.1.0-alpha.4" +version = "0.1.0-alpha.5" authors = ["Nikolay Kim "] description = "Actix http client." readme = "README.md" @@ -36,9 +36,9 @@ flate2-zlib = ["actix-http/flate2-zlib"] flate2-rust = ["actix-http/flate2-rust"] [dependencies] -actix-codec = "0.1.1" -actix-service = "0.3.4" -actix-http = "0.1.0-alpha.4" +actix-codec = "0.1.2" +actix-service = "0.3.6" +actix-http = "0.1.0-alpha.5" base64 = "0.10.1" bytes = "0.4" derive_more = "0.14" @@ -55,8 +55,8 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.2" -actix-web = { version = "1.0.0-alpha.4", features=["ssl"] } -actix-http = { version = "0.1.0-alpha.4", features=["ssl"] } +actix-web = { version = "1.0.0-alpha.5", features=["ssl"] } +actix-http = { version = "0.1.0-alpha.5", 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"] } diff --git a/src/lib.rs b/src/lib.rs index bebf6ef3e..f0600c6cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,7 +67,6 @@ //! ## Package feature //! //! * `client` - enables http client -//! * `tls` - enables ssl support via `native-tls` crate //! * `ssl` - enables ssl support via `openssl` crate, supports `http/2` //! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2` //! * `secure-cookies` - enables secure cookies support, includes `ring` crate as diff --git a/src/test.rs b/src/test.rs index affd80bb7..f52aefc48 100644 --- a/src/test.rs +++ b/src/test.rs @@ -14,6 +14,8 @@ use actix_service::{FnService, IntoNewService, NewService, Service}; use bytes::Bytes; use futures::future::{lazy, Future}; +pub use actix_http::test::TestBuffer; + use crate::config::{AppConfig, AppConfigInner}; use crate::data::RouteData; use crate::dev::{Body, Payload}; diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index fefcb5184..873eaea87 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -30,12 +30,12 @@ default = [] ssl = ["openssl", "actix-server/ssl", "awc/ssl"] [dependencies] -actix-codec = "0.1.1" -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.3" +actix-codec = "0.1.2" +actix-rt = "0.2.2" +actix-service = "0.3.6" +actix-server = "0.4.1" +actix-utils = "0.3.5" +awc = "0.1.0-alpha.5" base64 = "0.10" bytes = "0.4" @@ -55,5 +55,5 @@ tokio-timer = "0.2" openssl = { version="0.10", optional = true } [dev-dependencies] -actix-web = "1.0.0-alpha.3" -actix-http = "0.1.0-alpha.3" +actix-web = "1.0.0-alpha.5" +actix-http = "0.1.0-alpha.5" From 48518df8830b8f4948f1d083d20755246ec5bf21 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 13 Apr 2019 09:35:23 -0700 Subject: [PATCH 065/122] do not generate all docs; use docs.rs for 1.0 docs --- .travis.yml | 2 +- README.md | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 20e4c4a52..2dea00c58 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,7 +42,7 @@ script: after_success: - | if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then - cargo doc --all-features && + cargo doc --no-deps --all-features && echo "" > target/doc/index.html && git clone https://github.com/davisp/ghp-import.git && ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && diff --git a/README.md b/README.md index 35ea0bf0e..fc8f78b86 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. ## Documentation & community resources * [User Guide](https://actix.rs/docs/) -* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/) -* [API Documentation (0.7 Release)](https://docs.rs/actix-web/0.7.19/actix_web/) +* [API Documentation (1.0)](https://docs.rs/actix-web/) +* [API Documentation (0.7)](https://docs.rs/actix-web/0.7.19/actix_web/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-web](https://crates.io/crates/actix-web) * Minimum supported Rust version: 1.32 or later @@ -36,8 +36,7 @@ fn index(info: web::Path<(u32, String)>) -> impl Responder { fn main() -> std::io::Result<()> { HttpServer::new( || App::new().service( - web::resource("/{id}/{name}/index.html") - .route(web::get().to(index)))) + web::resource("/{id}/{name}/index.html").to(index))) .bind("127.0.0.1:8080")? .run() } From 043f6e77ae23f9d545946101439ce0ad195d5694 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 13 Apr 2019 10:11:07 -0700 Subject: [PATCH 066/122] remove nested multipart support --- actix-multipart/CHANGES.md | 2 + actix-multipart/src/error.rs | 3 + actix-multipart/src/extractor.rs | 20 ++---- actix-multipart/src/lib.rs | 2 +- actix-multipart/src/server.rs | 107 +++++++++---------------------- 5 files changed, 45 insertions(+), 89 deletions(-) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 6be07f2e2..fec3e50f8 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -2,4 +2,6 @@ ## [0.1.0-alpha.1] - 2019-04-xx +* Do not support nested multipart + * Split multipart support to separate crate diff --git a/actix-multipart/src/error.rs b/actix-multipart/src/error.rs index 1b872187d..995585850 100644 --- a/actix-multipart/src/error.rs +++ b/actix-multipart/src/error.rs @@ -16,6 +16,9 @@ pub enum MultipartError { /// Multipart boundary is not found #[display(fmt = "Multipart boundary is not found")] Boundary, + /// Nested multipart is not supported + #[display(fmt = "Nested multipart is not supported")] + Nested, /// Multipart stream is incomplete #[display(fmt = "Multipart stream is incomplete")] Incomplete, diff --git a/actix-multipart/src/extractor.rs b/actix-multipart/src/extractor.rs index 94eb4c305..52290b60f 100644 --- a/actix-multipart/src/extractor.rs +++ b/actix-multipart/src/extractor.rs @@ -21,19 +21,13 @@ use crate::server::Multipart; /// /// 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(())) -/// } +/// .and_then(|field| { // <- iterate over multipart items +/// // Field in turn is stream of *Bytes* object +/// field.from_err() +/// .fold((), |_, chunk| { +/// println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk)); +/// Ok::<_, Error>(()) +/// }) /// }) /// .fold((), |_, _| Ok::<_, Error>(())) /// .map(|_| HttpResponse::Ok().into()) diff --git a/actix-multipart/src/lib.rs b/actix-multipart/src/lib.rs index 602c27931..d8f365b2c 100644 --- a/actix-multipart/src/lib.rs +++ b/actix-multipart/src/lib.rs @@ -3,4 +3,4 @@ mod extractor; mod server; pub use self::error::MultipartError; -pub use self::server::{Field, Item, Multipart}; +pub use self::server::{Field, Multipart}; diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 2dae5fd33..c651fae56 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -32,18 +32,9 @@ pub struct Multipart { inner: Option>>, } -/// Multipart item -pub enum Item { - /// Multipart field - Field(Field), - /// Nested multipart stream - Nested(Multipart), -} - enum InnerMultipartItem { None, Field(Rc>), - Multipart(Rc>), } #[derive(PartialEq, Debug)] @@ -113,7 +104,7 @@ impl Multipart { } impl Stream for Multipart { - type Item = Item; + type Item = Field; type Error = MultipartError; fn poll(&mut self) -> Poll, Self::Error> { @@ -245,7 +236,7 @@ impl InnerMultipart { 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 { @@ -262,14 +253,7 @@ impl InnerMultipart { Async::Ready(None) => true, } } - InnerMultipartItem::Multipart(ref mut multipart) => { - match multipart.borrow_mut().poll(safety)? { - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(Some(_)) => continue, - Async::Ready(None) => true, - } - } - _ => false, + InnerMultipartItem::None => false, }; if stop { self.item = InnerMultipartItem::None; @@ -346,24 +330,7 @@ impl InnerMultipart { // nested multipart stream if mt.type_() == mime::MULTIPART { - let inner = if let Some(boundary) = mt.get_param(mime::BOUNDARY) { - Rc::new(RefCell::new(InnerMultipart { - payload: self.payload.clone(), - boundary: boundary.as_str().to_owned(), - state: InnerState::FirstBoundary, - item: InnerMultipartItem::None, - })) - } else { - return Err(MultipartError::Boundary); - }; - - self.item = InnerMultipartItem::Multipart(Rc::clone(&inner)); - - Ok(Async::Ready(Some(Item::Nested(Multipart { - safety: safety.clone(), - error: None, - inner: Some(inner), - })))) + Err(MultipartError::Nested) } else { let field = Rc::new(RefCell::new(InnerField::new( self.payload.clone(), @@ -372,12 +339,12 @@ impl InnerMultipart { )?)); self.item = InnerMultipartItem::Field(Rc::clone(&field)); - Ok(Async::Ready(Some(Item::Field(Field::new( + Ok(Async::Ready(Some(Field::new( safety.clone(), headers, mt, field, - ))))) + )))) } } } @@ -864,50 +831,40 @@ mod tests { let mut multipart = Multipart::new(&headers, payload); 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); - assert_eq!( - cd.parameters[0], - DispositionParam::Name("file".into()) - ); - } - assert_eq!(field.content_type().type_(), mime::TEXT); - assert_eq!(field.content_type().subtype(), mime::PLAIN); + Async::Ready(Some(mut field)) => { + let cd = field.content_disposition().unwrap(); + assert_eq!(cd.disposition, DispositionType::FormData); + assert_eq!(cd.parameters[0], DispositionParam::Name("file".into())); - match field.poll().unwrap() { - Async::Ready(Some(chunk)) => assert_eq!(chunk, "test"), - _ => unreachable!(), - } - match field.poll().unwrap() { - Async::Ready(None) => (), - _ => unreachable!(), - } + assert_eq!(field.content_type().type_(), mime::TEXT); + assert_eq!(field.content_type().subtype(), mime::PLAIN); + + match field.poll().unwrap() { + Async::Ready(Some(chunk)) => assert_eq!(chunk, "test"), + _ => unreachable!(), } - _ => unreachable!(), - }, + match field.poll().unwrap() { + Async::Ready(None) => (), + _ => unreachable!(), + } + } _ => unreachable!(), } 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); + Async::Ready(Some(mut field)) => { + 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, "data"), - _ => unreachable!(), - } - match field.poll() { - Ok(Async::Ready(None)) => (), - _ => unreachable!(), - } + match field.poll() { + Ok(Async::Ready(Some(chunk))) => assert_eq!(chunk, "data"), + _ => unreachable!(), } - _ => unreachable!(), - }, + match field.poll() { + Ok(Async::Ready(None)) => (), + _ => unreachable!(), + } + } _ => unreachable!(), } From 4f30fa9d46ada3523c5ffd1f2310de88166d4949 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 13 Apr 2019 14:50:54 -0700 Subject: [PATCH 067/122] Remove generic type for request payload, always use default --- CHANGES.md | 10 + actix-files/src/lib.rs | 59 ++- actix-files/src/named.rs | 2 +- actix-http/src/payload.rs | 1 + actix-multipart/src/extractor.rs | 13 +- actix-session/src/cookie.rs | 16 +- actix-session/src/lib.rs | 8 +- actix-web-codegen/src/route.rs | 4 +- actix-web-codegen/tests/test_macro.rs | 5 - examples/basic.rs | 2 +- src/app.rs | 594 +++++++------------------- src/app_service.rs | 202 +++------ src/config.rs | 36 +- src/data.rs | 8 +- src/extract.rs | 38 +- src/handler.rs | 36 +- src/lib.rs | 2 +- src/middleware/compress.rs | 28 +- src/middleware/cors.rs | 25 +- src/middleware/decompress.rs | 75 ---- src/middleware/defaultheaders.rs | 18 +- src/middleware/errhandlers.rs | 26 +- src/middleware/identity.rs | 26 +- src/middleware/logger.rs | 28 +- src/middleware/mod.rs | 10 +- src/request.rs | 4 +- src/resource.rs | 119 +++--- src/route.rs | 96 ++--- src/scope.rs | 117 +++-- src/service.rs | 39 +- src/test.rs | 23 +- src/types/form.rs | 45 +- src/types/json.rs | 40 +- src/types/path.rs | 4 +- src/types/payload.rs | 59 +-- src/types/query.rs | 4 +- src/web.rs | 28 +- tests/test_server.rs | 61 ++- 38 files changed, 704 insertions(+), 1207 deletions(-) delete mode 100644 src/middleware/decompress.rs diff --git a/CHANGES.md b/CHANGES.md index bf60787f9..f7693e66e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,15 @@ # Changes +## [1.0.0-alpha.6] - 2019-04-xx + +### Changed + +* Remove generic type for request payload, always use default. + +* Removed `Decompress` middleware. Bytes, String, Json, Form extractors + automatically decompress payload. + + ## [1.0.0-alpha.5] - 2019-04-12 ### Added diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 6ebf43363..fd7ac3f64 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -32,9 +32,8 @@ use self::error::{FilesError, UriSegmentError}; pub use crate::named::NamedFile; pub use crate::range::HttpRange; -type HttpService

= BoxedService, ServiceResponse, Error>; -type HttpNewService

= - BoxedNewService<(), ServiceRequest

, ServiceResponse, Error, ()>; +type HttpService = BoxedService; +type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; /// Return the MIME type associated with a filename extension (case-insensitive). /// If `ext` is empty or no associated type for the extension was found, returns @@ -225,18 +224,18 @@ type MimeOverride = Fn(&mime::Name) -> DispositionType; /// .service(fs::Files::new("/static", ".")); /// } /// ``` -pub struct Files { +pub struct Files { path: String, directory: PathBuf, index: Option, show_index: bool, - default: Rc>>>>, + default: Rc>>>, renderer: Rc, mime_override: Option>, file_flags: named::Flags, } -impl Clone for Files { +impl Clone for Files { fn clone(&self) -> Self { Self { directory: self.directory.clone(), @@ -251,13 +250,13 @@ impl Clone for Files { } } -impl Files { +impl Files { /// Create new `Files` instance for specified base directory. /// /// `File` uses `ThreadPool` for blocking filesystem operations. /// By default pool with 5x threads of available cpus is used. /// Pool size can be changed by setting ACTIX_CPU_POOL environment variable. - pub fn new>(path: &str, dir: T) -> Files { + pub fn new>(path: &str, dir: T) -> Files { let dir = dir.into().canonicalize().unwrap_or_else(|_| PathBuf::new()); if !dir.is_dir() { log::error!("Specified path is not a directory"); @@ -335,7 +334,7 @@ impl Files { where F: IntoNewService, U: NewService< - Request = ServiceRequest, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, > + 'static, @@ -349,11 +348,8 @@ impl Files { } } -impl

HttpServiceFactory

for Files

-where - P: 'static, -{ - fn register(self, config: &mut ServiceConfig

) { +impl HttpServiceFactory for Files { + fn register(self, config: &mut ServiceConfig) { if self.default.borrow().is_none() { *self.default.borrow_mut() = Some(config.default_service()); } @@ -366,11 +362,11 @@ where } } -impl NewService for Files

{ - type Request = ServiceRequest

; +impl NewService for Files { + type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Service = FilesService

; + type Service = FilesService; type InitError = (); type Future = Box>; @@ -401,22 +397,22 @@ impl NewService for Files

{ } } -pub struct FilesService

{ +pub struct FilesService { directory: PathBuf, index: Option, show_index: bool, - default: Option>, + default: Option, renderer: Rc, mime_override: Option>, file_flags: named::Flags, } -impl

FilesService

{ +impl FilesService { fn handle_err( &mut self, e: io::Error, req: HttpRequest, - payload: Payload

, + payload: Payload, ) -> Either< FutureResult, Box>, @@ -430,8 +426,8 @@ impl

FilesService

{ } } -impl

Service for FilesService

{ - type Request = ServiceRequest

; +impl Service for FilesService { + type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Future = Either< @@ -443,7 +439,7 @@ impl

Service for FilesService

{ Ok(Async::Ready(())) } - fn call(&mut self, req: ServiceRequest

) -> Self::Future { + fn call(&mut self, req: ServiceRequest) -> Self::Future { let (req, pl) = req.into_parts(); let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) { @@ -547,11 +543,11 @@ impl PathBufWrp { } } -impl

FromRequest

for PathBufWrp { +impl FromRequest for PathBufWrp { type Error = UriSegmentError; type Future = Result; - fn from_request(req: &HttpRequest, _: &mut Payload

) -> Self::Future { + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { PathBufWrp::get_pathbuf(req.match_info().path()) } } @@ -570,6 +566,7 @@ mod tests { self, ContentDisposition, DispositionParam, DispositionType, }; use actix_web::http::{Method, StatusCode}; + use actix_web::middleware::Compress; use actix_web::test::{self, TestRequest}; use actix_web::App; @@ -965,7 +962,7 @@ mod tests { #[test] fn test_named_file_content_encoding() { - let mut srv = test::init_service(App::new().enable_encoding().service( + let mut srv = test::init_service(App::new().wrap(Compress::default()).service( web::resource("/").to(|| { NamedFile::open("Cargo.toml") .unwrap() @@ -984,7 +981,7 @@ mod tests { #[test] fn test_named_file_content_encoding_gzip() { - let mut srv = test::init_service(App::new().enable_encoding().service( + let mut srv = test::init_service(App::new().wrap(Compress::default()).service( web::resource("/").to(|| { NamedFile::open("Cargo.toml") .unwrap() @@ -1053,15 +1050,15 @@ mod tests { #[test] fn test_static_files_bad_directory() { - let _st: Files<()> = Files::new("/", "missing"); - let _st: Files<()> = Files::new("/", "Cargo.toml"); + let _st: Files = Files::new("/", "missing"); + let _st: Files = Files::new("/", "Cargo.toml"); } #[test] fn test_default_handler_file_missing() { let mut st = test::block_on( Files::new("/", ".") - .default_handler(|req: ServiceRequest<_>| { + .default_handler(|req: ServiceRequest| { Ok(req.into_response(HttpResponse::Ok().body("default content"))) }) .new_service(&()), diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 1f54c8288..c506c02f2 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -15,7 +15,7 @@ use actix_web::http::header::{ self, ContentDisposition, DispositionParam, DispositionType, }; use actix_web::http::{ContentEncoding, Method, StatusCode}; -use actix_web::middleware::encoding::BodyEncoding; +use actix_web::middleware::BodyEncoding; use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder}; use crate::range::HttpRange; diff --git a/actix-http/src/payload.rs b/actix-http/src/payload.rs index 91e6b5c95..0ce209705 100644 --- a/actix-http/src/payload.rs +++ b/actix-http/src/payload.rs @@ -53,6 +53,7 @@ where type Item = Bytes; type Error = PayloadError; + #[inline] fn poll(&mut self) -> Poll, Self::Error> { match self { Payload::None => Ok(Async::Ready(None)), diff --git a/actix-multipart/src/extractor.rs b/actix-multipart/src/extractor.rs index 52290b60f..1f2f15c63 100644 --- a/actix-multipart/src/extractor.rs +++ b/actix-multipart/src/extractor.rs @@ -1,9 +1,5 @@ //! Multipart payload support -use bytes::Bytes; -use futures::Stream; - -use actix_web::error::{Error, PayloadError}; -use actix_web::{dev::Payload, FromRequest, HttpRequest}; +use actix_web::{dev::Payload, Error, FromRequest, HttpRequest}; use crate::server::Multipart; @@ -34,15 +30,12 @@ use crate::server::Multipart; /// } /// # fn main() {} /// ``` -impl

FromRequest

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

) -> Self::Future { + fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { Ok(Multipart::new(req.headers(), payload.take())) } } diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index b44c87e04..634288b45 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -120,7 +120,7 @@ impl CookieSessionInner { Ok(()) } - fn load

(&self, req: &ServiceRequest

) -> HashMap { + fn load(&self, req: &ServiceRequest) -> HashMap { if let Ok(cookies) = req.cookies() { for cookie in cookies.iter() { if cookie.name() == self.name { @@ -256,13 +256,13 @@ impl CookieSession { } } -impl Transform for CookieSession +impl Transform for CookieSession where - S: Service, Response = ServiceResponse>, + S: Service>, S::Future: 'static, S::Error: 'static, { - type Request = ServiceRequest

; + type Request = ServiceRequest; type Response = ServiceResponse; type Error = S::Error; type InitError = (); @@ -283,13 +283,13 @@ pub struct CookieSessionMiddleware { inner: Rc, } -impl Service for CookieSessionMiddleware +impl Service for CookieSessionMiddleware where - S: Service, Response = ServiceResponse>, + S: Service>, S::Future: 'static, S::Error: 'static, { - type Request = ServiceRequest

; + type Request = ServiceRequest; type Response = ServiceResponse; type Error = S::Error; type Future = Box>; @@ -298,7 +298,7 @@ where self.service.poll_ready() } - fn call(&mut self, mut req: ServiceRequest

) -> Self::Future { + fn call(&mut self, mut req: ServiceRequest) -> Self::Future { let inner = self.inner.clone(); let state = self.inner.load(&req); Session::set_session(state.into_iter(), &mut req); diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index 4b7ae2fde..8db875238 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -119,9 +119,9 @@ impl Session { inner.state.clear() } - pub fn set_session

( + pub fn set_session( data: impl Iterator, - req: &mut ServiceRequest

, + req: &mut ServiceRequest, ) { let session = Session::get_session(&mut *req.extensions_mut()); let mut inner = session.0.borrow_mut(); @@ -172,12 +172,12 @@ impl Session { /// } /// # fn main() {} /// ``` -impl

FromRequest

for Session { +impl FromRequest for Session { type Error = Error; type Future = Result; #[inline] - fn from_request(req: &HttpRequest, _: &mut Payload

) -> Self::Future { + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { Ok(Session::get_session(&mut *req.extensions_mut())) } } diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index e1a870dbc..348ce86ae 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -61,8 +61,8 @@ impl fmt::Display for Args { #[allow(non_camel_case_types)] pub struct {name}; -impl actix_web::dev::HttpServiceFactory

for {name} {{ - fn register(self, config: &mut actix_web::dev::ServiceConfig

) {{ +impl actix_web::dev::HttpServiceFactory for {name} {{ + fn register(self, config: &mut actix_web::dev::ServiceConfig) {{ {ast} let resource = actix_web::Resource::new(\"{path}\"){guards}.{to}({name}); diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index c27eefdef..dd105785d 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -4,11 +4,6 @@ use actix_web::{http, App, HttpResponse, Responder}; use actix_web_codegen::get; use futures::{future, Future}; -//fn guard_head(head: &actix_web::dev::RequestHead) -> bool { -// true -//} - -//#[get("/test", guard="guard_head")] #[get("/test")] fn test() -> impl Responder { HttpResponse::Ok() diff --git a/examples/basic.rs b/examples/basic.rs index 1191b371c..911196570 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -27,7 +27,7 @@ fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) - .wrap(middleware::encoding::Compress::default()) + .wrap(middleware::Compress::default()) .wrap(middleware::Logger::default()) .service(index) .service(no_params) diff --git a/src/app.rs b/src/app.rs index f378572b2..39c96cd92 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3,22 +3,18 @@ use std::marker::PhantomData; use std::rc::Rc; use actix_http::body::{Body, MessageBody}; -#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] -use actix_http::encoding::{Decoder, Encoder}; use actix_server_config::ServerConfig; use actix_service::boxed::{self, BoxedNewService}; use actix_service::{ apply_transform, IntoNewService, IntoTransform, NewService, Transform, }; -#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] -use bytes::Bytes; -use futures::{IntoFuture, Stream}; +use futures::IntoFuture; -use crate::app_service::{AppChain, AppEntry, AppInit, AppRouting, AppRoutingFactory}; +use crate::app_service::{AppEntry, AppInit, AppRoutingFactory}; use crate::config::{AppConfig, AppConfigInner, RouterConfig}; use crate::data::{Data, DataFactory}; -use crate::dev::{Payload, PayloadStream, ResourceDef}; -use crate::error::{Error, PayloadError}; +use crate::dev::ResourceDef; +use crate::error::Error; use crate::resource::Resource; use crate::route::Route; use crate::service::{ @@ -26,40 +22,44 @@ use crate::service::{ ServiceResponse, }; -type HttpNewService

= - BoxedNewService<(), ServiceRequest

, ServiceResponse, Error, ()>; +type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; /// Application builder - structure that follows the builder pattern /// for building application instances. -pub struct App -where - T: NewService, Response = ServiceRequest>, -{ - chain: T, +pub struct App { + endpoint: T, + services: Vec>, + default: Option>, + factory_ref: Rc>>, data: Vec>, config: AppConfigInner, - _t: PhantomData<(In, Out)>, + external: Vec, + _t: PhantomData<(B)>, } -impl App { +impl App { /// Create application builder. Application can be configured with a builder-like pattern. pub fn new() -> Self { + let fref = Rc::new(RefCell::new(None)); App { - chain: AppChain, + endpoint: AppEntry::new(fref.clone()), data: Vec::new(), + services: Vec::new(), + default: None, + factory_ref: fref, config: AppConfigInner::default(), + external: Vec::new(), _t: PhantomData, } } } -impl App +impl App where - In: 'static, - Out: 'static, + B: MessageBody, T: NewService< - Request = ServiceRequest, - Response = ServiceRequest, + Request = ServiceRequest, + Response = ServiceResponse, Error = Error, InitError = (), >, @@ -112,151 +112,6 @@ 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 - /// necessary, across all requests managed by the *Application*. - /// - /// Use middleware when you need to read or modify *every* request or response in some way. - /// - /// ```rust - /// use actix_service::Service; - /// # use futures::Future; - /// use actix_web::{middleware, web, App}; - /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; - /// - /// fn index() -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new() - /// .wrap(middleware::Logger::default()) - /// .route("/index.html", web::get().to(index)); - /// } - /// ``` - pub fn wrap( - self, - mw: F, - ) -> AppRouter< - T, - Out, - B, - impl NewService< - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - > - where - M: Transform< - AppRouting, - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - F: IntoTransform>, - { - let fref = Rc::new(RefCell::new(None)); - let endpoint = apply_transform(mw, AppEntry::new(fref.clone())); - AppRouter { - endpoint, - chain: self.chain, - data: self.data, - services: Vec::new(), - default: None, - factory_ref: fref, - config: self.config, - external: Vec::new(), - _t: PhantomData, - } - } - - /// Registers middleware, in the form of a closure, 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. - /// - /// ```rust - /// use actix_service::Service; - /// # use futures::Future; - /// use actix_web::{web, App}; - /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; - /// - /// fn index() -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new() - /// .wrap_fn(|req, srv| - /// srv.call(req).map(|mut res| { - /// res.headers_mut().insert( - /// CONTENT_TYPE, HeaderValue::from_static("text/plain"), - /// ); - /// res - /// })) - /// .route("/index.html", web::get().to(index)); - /// } - /// ``` - pub fn wrap_fn( - self, - mw: F, - ) -> AppRouter< - T, - Out, - B, - impl NewService< - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - > - where - F: FnMut(ServiceRequest, &mut AppRouting) -> R + Clone, - R: IntoFuture, Error = Error>, - { - self.wrap(mw) - } - - /// Register a request modifier. It can modify any request parameters - /// including request payload type. - pub fn chain( - self, - chain: F, - ) -> App< - In, - P, - impl NewService< - Request = ServiceRequest, - Response = ServiceRequest

, - Error = Error, - InitError = (), - >, - > - where - C: NewService< - Request = ServiceRequest, - Response = ServiceRequest

, - Error = Error, - InitError = (), - >, - F: IntoNewService, - { - let chain = self.chain.and_then(chain.into_new_service()); - App { - chain, - data: self.data, - config: self.config, - _t: PhantomData, - } - } - /// Run external configuration as part of the application building /// process /// @@ -269,7 +124,7 @@ where /// use actix_web::{web, middleware, App, HttpResponse}; /// /// // this function could be located in different module - /// fn config

(cfg: &mut web::RouterConfig

) { + /// fn config(cfg: &mut web::RouterConfig) { /// cfg.service(web::resource("/test") /// .route(web::get().to(|| HttpResponse::Ok())) /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) @@ -283,27 +138,16 @@ where /// .route("/index.html", web::get().to(|| HttpResponse::Ok())); /// } /// ``` - pub fn configure(mut self, f: F) -> AppRouter> + pub fn configure(mut self, f: F) -> Self where - F: Fn(&mut RouterConfig), + 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, - } + self.services.extend(cfg.services); + self.external.extend(cfg.external); + self } /// Configure route for a specific path. @@ -325,171 +169,7 @@ where /// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed())); /// } /// ``` - pub fn route( - self, - path: &str, - mut route: Route, - ) -> AppRouter> { - self.service( - Resource::new(path) - .add_guards(route.take_guards()) - .route(route), - ) - } - - /// Register http service. - /// - /// Http service is any type that implements `HttpServiceFactory` trait. - /// - /// Actix web provides several services implementations: - /// - /// * *Resource* is an entry in resource table which corresponds to requested URL. - /// * *Scope* is a set of resources with common root path. - /// * "StaticFiles" is a service for static files support - pub fn service(self, service: F) -> AppRouter> - where - F: HttpServiceFactory + 'static, - { - 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: vec![Box::new(ServiceFactoryWrapper::new(service))], - external: Vec::new(), - _t: PhantomData, - } - } - - /// Set server host name. - /// - /// Host name is used by application router as a hostname for url - /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. - /// html#method.host) documentation for more information. - /// - /// By default host name is set to a "localhost" value. - pub fn hostname(mut self, val: &str) -> Self { - self.config.host = val.to_owned(); - self - } - - #[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] - /// Enable content compression and decompression. - pub fn enable_encoding( - self, - ) -> AppRouter< - impl NewService< - Request = ServiceRequest, - Response = ServiceRequest>>, - Error = Error, - InitError = (), - >, - Decoder>, - Encoder, - impl NewService< - Request = ServiceRequest>>, - Response = ServiceResponse>, - Error = Error, - InitError = (), - >, - > - where - Out: Stream, - { - use crate::middleware::encoding::{Compress, Decompress}; - - self.chain(Decompress::new()).wrap(Compress::default()) - } -} - -/// Application router builder - Structure that follows the builder pattern -/// for building application instances. -pub struct AppRouter { - chain: C, - endpoint: T, - services: Vec>>, - default: Option>>, - factory_ref: Rc>>>, - data: Vec>, - config: AppConfigInner, - external: Vec, - _t: PhantomData<(P, B)>, -} - -impl AppRouter -where - P: 'static, - B: MessageBody, - T: NewService< - Request = ServiceRequest

, - Response = ServiceResponse, - Error = Error, - 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. - /// This method can not be could multiple times, in that case - /// multiple resources with one route would be registered for same resource path. - /// - /// ```rust - /// use actix_web::{web, App, HttpResponse}; - /// - /// fn index(data: web::Path<(String, String)>) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new() - /// .route("/test1", web::get().to(index)) - /// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed())); - /// } - /// ``` - pub fn route(self, path: &str, mut route: Route

) -> Self { + pub fn route(self, path: &str, mut route: Route) -> Self { self.service( Resource::new(path) .add_guards(route.take_guards()) @@ -508,94 +188,31 @@ where /// * "StaticFiles" is a service for static files support pub fn service(mut self, factory: F) -> Self where - F: HttpServiceFactory

+ 'static, + F: HttpServiceFactory + 'static, { self.services .push(Box::new(ServiceFactoryWrapper::new(factory))); 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 - /// necessary, across all requests managed by the *Route*. + /// Set server host name. /// - /// Use middleware when you need to read or modify *every* request or response in some way. + /// Host name is used by application router as a hostname for url + /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. + /// html#method.host) documentation for more information. /// - pub fn wrap( - self, - mw: F, - ) -> AppRouter< - C, - P, - B1, - impl NewService< - Request = ServiceRequest

, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - > - where - M: Transform< - T::Service, - Request = ServiceRequest

, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - B1: MessageBody, - F: IntoTransform, - { - let endpoint = apply_transform(mw, self.endpoint); - AppRouter { - endpoint, - chain: self.chain, - data: self.data, - services: self.services, - default: self.default, - factory_ref: self.factory_ref, - config: self.config, - external: self.external, - _t: PhantomData, - } - } - - /// Registers middleware, in the form of a closure, 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. - /// - pub fn wrap_fn( - self, - mw: F, - ) -> AppRouter< - C, - P, - B1, - impl NewService< - Request = ServiceRequest

, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - > - where - B1: MessageBody, - F: FnMut(ServiceRequest

, &mut T::Service) -> R + Clone, - R: IntoFuture, Error = Error>, - { - self.wrap(mw) + /// By default host name is set to a "localhost" value. + pub fn hostname(mut self, val: &str) -> Self { + self.config.host = val.to_owned(); + self } /// Default resource to be used if no matching resource could be found. pub fn default_resource(mut self, f: F) -> Self where - F: FnOnce(Resource

) -> Resource, + F: FnOnce(Resource) -> Resource, U: NewService< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -641,27 +258,128 @@ where self.external.push(rdef); 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 + /// necessary, across all requests managed by the *Application*. + /// + /// Use middleware when you need to read or modify *every* request or response in some way. + /// + /// ```rust + /// use actix_service::Service; + /// # use futures::Future; + /// use actix_web::{middleware, web, App}; + /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; + /// + /// fn index() -> &'static str { + /// "Welcome!" + /// } + /// + /// fn main() { + /// let app = App::new() + /// .wrap(middleware::Logger::default()) + /// .route("/index.html", web::get().to(index)); + /// } + /// ``` + pub fn wrap( + self, + mw: F, + ) -> App< + impl NewService< + Request = ServiceRequest, + Response = ServiceResponse, + Error = Error, + InitError = (), + >, + B1, + > + where + M: Transform< + T::Service, + Request = ServiceRequest, + Response = ServiceResponse, + Error = Error, + InitError = (), + >, + B1: MessageBody, + F: IntoTransform, + { + let endpoint = apply_transform(mw, self.endpoint); + App { + endpoint, + data: self.data, + services: self.services, + default: self.default, + factory_ref: self.factory_ref, + config: self.config, + external: self.external, + _t: PhantomData, + } + } + + /// Registers middleware, in the form of a closure, 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. + /// + /// ```rust + /// use actix_service::Service; + /// # use futures::Future; + /// use actix_web::{web, App}; + /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; + /// + /// fn index() -> &'static str { + /// "Welcome!" + /// } + /// + /// fn main() { + /// let app = App::new() + /// .wrap_fn(|req, srv| + /// srv.call(req).map(|mut res| { + /// res.headers_mut().insert( + /// CONTENT_TYPE, HeaderValue::from_static("text/plain"), + /// ); + /// res + /// })) + /// .route("/index.html", web::get().to(index)); + /// } + /// ``` + pub fn wrap_fn( + self, + mw: F, + ) -> App< + impl NewService< + Request = ServiceRequest, + Response = ServiceResponse, + Error = Error, + InitError = (), + >, + B1, + > + where + B1: MessageBody, + F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, + R: IntoFuture, Error = Error>, + { + self.wrap(mw) + } } -impl IntoNewService, ServerConfig> - for AppRouter +impl IntoNewService, ServerConfig> for App where + B: MessageBody, T: NewService< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), >, - C: NewService< - Request = ServiceRequest, - Response = ServiceRequest

, - Error = Error, - InitError = (), - >, { - fn into_new_service(self) -> AppInit { + fn into_new_service(self) -> AppInit { AppInit { - chain: self.chain, data: self.data, endpoint: self.endpoint, services: RefCell::new(self.services), @@ -742,13 +460,13 @@ mod tests { assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } - fn md( - req: ServiceRequest

, + fn md( + req: ServiceRequest, srv: &mut S, ) -> impl IntoFuture, Error = Error> where S: Service< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, >, diff --git a/src/app_service.rs b/src/app_service.rs index 593fbe673..a5d906363 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -6,7 +6,7 @@ use actix_http::{Request, Response}; use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; use actix_server_config::ServerConfig; use actix_service::boxed::{self, BoxedNewService, BoxedService}; -use actix_service::{fn_service, AndThen, NewService, Service, ServiceExt}; +use actix_service::{fn_service, NewService, Service}; use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, Poll}; @@ -19,9 +19,8 @@ use crate::rmap::ResourceMap; use crate::service::{ServiceFactory, ServiceRequest, ServiceResponse}; type Guards = Vec>; -type HttpService

= BoxedService, ServiceResponse, Error>; -type HttpNewService

= - BoxedNewService<(), ServiceRequest

, ServiceResponse, Error, ()>; +type HttpService = BoxedService; +type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; type BoxedResponse = Either< FutureResult, Box>, @@ -29,36 +28,28 @@ type BoxedResponse = Either< /// Service factory to convert `Request` to a `ServiceRequest`. /// It also executes data factories. -pub struct AppInit +pub struct AppInit where - C: NewService>, T: NewService< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), >, { - pub(crate) chain: C, pub(crate) endpoint: T, pub(crate) data: Vec>, pub(crate) config: RefCell, - pub(crate) services: RefCell>>>, - pub(crate) default: Option>>, - pub(crate) factory_ref: Rc>>>, + pub(crate) services: RefCell>>, + pub(crate) default: Option>, + pub(crate) factory_ref: Rc>>, pub(crate) external: RefCell>, } -impl NewService for AppInit +impl NewService for AppInit where - C: NewService< - Request = ServiceRequest, - Response = ServiceRequest

, - Error = Error, - InitError = (), - >, T: NewService< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -66,15 +57,15 @@ where { type Request = Request; type Response = ServiceResponse; - type Error = C::Error; - type InitError = C::InitError; - type Service = AndThen, T::Service>; - type Future = AppInitResult; + type Error = T::Error; + type InitError = T::InitError; + type Service = AppInitService; + type Future = AppInitResult; fn new_service(&self, cfg: &ServerConfig) -> Self::Future { // update resource default service let default = self.default.clone().unwrap_or_else(|| { - Rc::new(boxed::new_service(fn_service(|req: ServiceRequest

| { + Rc::new(boxed::new_service(fn_service(|req: ServiceRequest| { Ok(req.into_response(Response::NotFound().finish())) }))) }); @@ -121,8 +112,6 @@ where rmap.finish(rmap.clone()); AppInitResult { - chain: None, - chain_fut: self.chain.new_service(&()), endpoint: None, endpoint_fut: self.endpoint.new_service(&()), data: self.data.iter().map(|s| s.construct()).collect(), @@ -133,38 +122,29 @@ where } } -pub struct AppInitResult +pub struct AppInitResult where - C: NewService, T: NewService, { - chain: Option, endpoint: Option, - chain_fut: C::Future, endpoint_fut: T::Future, rmap: Rc, data: Vec>, config: AppConfig, - _t: PhantomData<(P, B)>, + _t: PhantomData, } -impl Future for AppInitResult +impl Future for AppInitResult where - C: NewService< - Request = ServiceRequest, - Response = ServiceRequest

, - Error = Error, - InitError = (), - >, T: NewService< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), >, { - type Item = AndThen, T::Service>; - type Error = C::InitError; + type Item = AppInitService; + type Error = T::InitError; fn poll(&mut self) -> Poll { let mut idx = 0; @@ -177,28 +157,19 @@ where } } - if self.chain.is_none() { - if let Async::Ready(srv) = self.chain_fut.poll()? { - self.chain = Some(srv); - } - } - if self.endpoint.is_none() { if let Async::Ready(srv) = self.endpoint_fut.poll()? { self.endpoint = Some(srv); } } - if self.chain.is_some() && self.endpoint.is_some() { - Ok(Async::Ready( - AppInitService { - chain: self.chain.take().unwrap(), - rmap: self.rmap.clone(), - config: self.config.clone(), - pool: HttpRequestPool::create(), - } - .and_then(self.endpoint.take().unwrap()), - )) + if self.endpoint.is_some() { + Ok(Async::Ready(AppInitService { + service: self.endpoint.take().unwrap(), + rmap: self.rmap.clone(), + config: self.config.clone(), + pool: HttpRequestPool::create(), + })) } else { Ok(Async::NotReady) } @@ -206,27 +177,27 @@ where } /// Service to convert `Request` to a `ServiceRequest` -pub struct AppInitService +pub struct AppInitService where - C: Service, Error = Error>, + T: Service, Error = Error>, { - chain: C, + service: T, rmap: Rc, config: AppConfig, pool: &'static HttpRequestPool, } -impl Service for AppInitService +impl Service for AppInitService where - C: Service, Error = Error>, + T: Service, Error = Error>, { type Request = Request; - type Response = ServiceRequest

; - type Error = C::Error; - type Future = C::Future; + type Response = ServiceResponse; + type Error = T::Error; + type Future = T::Future; fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.chain.poll_ready() + self.service.poll_ready() } fn call(&mut self, req: Request) -> Self::Future { @@ -247,22 +218,22 @@ where self.pool, ) }; - self.chain.call(ServiceRequest::from_parts(req, payload)) + self.service.call(ServiceRequest::from_parts(req, payload)) } } -pub struct AppRoutingFactory

{ - services: Rc, RefCell>)>>, - default: Rc>, +pub struct AppRoutingFactory { + services: Rc>)>>, + default: Rc, } -impl NewService for AppRoutingFactory

{ - type Request = ServiceRequest

; +impl NewService for AppRoutingFactory { + type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); - type Service = AppRouting

; - type Future = AppRoutingFactoryResponse

; + type Service = AppRouting; + type Future = AppRoutingFactoryResponse; fn new_service(&self, _: &()) -> Self::Future { AppRoutingFactoryResponse { @@ -283,23 +254,23 @@ impl NewService for AppRoutingFactory

{ } } -type HttpServiceFut

= Box, Error = ()>>; +type HttpServiceFut = Box>; /// Create app service #[doc(hidden)] -pub struct AppRoutingFactoryResponse

{ - fut: Vec>, - default: Option>, - default_fut: Option, Error = ()>>>, +pub struct AppRoutingFactoryResponse { + fut: Vec, + default: Option, + default_fut: Option>>, } -enum CreateAppRoutingItem

{ - Future(Option, Option, HttpServiceFut

), - Service(ResourceDef, Option, HttpService

), +enum CreateAppRoutingItem { + Future(Option, Option, HttpServiceFut), + Service(ResourceDef, Option, HttpService), } -impl

Future for AppRoutingFactoryResponse

{ - type Item = AppRouting

; +impl Future for AppRoutingFactoryResponse { + type Item = AppRouting; type Error = (); fn poll(&mut self) -> Poll { @@ -360,14 +331,14 @@ impl

Future for AppRoutingFactoryResponse

{ } } -pub struct AppRouting

{ - router: Router, Guards>, - ready: Option<(ServiceRequest

, ResourceInfo)>, - default: Option>, +pub struct AppRouting { + router: Router, + ready: Option<(ServiceRequest, ResourceInfo)>, + default: Option, } -impl

Service for AppRouting

{ - type Request = ServiceRequest

; +impl Service for AppRouting { + type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Future = BoxedResponse; @@ -380,7 +351,7 @@ impl

Service for AppRouting

{ } } - fn call(&mut self, mut req: ServiceRequest

) -> Self::Future { + fn call(&mut self, mut req: ServiceRequest) -> Self::Future { let res = self.router.recognize_mut_checked(&mut req, |req, guards| { if let Some(ref guards) = guards { for f in guards { @@ -404,58 +375,25 @@ impl

Service for AppRouting

{ } /// Wrapper service for routing -pub struct AppEntry

{ - factory: Rc>>>, +pub struct AppEntry { + factory: Rc>>, } -impl

AppEntry

{ - pub fn new(factory: Rc>>>) -> Self { +impl AppEntry { + pub fn new(factory: Rc>>) -> Self { AppEntry { factory } } } -impl NewService for AppEntry

{ - type Request = ServiceRequest

; +impl NewService for AppEntry { + type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); - type Service = AppRouting

; - type Future = AppRoutingFactoryResponse

; + type Service = AppRouting; + type Future = AppRoutingFactoryResponse; fn new_service(&self, _: &()) -> Self::Future { self.factory.borrow_mut().as_mut().unwrap().new_service(&()) } } - -#[doc(hidden)] -pub struct AppChain; - -impl NewService for AppChain { - type Request = ServiceRequest; - type Response = ServiceRequest; - type Error = Error; - type InitError = (); - type Service = AppChain; - type Future = FutureResult; - - fn new_service(&self, _: &()) -> Self::Future { - ok(AppChain) - } -} - -impl Service for AppChain { - type Request = ServiceRequest; - type Response = ServiceRequest; - type Error = Error; - type Future = FutureResult; - - #[inline] - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - #[inline] - fn call(&mut self, req: ServiceRequest) -> Self::Future { - ok(req) - } -} diff --git a/src/config.rs b/src/config.rs index 1e552291f..c28b66782 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,25 +19,25 @@ use crate::service::{ }; type Guards = Vec>; -type HttpNewService

= - boxed::BoxedNewService<(), ServiceRequest

, ServiceResponse, Error, ()>; +type HttpNewService = + boxed::BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; /// Application configuration -pub struct ServiceConfig

{ +pub struct ServiceConfig { config: AppConfig, root: bool, - default: Rc>, + default: Rc, services: Vec<( ResourceDef, - HttpNewService

, + HttpNewService, Option, Option>, )>, } -impl ServiceConfig

{ +impl ServiceConfig { /// Crate server settings instance - pub(crate) fn new(config: AppConfig, default: Rc>) -> Self { + pub(crate) fn new(config: AppConfig, default: Rc) -> Self { ServiceConfig { config, default, @@ -55,7 +55,7 @@ impl ServiceConfig

{ self, ) -> Vec<( ResourceDef, - HttpNewService

, + HttpNewService, Option, Option>, )> { @@ -77,7 +77,7 @@ impl ServiceConfig

{ } /// Default resource - pub fn default_service(&self) -> Rc> { + pub fn default_service(&self) -> Rc { self.default.clone() } @@ -90,7 +90,7 @@ impl ServiceConfig

{ ) where F: IntoNewService, S: NewService< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -169,13 +169,13 @@ impl Default for AppConfigInner { /// 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 struct RouterConfig { + pub(crate) services: Vec>, pub(crate) data: Vec>, pub(crate) external: Vec, } -impl RouterConfig

{ +impl RouterConfig { pub(crate) fn new() -> Self { Self { services: Vec::new(), @@ -211,7 +211,7 @@ impl RouterConfig

{ /// 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 { + pub fn route(&mut self, path: &str, mut route: Route) -> &mut Self { self.service( Resource::new(path) .add_guards(route.take_guards()) @@ -224,7 +224,7 @@ impl RouterConfig

{ /// This is same as `App::service()` method. pub fn service(&mut self, factory: F) -> &mut Self where - F: HttpServiceFactory

+ 'static, + F: HttpServiceFactory + 'static, { self.services .push(Box::new(ServiceFactoryWrapper::new(factory))); @@ -261,7 +261,7 @@ mod tests { #[test] fn test_data() { - let cfg = |cfg: &mut RouterConfig<_>| { + let cfg = |cfg: &mut RouterConfig| { cfg.data(10usize); }; @@ -276,7 +276,7 @@ mod tests { #[test] fn test_data_factory() { - let cfg = |cfg: &mut RouterConfig<_>| { + let cfg = |cfg: &mut RouterConfig| { cfg.data_factory(|| Ok::<_, ()>(10usize)); }; @@ -288,7 +288,7 @@ mod tests { let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let cfg2 = |cfg: &mut RouterConfig<_>| { + let cfg2 = |cfg: &mut RouterConfig| { cfg.data_factory(|| Ok::<_, ()>(10u32)); }; let mut srv = init_service( diff --git a/src/data.rs b/src/data.rs index d06eb6468..d178d779a 100644 --- a/src/data.rs +++ b/src/data.rs @@ -88,12 +88,12 @@ impl Clone for Data { } } -impl FromRequest

for Data { +impl FromRequest for Data { type Error = Error; type Future = Result; #[inline] - fn from_request(req: &HttpRequest, _: &mut Payload

) -> Self::Future { + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { if let Some(st) = req.app_config().extensions().get::>() { Ok(st.clone()) } else { @@ -232,12 +232,12 @@ impl Clone for RouteData { } } -impl FromRequest

for RouteData { +impl FromRequest for RouteData { type Error = Error; type Future = Result; #[inline] - fn from_request(req: &HttpRequest, _: &mut Payload

) -> Self::Future { + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { if let Some(st) = req.route_data::() { Ok(st.clone()) } else { diff --git a/src/extract.rs b/src/extract.rs index 73cbb4cee..3f20f3e3f 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -10,7 +10,7 @@ use crate::request::HttpRequest; /// Trait implemented by types that can be extracted from request. /// /// Types that implement this trait can be used with `Route` handlers. -pub trait FromRequest

: Sized { +pub trait FromRequest: Sized { /// The associated error which can be returned. type Error: Into; @@ -18,7 +18,7 @@ pub trait FromRequest

: Sized { type Future: IntoFuture; /// Convert request to a Self - fn from_request(req: &HttpRequest, payload: &mut Payload

) -> Self::Future; + fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future; /// Convert request to a Self /// @@ -45,11 +45,11 @@ pub trait FromRequest

: Sized { /// name: String /// } /// -/// impl

FromRequest

for Thing { +/// impl FromRequest for Thing { /// type Error = Error; /// type Future = Result; /// -/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload

) -> Self::Future { +/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { /// if rand::random() { /// Ok(Thing { name: "thingy".into() }) /// } else { @@ -75,16 +75,16 @@ pub trait FromRequest

: Sized { /// ); /// } /// ``` -impl FromRequest

for Option +impl FromRequest for Option where - T: FromRequest

, + T: FromRequest, T::Future: 'static, { type Error = Error; type Future = Box, Error = Error>>; #[inline] - fn from_request(req: &HttpRequest, payload: &mut Payload

) -> Self::Future { + fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { Box::new( T::from_request(req, payload) .into_future() @@ -116,11 +116,11 @@ where /// name: String /// } /// -/// impl

FromRequest

for Thing { +/// impl FromRequest for Thing { /// type Error = Error; /// type Future = Result; /// -/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload

) -> Self::Future { +/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { /// if rand::random() { /// Ok(Thing { name: "thingy".into() }) /// } else { @@ -143,9 +143,9 @@ where /// ); /// } /// ``` -impl FromRequest

for Result +impl FromRequest for Result where - T: FromRequest

, + T: FromRequest, T::Future: 'static, T::Error: 'static, { @@ -153,7 +153,7 @@ where type Future = Box, Error = Error>>; #[inline] - fn from_request(req: &HttpRequest, payload: &mut Payload

) -> Self::Future { + fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { Box::new( T::from_request(req, payload) .into_future() @@ -166,11 +166,11 @@ where } #[doc(hidden)] -impl

FromRequest

for () { +impl FromRequest for () { type Error = Error; type Future = Result<(), Error>; - fn from_request(_: &HttpRequest, _: &mut Payload

) -> Self::Future { + fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future { Ok(()) } } @@ -179,12 +179,12 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { /// FromRequest implementation for tuple #[doc(hidden)] - impl + 'static),+> FromRequest

for ($($T,)+) + impl<$($T: FromRequest + 'static),+> FromRequest for ($($T,)+) { type Error = Error; - type Future = $fut_type; + type Future = $fut_type<$($T),+>; - fn from_request(req: &HttpRequest, payload: &mut Payload

) -> Self::Future { + fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { $fut_type { items: <($(Option<$T>,)+)>::default(), futs: ($($T::from_request(req, payload).into_future(),)+), @@ -193,12 +193,12 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { } #[doc(hidden)] - pub struct $fut_type),+> { + pub struct $fut_type<$($T: FromRequest),+> { items: ($(Option<$T>,)+), futs: ($(<$T::Future as futures::IntoFuture>::Future,)+), } - impl),+> Future for $fut_type + impl<$($T: FromRequest),+> Future for $fut_type<$($T),+> { type Item = ($($T,)+); type Error = Error; diff --git a/src/handler.rs b/src/handler.rs index 42a9d88d7..f328cd25d 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -242,13 +242,13 @@ where } /// Extract arguments from request -pub struct Extract, S> { +pub struct Extract { config: Rc>>>, service: S, - _t: PhantomData<(P, T)>, + _t: PhantomData, } -impl, S> Extract { +impl Extract { pub fn new(config: Rc>>>, service: S) -> Self { Extract { config, @@ -258,16 +258,16 @@ impl, S> Extract { } } -impl, S> NewService for Extract +impl NewService for Extract where S: Service + Clone, { - type Request = ServiceRequest

; + type Request = ServiceRequest; type Response = ServiceResponse; - type Error = (Error, ServiceRequest

); + type Error = (Error, ServiceRequest); type InitError = (); - type Service = ExtractService; + type Service = ExtractService; type Future = FutureResult; fn new_service(&self, _: &()) -> Self::Future { @@ -279,27 +279,27 @@ where } } -pub struct ExtractService, S> { +pub struct ExtractService { config: Option>, service: S, - _t: PhantomData<(P, T)>, + _t: PhantomData, } -impl, S> Service for ExtractService +impl Service for ExtractService where S: Service + Clone, { - type Request = ServiceRequest

; + type Request = ServiceRequest; type Response = ServiceResponse; - type Error = (Error, ServiceRequest

); - type Future = ExtractResponse; + type Error = (Error, ServiceRequest); + type Future = ExtractResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { Ok(Async::Ready(())) } - fn call(&mut self, req: ServiceRequest

) -> Self::Future { + fn call(&mut self, req: ServiceRequest) -> Self::Future { let (mut req, mut payload) = req.into_parts(); req.set_route_data(self.config.clone()); let fut = T::from_request(&req, &mut payload).into_future(); @@ -313,19 +313,19 @@ where } } -pub struct ExtractResponse, S: Service> { - req: Option<(HttpRequest, Payload

)>, +pub struct ExtractResponse { + req: Option<(HttpRequest, Payload)>, service: S, fut: ::Future, fut_s: Option, } -impl, S> Future for ExtractResponse +impl Future for ExtractResponse where S: Service, { type Item = ServiceResponse; - type Error = (Error, ServiceRequest

); + type Error = (Error, ServiceRequest); fn poll(&mut self) -> Poll { if let Some(ref mut fut) = self.fut_s { diff --git a/src/lib.rs b/src/lib.rs index f0600c6cd..6636d96d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -133,7 +133,6 @@ pub mod dev { //! use actix_web::dev::*; //! ``` - pub use crate::app::AppRouter; pub use crate::config::{AppConfig, ServiceConfig}; pub use crate::info::ConnectionInfo; pub use crate::rmap::ResourceMap; @@ -143,6 +142,7 @@ pub mod dev { pub use crate::types::readlines::Readlines; pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody}; + pub use actix_http::encoding::Decoder as Decompress; pub use actix_http::ResponseBuilder as HttpResponseBuilder; pub use actix_http::{ Extensions, Payload, PayloadStream, RequestHead, ResponseHead, diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index a4b6a4608..d5c4082ea 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -41,11 +41,11 @@ impl BodyEncoding for Response { /// To disable compression set encoding to `ContentEncoding::Identity` value. /// /// ```rust -/// use actix_web::{web, middleware::encoding, App, HttpResponse}; +/// use actix_web::{web, middleware, App, HttpResponse}; /// /// fn main() { /// let app = App::new() -/// .wrap(encoding::Compress::default()) +/// .wrap(middleware::Compress::default()) /// .service( /// web::resource("/test") /// .route(web::get().to(|| HttpResponse::Ok())) @@ -68,12 +68,12 @@ impl Default for Compress { } } -impl Transform for Compress +impl Transform for Compress where B: MessageBody, - S: Service, Response = ServiceResponse>, + S: Service>, { - type Request = ServiceRequest

; + type Request = ServiceRequest; type Response = ServiceResponse>; type Error = S::Error; type InitError = (); @@ -93,21 +93,21 @@ pub struct CompressMiddleware { encoding: ContentEncoding, } -impl Service for CompressMiddleware +impl Service for CompressMiddleware where B: MessageBody, - S: Service, Response = ServiceResponse>, + S: Service>, { - type Request = ServiceRequest

; + type Request = ServiceRequest; type Response = ServiceResponse>; type Error = S::Error; - type Future = CompressResponse; + type Future = CompressResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.service.poll_ready() } - fn call(&mut self, req: ServiceRequest

) -> Self::Future { + fn call(&mut self, req: ServiceRequest) -> Self::Future { // negotiate content-encoding let encoding = if let Some(val) = req.headers().get(&ACCEPT_ENCODING) { if let Ok(enc) = val.to_str() { @@ -128,20 +128,20 @@ where } #[doc(hidden)] -pub struct CompressResponse +pub struct CompressResponse where S: Service, B: MessageBody, { fut: S::Future, encoding: ContentEncoding, - _t: PhantomData<(P, B)>, + _t: PhantomData<(B)>, } -impl Future for CompressResponse +impl Future for CompressResponse where B: MessageBody, - S: Service, Response = ServiceResponse>, + S: Service>, { type Item = ServiceResponse>; type Error = S::Error; diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 27d24b432..1fa6e6690 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -475,9 +475,9 @@ fn cors<'a>( parts.as_mut() } -impl IntoTransform for Cors +impl IntoTransform for Cors where - S: Service, Response = ServiceResponse>, + S: Service>, S::Future: 'static, S::Error: 'static, B: 'static, @@ -537,14 +537,14 @@ pub struct CorsFactory { inner: Rc, } -impl Transform for CorsFactory +impl Transform for CorsFactory where - S: Service, Response = ServiceResponse>, + S: Service>, S::Future: 'static, S::Error: 'static, B: 'static, { - type Request = ServiceRequest

; + type Request = ServiceRequest; type Response = ServiceResponse; type Error = S::Error; type InitError = (); @@ -678,14 +678,14 @@ impl Inner { } } -impl Service for CorsMiddleware +impl Service for CorsMiddleware where - S: Service, Response = ServiceResponse>, + S: Service>, S::Future: 'static, S::Error: 'static, B: 'static, { - type Request = ServiceRequest

; + type Request = ServiceRequest; type Response = ServiceResponse; type Error = S::Error; type Future = Either< @@ -697,7 +697,7 @@ where self.service.poll_ready() } - fn call(&mut self, req: ServiceRequest

) -> Self::Future { + fn call(&mut self, req: ServiceRequest) -> Self::Future { if self.inner.preflight && Method::OPTIONS == *req.method() { if let Err(e) = self .inner @@ -815,13 +815,12 @@ mod tests { use actix_service::{FnService, Transform}; use super::*; - use crate::dev::PayloadStream; use crate::test::{self, block_on, TestRequest}; impl Cors { - fn finish(self, srv: S) -> CorsMiddleware + fn finish(self, srv: S) -> CorsMiddleware where - S: Service, Response = ServiceResponse> + S: Service> + 'static, S::Future: 'static, S::Error: 'static, @@ -1057,7 +1056,7 @@ mod tests { .allowed_headers(exposed_headers.clone()) .expose_headers(exposed_headers.clone()) .allowed_header(header::CONTENT_TYPE) - .finish(FnService::new(move |req: ServiceRequest| { + .finish(FnService::new(move |req: ServiceRequest| { req.into_response( HttpResponse::Ok().header(header::VARY, "Accept").finish(), ) diff --git a/src/middleware/decompress.rs b/src/middleware/decompress.rs deleted file mode 100644 index 13735143a..000000000 --- a/src/middleware/decompress.rs +++ /dev/null @@ -1,75 +0,0 @@ -//! Chain service for decompressing request payload. -use std::marker::PhantomData; - -use actix_http::encoding::Decoder; -use actix_service::{NewService, Service}; -use bytes::Bytes; -use futures::future::{ok, FutureResult}; -use futures::{Async, Poll, Stream}; - -use crate::dev::Payload; -use crate::error::{Error, PayloadError}; -use crate::service::ServiceRequest; - -/// `Middleware` for decompressing request's payload. -/// `Decompress` middleware must be added with `App::chain()` method. -/// -/// ```rust -/// use actix_web::{web, middleware::encoding, App, HttpResponse}; -/// -/// fn main() { -/// let app = App::new() -/// .chain(encoding::Decompress::new()) -/// .service( -/// web::resource("/test") -/// .route(web::get().to(|| HttpResponse::Ok())) -/// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) -/// ); -/// } -/// ``` -pub struct Decompress

(PhantomData

); - -impl

Decompress

-where - P: Stream, -{ - pub fn new() -> Self { - Decompress(PhantomData) - } -} - -impl

NewService for Decompress

-where - P: Stream, -{ - type Request = ServiceRequest

; - type Response = ServiceRequest>>; - type Error = Error; - type InitError = (); - type Service = Decompress

; - type Future = FutureResult; - - fn new_service(&self, _: &()) -> Self::Future { - ok(Decompress(PhantomData)) - } -} - -impl

Service for Decompress

-where - P: Stream, -{ - type Request = ServiceRequest

; - type Response = ServiceRequest>>; - type Error = Error; - type Future = FutureResult; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, req: ServiceRequest

) -> Self::Future { - let (req, payload) = req.into_parts(); - let payload = Decoder::from_headers(payload, req.headers()); - ok(ServiceRequest::from_parts(req, Payload::Stream(payload))) - } -} diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index a2bc6f27a..c0e62e285 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -85,12 +85,12 @@ impl DefaultHeaders { } } -impl Transform for DefaultHeaders +impl Transform for DefaultHeaders where - S: Service, Response = ServiceResponse>, + S: Service>, S::Future: 'static, { - type Request = ServiceRequest

; + type Request = ServiceRequest; type Response = ServiceResponse; type Error = S::Error; type InitError = (); @@ -110,12 +110,12 @@ pub struct DefaultHeadersMiddleware { inner: Rc, } -impl Service for DefaultHeadersMiddleware +impl Service for DefaultHeadersMiddleware where - S: Service, Response = ServiceResponse>, + S: Service>, S::Future: 'static, { - type Request = ServiceRequest

; + type Request = ServiceRequest; type Response = ServiceResponse; type Error = S::Error; type Future = Box>; @@ -124,7 +124,7 @@ where self.service.poll_ready() } - fn call(&mut self, req: ServiceRequest

) -> Self::Future { + fn call(&mut self, req: ServiceRequest) -> Self::Future { let inner = self.inner.clone(); Box::new(self.service.call(req).map(move |mut res| { @@ -171,7 +171,7 @@ mod tests { assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); let req = TestRequest::default().to_srv_request(); - let srv = FnService::new(|req: ServiceRequest<_>| { + let srv = FnService::new(|req: ServiceRequest| { req.into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish()) }); let mut mw = block_on( @@ -186,7 +186,7 @@ mod tests { #[test] fn test_content_type() { - let srv = FnService::new(|req: ServiceRequest<_>| { + let srv = FnService::new(|req: ServiceRequest| { req.into_response(HttpResponse::Ok().finish()) }); let mut mw = diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index a69bdaf9f..56745630b 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -81,18 +81,14 @@ impl ErrorHandlers { } } -impl Transform for ErrorHandlers +impl Transform for ErrorHandlers where - S: Service< - Request = ServiceRequest

, - Response = ServiceResponse, - Error = Error, - >, + S: Service, Error = Error>, S::Future: 'static, S::Error: 'static, B: 'static, { - type Request = ServiceRequest

; + type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); @@ -113,18 +109,14 @@ pub struct ErrorHandlersMiddleware { handlers: Rc>>>, } -impl Service for ErrorHandlersMiddleware +impl Service for ErrorHandlersMiddleware where - S: Service< - Request = ServiceRequest

, - Response = ServiceResponse, - Error = Error, - >, + S: Service, Error = Error>, S::Future: 'static, S::Error: 'static, B: 'static, { - type Request = ServiceRequest

; + type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Future = Box>; @@ -133,7 +125,7 @@ where self.service.poll_ready() } - fn call(&mut self, req: ServiceRequest

) -> Self::Future { + fn call(&mut self, req: ServiceRequest) -> Self::Future { let handlers = self.handlers.clone(); Box::new(self.service.call(req).and_then(move |res| { @@ -169,7 +161,7 @@ mod tests { #[test] fn test_handler() { - let srv = FnService::new(|req: ServiceRequest<_>| { + let srv = FnService::new(|req: ServiceRequest| { req.into_response(HttpResponse::InternalServerError().finish()) }); @@ -195,7 +187,7 @@ mod tests { #[test] fn test_handler_async() { - let srv = FnService::new(|req: ServiceRequest<_>| { + let srv = FnService::new(|req: ServiceRequest| { req.into_response(HttpResponse::InternalServerError().finish()) }); diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index e263099f4..8dd2ddb80 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -140,12 +140,12 @@ struct IdentityItem { /// } /// # fn main() {} /// ``` -impl

FromRequest

for Identity { +impl FromRequest for Identity { type Error = Error; type Future = Result; #[inline] - fn from_request(req: &HttpRequest, _: &mut Payload

) -> Self::Future { + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { Ok(Identity(req.clone())) } } @@ -159,7 +159,7 @@ pub trait IdentityPolicy: Sized + 'static { type ResponseFuture: IntoFuture; /// Parse the session from request and load data from a service identity. - fn from_request

(&self, request: &mut ServiceRequest

) -> Self::Future; + fn from_request(&self, request: &mut ServiceRequest) -> Self::Future; /// Write changes to response fn to_response( @@ -198,16 +198,15 @@ impl IdentityService { } } -impl Transform for IdentityService +impl Transform for IdentityService where - S: Service, Response = ServiceResponse> + 'static, + S: Service> + 'static, S::Future: 'static, S::Error: 'static, T: IdentityPolicy, - P: 'static, B: 'static, { - type Request = ServiceRequest

; + type Request = ServiceRequest; type Response = ServiceResponse; type Error = S::Error; type InitError = (); @@ -228,16 +227,15 @@ pub struct IdentityServiceMiddleware { service: Rc>, } -impl Service for IdentityServiceMiddleware +impl Service for IdentityServiceMiddleware where - P: 'static, B: 'static, - S: Service, Response = ServiceResponse> + 'static, + S: Service> + 'static, S::Future: 'static, S::Error: 'static, T: IdentityPolicy, { - type Request = ServiceRequest

; + type Request = ServiceRequest; type Response = ServiceResponse; type Error = S::Error; type Future = Box>; @@ -246,7 +244,7 @@ where self.service.borrow_mut().poll_ready() } - fn call(&mut self, mut req: ServiceRequest

) -> Self::Future { + fn call(&mut self, mut req: ServiceRequest) -> Self::Future { let srv = self.service.clone(); let backend = self.backend.clone(); @@ -348,7 +346,7 @@ impl CookieIdentityInner { Ok(()) } - fn load(&self, req: &ServiceRequest) -> Option { + fn load(&self, req: &ServiceRequest) -> Option { if let Ok(cookies) = req.cookies() { for cookie in cookies.iter() { if cookie.name() == self.name { @@ -445,7 +443,7 @@ impl IdentityPolicy for CookieIdentityPolicy { type Future = Result, Error>; type ResponseFuture = Result<(), Error>; - fn from_request

(&self, req: &mut ServiceRequest

) -> Self::Future { + fn from_request(&self, req: &mut ServiceRequest) -> Self::Future { Ok(self.0.load(req)) } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 66ca150b1..d5fca526b 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -114,12 +114,12 @@ impl Default for Logger { } } -impl Transform for Logger +impl Transform for Logger where - S: Service, Response = ServiceResponse>, + S: Service>, B: MessageBody, { - type Request = ServiceRequest

; + type Request = ServiceRequest; type Response = ServiceResponse>; type Error = S::Error; type InitError = (); @@ -140,21 +140,21 @@ pub struct LoggerMiddleware { service: S, } -impl Service for LoggerMiddleware +impl Service for LoggerMiddleware where - S: Service, Response = ServiceResponse>, + S: Service>, B: MessageBody, { - type Request = ServiceRequest

; + type Request = ServiceRequest; type Response = ServiceResponse>; type Error = S::Error; - type Future = LoggerResponse; + type Future = LoggerResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.service.poll_ready() } - fn call(&mut self, req: ServiceRequest

) -> Self::Future { + fn call(&mut self, req: ServiceRequest) -> Self::Future { if self.inner.exclude.contains(req.path()) { LoggerResponse { fut: self.service.call(req), @@ -180,7 +180,7 @@ where } #[doc(hidden)] -pub struct LoggerResponse +pub struct LoggerResponse where B: MessageBody, S: Service, @@ -188,13 +188,13 @@ where fut: S::Future, time: time::Tm, format: Option, - _t: PhantomData<(P, B)>, + _t: PhantomData<(B,)>, } -impl Future for LoggerResponse +impl Future for LoggerResponse where B: MessageBody, - S: Service, Response = ServiceResponse>, + S: Service>, { type Item = ServiceResponse>; type Error = S::Error; @@ -402,7 +402,7 @@ impl FormatText { } } - fn render_request

(&mut self, now: time::Tm, req: &ServiceRequest

) { + fn render_request(&mut self, now: time::Tm, req: &ServiceRequest) { match *self { FormatText::RequestLine => { *self = if req.query_string().is_empty() { @@ -464,7 +464,7 @@ mod tests { #[test] fn test_logger() { - let srv = FnService::new(|req: ServiceRequest<_>| { + let srv = FnService::new(|req: ServiceRequest| { req.into_response( HttpResponse::build(StatusCode::OK) .header("X-Test", "ttt") diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 6b6253fbc..59d467c03 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -1,14 +1,6 @@ //! Middlewares -#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] mod compress; -#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] -mod decompress; -#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] -pub mod encoding { - //! Middlewares for compressing/decompressing payloads. - pub use super::compress::{BodyEncoding, Compress}; - pub use super::decompress::Decompress; -} +pub use self::compress::{BodyEncoding, Compress}; pub mod cors; mod defaultheaders; diff --git a/src/request.rs b/src/request.rs index 53d848f0d..93ac954f2 100644 --- a/src/request.rs +++ b/src/request.rs @@ -265,12 +265,12 @@ impl Drop for HttpRequest { /// ); /// } /// ``` -impl

FromRequest

for HttpRequest { +impl FromRequest for HttpRequest { type Error = Error; type Future = Result; #[inline] - fn from_request(req: &HttpRequest, _: &mut Payload

) -> Self::Future { + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { Ok(req.clone()) } } diff --git a/src/resource.rs b/src/resource.rs index 313a3bc06..f0dea9810 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -17,9 +17,8 @@ use crate::responder::Responder; use crate::route::{CreateRouteService, Route, RouteService}; use crate::service::{ServiceRequest, ServiceResponse}; -type HttpService

= BoxedService, ServiceResponse, Error>; -type HttpNewService

= - BoxedNewService<(), ServiceRequest

, ServiceResponse, Error, ()>; +type HttpService = BoxedService; +type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; /// *Resource* is an entry in resources table which corresponds to requested URL. /// @@ -43,18 +42,18 @@ type HttpNewService

= /// /// If no matching route could be found, *405* response code get returned. /// Default behavior could be overriden with `default_resource()` method. -pub struct Resource> { +pub struct Resource { endpoint: T, rdef: String, name: Option, - routes: Vec>, + routes: Vec, guards: Vec>, - default: Rc>>>>, - factory_ref: Rc>>>, + default: Rc>>>, + factory_ref: Rc>>, } -impl

Resource

{ - pub fn new(path: &str) -> Resource

{ +impl Resource { + pub fn new(path: &str) -> Resource { let fref = Rc::new(RefCell::new(None)); Resource { @@ -69,11 +68,10 @@ impl

Resource

{ } } -impl Resource +impl Resource where - P: 'static, T: NewService< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -154,7 +152,7 @@ where /// # fn post_handler() {} /// # fn delete_handler() {} /// ``` - pub fn route(mut self, route: Route

) -> Self { + pub fn route(mut self, route: Route) -> Self { self.routes.push(route.finish()); self } @@ -182,7 +180,7 @@ where pub fn to(mut self, handler: F) -> Self where F: Factory + 'static, - I: FromRequest

+ 'static, + I: FromRequest + 'static, R: Responder + 'static, { self.routes.push(Route::new().to(handler)); @@ -216,7 +214,7 @@ where pub fn to_async(mut self, handler: F) -> Self where F: AsyncFactory, - I: FromRequest

+ 'static, + I: FromRequest + 'static, R: IntoFuture + 'static, R::Item: Into, R::Error: Into, @@ -236,9 +234,8 @@ where self, mw: F, ) -> Resource< - P, impl NewService< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -247,7 +244,7 @@ where where M: Transform< T::Service, - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -302,16 +299,15 @@ where self, mw: F, ) -> Resource< - P, impl NewService< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), >, > where - F: FnMut(ServiceRequest

, &mut T::Service) -> R + Clone, + F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, R: IntoFuture, { self.wrap(mw) @@ -322,10 +318,10 @@ where /// default handler from `App` or `Scope`. pub fn default_resource(mut self, f: F) -> Self where - F: FnOnce(Resource

) -> R, + F: FnOnce(Resource) -> R, R: IntoNewService, U: NewService< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, > + 'static, @@ -339,17 +335,16 @@ where } } -impl HttpServiceFactory

for Resource +impl HttpServiceFactory for Resource where - P: 'static, T: NewService< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), > + 'static, { - fn register(mut self, config: &mut ServiceConfig

) { + fn register(mut self, config: &mut ServiceConfig) { let guards = if self.guards.is_empty() { None } else { @@ -367,10 +362,10 @@ where } } -impl IntoNewService for Resource +impl IntoNewService for Resource where T: NewService< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -386,18 +381,18 @@ where } } -pub struct ResourceFactory

{ - routes: Vec>, - default: Rc>>>>, +pub struct ResourceFactory { + routes: Vec, + default: Rc>>>, } -impl NewService for ResourceFactory

{ - type Request = ServiceRequest

; +impl NewService for ResourceFactory { + type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); - type Service = ResourceService

; - type Future = CreateResourceService

; + type Service = ResourceService; + type Future = CreateResourceService; fn new_service(&self, _: &()) -> Self::Future { let default_fut = if let Some(ref default) = *self.default.borrow() { @@ -418,19 +413,19 @@ impl NewService for ResourceFactory

{ } } -enum CreateRouteServiceItem

{ - Future(CreateRouteService

), - Service(RouteService

), +enum CreateRouteServiceItem { + Future(CreateRouteService), + Service(RouteService), } -pub struct CreateResourceService

{ - fut: Vec>, - default: Option>, - default_fut: Option, Error = ()>>>, +pub struct CreateResourceService { + fut: Vec, + default: Option, + default_fut: Option>>, } -impl

Future for CreateResourceService

{ - type Item = ResourceService

; +impl Future for CreateResourceService { + type Item = ResourceService; type Error = (); fn poll(&mut self) -> Poll { @@ -477,13 +472,13 @@ impl

Future for CreateResourceService

{ } } -pub struct ResourceService

{ - routes: Vec>, - default: Option>, +pub struct ResourceService { + routes: Vec, + default: Option, } -impl

Service for ResourceService

{ - type Request = ServiceRequest

; +impl Service for ResourceService { + type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Future = Either< @@ -495,7 +490,7 @@ impl

Service for ResourceService

{ Ok(Async::Ready(())) } - fn call(&mut self, mut req: ServiceRequest

) -> Self::Future { + fn call(&mut self, mut req: ServiceRequest) -> Self::Future { for route in self.routes.iter_mut() { if route.check(&mut req) { return route.call(req); @@ -514,23 +509,23 @@ impl

Service for ResourceService

{ } #[doc(hidden)] -pub struct ResourceEndpoint

{ - factory: Rc>>>, +pub struct ResourceEndpoint { + factory: Rc>>, } -impl

ResourceEndpoint

{ - fn new(factory: Rc>>>) -> Self { +impl ResourceEndpoint { + fn new(factory: Rc>>) -> Self { ResourceEndpoint { factory } } } -impl NewService for ResourceEndpoint

{ - type Request = ServiceRequest

; +impl NewService for ResourceEndpoint { + type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); - type Service = ResourceService

; - type Future = CreateResourceService

; + type Service = ResourceService; + type Future = CreateResourceService; fn new_service(&self, _: &()) -> Self::Future { self.factory.borrow_mut().as_mut().unwrap().new_service(&()) @@ -550,13 +545,13 @@ mod tests { use crate::test::{call_success, init_service, TestRequest}; use crate::{web, App, Error, HttpResponse}; - fn md( - req: ServiceRequest

, + fn md( + req: ServiceRequest, srv: &mut S, ) -> impl IntoFuture, Error = Error> where S: Service< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, >, diff --git a/src/route.rs b/src/route.rs index 8bff863fe..eb911b307 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,5 +1,4 @@ use std::cell::RefCell; -use std::marker::PhantomData; use std::rc::Rc; use actix_http::{http::Method, Error, Extensions, Response}; @@ -42,16 +41,16 @@ type BoxedRouteNewService = Box< /// /// Route uses builder-like pattern for configuration. /// If handler is not explicitly set, default *404 Not Found* handler is used. -pub struct Route

{ - service: BoxedRouteNewService, ServiceResponse>, +pub struct Route { + service: BoxedRouteNewService, guards: Rc>>, data: Option, data_ref: Rc>>>, } -impl Route

{ +impl Route { /// Create new route which matches any request. - pub fn new() -> Route

{ + pub fn new() -> Route { let data_ref = Rc::new(RefCell::new(None)); Route { service: Box::new(RouteNewService::new(Extract::new( @@ -74,13 +73,13 @@ impl Route

{ } } -impl

NewService for Route

{ - type Request = ServiceRequest

; +impl NewService for Route { + type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); - type Service = RouteService

; - type Future = CreateRouteService

; + type Service = RouteService; + type Future = CreateRouteService; fn new_service(&self, _: &()) -> Self::Future { CreateRouteService { @@ -90,17 +89,16 @@ impl

NewService for Route

{ } } -type RouteFuture

= Box< - Future, ServiceResponse>, Error = ()>, ->; +type RouteFuture = + Box, Error = ()>>; -pub struct CreateRouteService

{ - fut: RouteFuture

, +pub struct CreateRouteService { + fut: RouteFuture, guards: Rc>>, } -impl

Future for CreateRouteService

{ - type Item = RouteService

; +impl Future for CreateRouteService { + type Item = RouteService; type Error = (); fn poll(&mut self) -> Poll { @@ -114,13 +112,13 @@ impl

Future for CreateRouteService

{ } } -pub struct RouteService

{ - service: BoxedRouteService, ServiceResponse>, +pub struct RouteService { + service: BoxedRouteService, guards: Rc>>, } -impl

RouteService

{ - pub fn check(&self, req: &mut ServiceRequest

) -> bool { +impl RouteService { + pub fn check(&self, req: &mut ServiceRequest) -> bool { for f in self.guards.iter() { if !f.check(req.head()) { return false; @@ -130,8 +128,8 @@ impl

RouteService

{ } } -impl

Service for RouteService

{ - type Request = ServiceRequest

; +impl Service for RouteService { + type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Future = Either< @@ -143,12 +141,12 @@ impl

Service for RouteService

{ self.service.poll_ready() } - fn call(&mut self, req: ServiceRequest

) -> Self::Future { + fn call(&mut self, req: ServiceRequest) -> Self::Future { self.service.call(req) } } -impl Route

{ +impl Route { /// Add method guard to the route. /// /// ```rust @@ -235,10 +233,10 @@ impl Route

{ /// ); /// } /// ``` - pub fn to(mut self, handler: F) -> Route

+ pub fn to(mut self, handler: F) -> Route where F: Factory + 'static, - T: FromRequest

+ 'static, + T: FromRequest + 'static, R: Responder + 'static, { self.service = Box::new(RouteNewService::new(Extract::new( @@ -278,7 +276,7 @@ impl Route

{ pub fn to_async(mut self, handler: F) -> Self where F: AsyncFactory, - T: FromRequest

+ 'static, + T: FromRequest + 'static, R: IntoFuture + 'static, R::Item: Into, R::Error: Into, @@ -321,49 +319,45 @@ impl Route

{ } } -struct RouteNewService +struct RouteNewService where - T: NewService, Error = (Error, ServiceRequest

)>, + T: NewService, { service: T, - _t: PhantomData

, } -impl RouteNewService +impl RouteNewService where T: NewService< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, - Error = (Error, ServiceRequest

), + Error = (Error, ServiceRequest), >, T::Future: 'static, T::Service: 'static, ::Future: 'static, { pub fn new(service: T) -> Self { - RouteNewService { - service, - _t: PhantomData, - } + RouteNewService { service } } } -impl NewService for RouteNewService +impl NewService for RouteNewService where T: NewService< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, - Error = (Error, ServiceRequest

), + Error = (Error, ServiceRequest), >, T::Future: 'static, T::Service: 'static, ::Future: 'static, { - type Request = ServiceRequest

; + type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); - type Service = BoxedRouteService, Self::Response>; + type Service = BoxedRouteService; type Future = Box>; fn new_service(&self, _: &()) -> Self::Future { @@ -373,31 +367,27 @@ where .map_err(|_| ()) .and_then(|service| { let service: BoxedRouteService<_, _> = - Box::new(RouteServiceWrapper { - service, - _t: PhantomData, - }); + Box::new(RouteServiceWrapper { service }); Ok(service) }), ) } } -struct RouteServiceWrapper { +struct RouteServiceWrapper { service: T, - _t: PhantomData

, } -impl Service for RouteServiceWrapper +impl Service for RouteServiceWrapper where T::Future: 'static, T: Service< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, - Error = (Error, ServiceRequest

), + Error = (Error, ServiceRequest), >, { - type Request = ServiceRequest

; + type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Future = Either< @@ -409,7 +399,7 @@ where self.service.poll_ready().map_err(|(e, _)| e) } - fn call(&mut self, req: ServiceRequest

) -> Self::Future { + fn call(&mut self, req: ServiceRequest) -> Self::Future { let mut fut = self.service.call(req); match fut.poll() { Ok(Async::Ready(res)) => Either::A(ok(res)), diff --git a/src/scope.rs b/src/scope.rs index 2cb01961a..62badc86a 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -21,9 +21,8 @@ use crate::service::{ }; type Guards = Vec>; -type HttpService

= BoxedService, ServiceResponse, Error>; -type HttpNewService

= - BoxedNewService<(), ServiceRequest

, ServiceResponse, Error, ()>; +type HttpService = BoxedService; +type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; type BoxedResponse = Either< FutureResult, Box>, @@ -58,18 +57,18 @@ type BoxedResponse = Either< /// * /{project_id}/path2 - `GET` requests /// * /{project_id}/path3 - `HEAD` requests /// -pub struct Scope> { +pub struct Scope { endpoint: T, rdef: String, - services: Vec>>, + services: Vec>, guards: Vec>, - default: Rc>>>>, - factory_ref: Rc>>>, + default: Rc>>>, + factory_ref: Rc>>, } -impl Scope

{ +impl Scope { /// Create a new scope - pub fn new(path: &str) -> Scope

{ + pub fn new(path: &str) -> Scope { let fref = Rc::new(RefCell::new(None)); Scope { endpoint: ScopeEndpoint::new(fref.clone()), @@ -82,11 +81,10 @@ impl Scope

{ } } -impl Scope +impl Scope where - P: 'static, T: NewService< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -146,7 +144,7 @@ where /// ``` pub fn service(mut self, factory: F) -> Self where - F: HttpServiceFactory

+ 'static, + F: HttpServiceFactory + 'static, { self.services .push(Box::new(ServiceFactoryWrapper::new(factory))); @@ -174,7 +172,7 @@ where /// ); /// } /// ``` - pub fn route(self, path: &str, mut route: Route

) -> Self { + pub fn route(self, path: &str, mut route: Route) -> Self { self.service( Resource::new(path) .add_guards(route.take_guards()) @@ -187,9 +185,9 @@ where /// If default resource is not registered, app's default resource is being used. pub fn default_resource(mut self, f: F) -> Self where - F: FnOnce(Resource

) -> Resource, + F: FnOnce(Resource) -> Resource, U: NewService< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -216,9 +214,8 @@ where self, mw: F, ) -> Scope< - P, impl NewService< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -227,7 +224,7 @@ where where M: Transform< T::Service, - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -279,33 +276,31 @@ where self, mw: F, ) -> Scope< - P, impl NewService< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), >, > where - F: FnMut(ServiceRequest

, &mut T::Service) -> R + Clone, + F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, R: IntoFuture, { self.wrap(mw) } } -impl HttpServiceFactory

for Scope +impl HttpServiceFactory for Scope where - P: 'static, T: NewService< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), > + 'static, { - fn register(self, config: &mut ServiceConfig

) { + fn register(self, config: &mut ServiceConfig) { // update default resource if needed if self.default.borrow().is_none() { *self.default.borrow_mut() = Some(config.default_service()); @@ -350,18 +345,18 @@ where } } -pub struct ScopeFactory

{ - services: Rc, RefCell>)>>, - default: Rc>>>>, +pub struct ScopeFactory { + services: Rc>)>>, + default: Rc>>>, } -impl NewService for ScopeFactory

{ - type Request = ServiceRequest

; +impl NewService for ScopeFactory { + type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); - type Service = ScopeService

; - type Future = ScopeFactoryResponse

; + type Service = ScopeService; + type Future = ScopeFactoryResponse; fn new_service(&self, _: &()) -> Self::Future { let default_fut = if let Some(ref default) = *self.default.borrow() { @@ -390,21 +385,21 @@ impl NewService for ScopeFactory

{ /// Create scope service #[doc(hidden)] -pub struct ScopeFactoryResponse

{ - fut: Vec>, - default: Option>, - default_fut: Option, Error = ()>>>, +pub struct ScopeFactoryResponse { + fut: Vec, + default: Option, + default_fut: Option>>, } -type HttpServiceFut

= Box, Error = ()>>; +type HttpServiceFut = Box>; -enum CreateScopeServiceItem

{ - Future(Option, Option, HttpServiceFut

), - Service(ResourceDef, Option, HttpService

), +enum CreateScopeServiceItem { + Future(Option, Option, HttpServiceFut), + Service(ResourceDef, Option, HttpService), } -impl

Future for ScopeFactoryResponse

{ - type Item = ScopeService

; +impl Future for ScopeFactoryResponse { + type Item = ScopeService; type Error = (); fn poll(&mut self) -> Poll { @@ -465,14 +460,14 @@ impl

Future for ScopeFactoryResponse

{ } } -pub struct ScopeService

{ - router: Router, Vec>>, - default: Option>, - _ready: Option<(ServiceRequest

, ResourceInfo)>, +pub struct ScopeService { + router: Router>>, + default: Option, + _ready: Option<(ServiceRequest, ResourceInfo)>, } -impl

Service for ScopeService

{ - type Request = ServiceRequest

; +impl Service for ScopeService { + type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Future = Either>; @@ -481,7 +476,7 @@ impl

Service for ScopeService

{ Ok(Async::Ready(())) } - fn call(&mut self, mut req: ServiceRequest

) -> Self::Future { + fn call(&mut self, mut req: ServiceRequest) -> Self::Future { let res = self.router.recognize_mut_checked(&mut req, |req, guards| { if let Some(ref guards) = guards { for f in guards { @@ -505,23 +500,23 @@ impl

Service for ScopeService

{ } #[doc(hidden)] -pub struct ScopeEndpoint

{ - factory: Rc>>>, +pub struct ScopeEndpoint { + factory: Rc>>, } -impl

ScopeEndpoint

{ - fn new(factory: Rc>>>) -> Self { +impl ScopeEndpoint { + fn new(factory: Rc>>) -> Self { ScopeEndpoint { factory } } } -impl NewService for ScopeEndpoint

{ - type Request = ServiceRequest

; +impl NewService for ScopeEndpoint { + type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); - type Service = ScopeService

; - type Future = ScopeFactoryResponse

; + type Service = ScopeService; + type Future = ScopeFactoryResponse; fn new_service(&self, _: &()) -> Self::Future { self.factory.borrow_mut().as_mut().unwrap().new_service(&()) @@ -886,13 +881,13 @@ mod tests { assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); } - fn md( - req: ServiceRequest

, + fn md( + req: ServiceRequest, srv: &mut S, ) -> impl IntoFuture, Error = Error> where S: Service< - Request = ServiceRequest

, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, >, diff --git a/src/service.rs b/src/service.rs index 01875854e..2817cc0b8 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,6 +1,5 @@ use std::cell::{Ref, RefMut}; use std::fmt; -use std::marker::PhantomData; use actix_http::body::{Body, MessageBody, ResponseBody}; use actix_http::http::{HeaderMap, Method, StatusCode, Uri, Version}; @@ -15,52 +14,50 @@ use crate::config::{AppConfig, ServiceConfig}; use crate::data::Data; use crate::request::HttpRequest; -pub trait HttpServiceFactory

{ - fn register(self, config: &mut ServiceConfig

); +pub trait HttpServiceFactory { + fn register(self, config: &mut ServiceConfig); } -pub(crate) trait ServiceFactory

{ - fn register(&mut self, config: &mut ServiceConfig

); +pub(crate) trait ServiceFactory { + fn register(&mut self, config: &mut ServiceConfig); } -pub(crate) struct ServiceFactoryWrapper { +pub(crate) struct ServiceFactoryWrapper { factory: Option, - _t: PhantomData

, } -impl ServiceFactoryWrapper { +impl ServiceFactoryWrapper { pub fn new(factory: T) -> Self { Self { factory: Some(factory), - _t: PhantomData, } } } -impl ServiceFactory

for ServiceFactoryWrapper +impl ServiceFactory for ServiceFactoryWrapper where - T: HttpServiceFactory

, + T: HttpServiceFactory, { - fn register(&mut self, config: &mut ServiceConfig

) { + fn register(&mut self, config: &mut ServiceConfig) { if let Some(item) = self.factory.take() { item.register(config) } } } -pub struct ServiceRequest

{ +pub struct ServiceRequest { req: HttpRequest, - payload: Payload

, + payload: Payload, } -impl

ServiceRequest

{ +impl ServiceRequest { /// Construct service request from parts - pub fn from_parts(req: HttpRequest, payload: Payload

) -> Self { + pub fn from_parts(req: HttpRequest, payload: Payload) -> Self { ServiceRequest { req, payload } } /// Deconstruct request into parts - pub fn into_parts(self) -> (HttpRequest, Payload

) { + pub fn into_parts(self) -> (HttpRequest, Payload) { (self.req, self.payload) } @@ -170,14 +167,14 @@ impl

ServiceRequest

{ } } -impl

Resource for ServiceRequest

{ +impl Resource for ServiceRequest { fn resource_path(&mut self) -> &mut Path { self.match_info_mut() } } -impl

HttpMessage for ServiceRequest

{ - type Stream = P; +impl HttpMessage for ServiceRequest { + type Stream = PayloadStream; #[inline] /// Returns Request's headers. @@ -203,7 +200,7 @@ impl

HttpMessage for ServiceRequest

{ } } -impl

fmt::Debug for ServiceRequest

{ +impl fmt::Debug for ServiceRequest { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!( f, diff --git a/src/test.rs b/src/test.rs index f52aefc48..7cdf44854 100644 --- a/src/test.rs +++ b/src/test.rs @@ -6,7 +6,7 @@ use actix_http::cookie::Cookie; use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; use actix_http::http::{HttpTryFrom, Method, StatusCode, Version}; use actix_http::test::TestRequest as HttpTestRequest; -use actix_http::{Extensions, PayloadStream, Request}; +use actix_http::{Extensions, Request}; use actix_router::{Path, ResourceDef, Url}; use actix_rt::Runtime; use actix_server_config::ServerConfig; @@ -60,23 +60,18 @@ where } /// Create service that always responds with `HttpResponse::Ok()` -pub fn ok_service() -> impl Service< - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, -> { +pub fn ok_service( +) -> impl Service, Error = Error> +{ default_service(StatusCode::OK) } /// Create service that responds with response with specified status code pub fn default_service( status_code: StatusCode, -) -> impl Service< - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, -> { - FnService::new(move |req: ServiceRequest| { +) -> impl Service, Error = Error> +{ + FnService::new(move |req: ServiceRequest| { req.into_response(HttpResponse::build(status_code).finish()) }) } @@ -298,12 +293,12 @@ impl TestRequest { } /// Complete request creation and generate `Request` instance - pub fn to_request(mut self) -> Request { + pub fn to_request(mut self) -> Request { self.req.finish() } /// Complete request creation and generate `ServiceRequest` instance - pub fn to_srv_request(mut self) -> ServiceRequest { + pub fn to_srv_request(mut self) -> ServiceRequest { let (head, payload) = self.req.finish().into_parts(); let req = HttpRequest::new( diff --git a/src/types/form.rs b/src/types/form.rs index 2c876e260..c2e8c63bc 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -3,15 +3,15 @@ use std::rc::Rc; use std::{fmt, ops}; -use actix_http::error::{Error, PayloadError}; -use actix_http::{HttpMessage, Payload}; -use bytes::{Bytes, BytesMut}; +use actix_http::{Error, HttpMessage, Payload}; +use bytes::BytesMut; use encoding::all::UTF_8; use encoding::types::{DecoderTrap, Encoding}; use encoding::EncodingRef; use futures::{Future, Poll, Stream}; use serde::de::DeserializeOwned; +use crate::dev::Decompress; use crate::error::UrlencodedError; use crate::extract::FromRequest; use crate::http::header::CONTENT_LENGTH; @@ -69,16 +69,15 @@ impl ops::DerefMut for Form { } } -impl FromRequest

for Form +impl FromRequest for Form where T: DeserializeOwned + 'static, - P: Stream + 'static, { type Error = Error; type Future = Box>; #[inline] - fn from_request(req: &HttpRequest, payload: &mut Payload

) -> Self::Future { + fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { let req2 = req.clone(); let (limit, err) = req .route_data::() @@ -182,8 +181,8 @@ impl Default for FormConfig { /// * content type is not `application/x-www-form-urlencoded` /// * content-length is greater than 32k /// -pub struct UrlEncoded { - stream: Payload

, +pub struct UrlEncoded { + stream: Option>, limit: usize, length: Option, encoding: EncodingRef, @@ -191,12 +190,9 @@ pub struct UrlEncoded { fut: Option>>, } -impl UrlEncoded -where - P: Stream, -{ +impl UrlEncoded { /// Create a new future to URL encode a request - pub fn new(req: &HttpRequest, payload: &mut Payload

) -> UrlEncoded { + pub fn new(req: &HttpRequest, payload: &mut Payload) -> UrlEncoded { // check content type if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { return Self::err(UrlencodedError::ContentType); @@ -219,9 +215,10 @@ where } }; + let payload = Decompress::from_headers(payload.take(), req.headers()); UrlEncoded { encoding, - stream: payload.take(), + stream: Some(payload), limit: 32_768, length: len, fut: None, @@ -231,7 +228,7 @@ where fn err(e: UrlencodedError) -> Self { UrlEncoded { - stream: Payload::None, + stream: None, limit: 32_768, fut: None, err: Some(e), @@ -247,9 +244,8 @@ where } } -impl Future for UrlEncoded +impl Future for UrlEncoded where - P: Stream + 'static, U: DeserializeOwned + 'static, { type Item = U; @@ -274,7 +270,10 @@ where // future let encoding = self.encoding; - let fut = std::mem::replace(&mut self.stream, Payload::None) + let fut = self + .stream + .take() + .unwrap() .from_err() .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { if (body.len() + chunk.len()) > limit { @@ -355,20 +354,20 @@ mod tests { TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") .header(CONTENT_LENGTH, "xxxx") .to_http_parts(); - let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl)); + let info = block_on(UrlEncoded::::new(&req, &mut pl)); assert!(eq(info.err().unwrap(), UrlencodedError::UnknownLength)); let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") .header(CONTENT_LENGTH, "1000000") .to_http_parts(); - let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl)); + let info = block_on(UrlEncoded::::new(&req, &mut pl)); assert!(eq(info.err().unwrap(), UrlencodedError::Overflow)); let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "text/plain") .header(CONTENT_LENGTH, "10") .to_http_parts(); - let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl)); + let info = block_on(UrlEncoded::::new(&req, &mut pl)); assert!(eq(info.err().unwrap(), UrlencodedError::ContentType)); } @@ -380,7 +379,7 @@ mod tests { .set_payload(Bytes::from_static(b"hello=world")) .to_http_parts(); - let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl)).unwrap(); + let info = block_on(UrlEncoded::::new(&req, &mut pl)).unwrap(); assert_eq!( info, Info { @@ -396,7 +395,7 @@ mod tests { .set_payload(Bytes::from_static(b"hello=world")) .to_http_parts(); - let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl)).unwrap(); + let info = block_on(UrlEncoded::::new(&req, &mut pl)).unwrap(); assert_eq!( info, Info { diff --git a/src/types/json.rs b/src/types/json.rs index 5044cf70c..d59136225 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use std::{fmt, ops}; -use bytes::{Bytes, BytesMut}; +use bytes::BytesMut; use futures::{Future, Poll, Stream}; use serde::de::DeserializeOwned; use serde::Serialize; @@ -12,7 +12,8 @@ use serde_json; use actix_http::http::{header::CONTENT_LENGTH, StatusCode}; use actix_http::{HttpMessage, Payload, Response}; -use crate::error::{Error, JsonPayloadError, PayloadError}; +use crate::dev::Decompress; +use crate::error::{Error, JsonPayloadError}; use crate::extract::FromRequest; use crate::request::HttpRequest; use crate::responder::Responder; @@ -163,16 +164,15 @@ impl Responder for Json { /// ); /// } /// ``` -impl FromRequest

for Json +impl FromRequest for Json where T: DeserializeOwned + 'static, - P: Stream + 'static, { type Error = Error; type Future = Box>; #[inline] - fn from_request(req: &HttpRequest, payload: &mut Payload

) -> Self::Future { + fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { let req2 = req.clone(); let (limit, err) = req .route_data::() @@ -270,21 +270,20 @@ impl Default for JsonConfig { /// /// * content type is not `application/json` /// * content length is greater than 256k -pub struct JsonBody { +pub struct JsonBody { limit: usize, length: Option, - stream: Payload

, + stream: Option>, err: Option, fut: Option>>, } -impl JsonBody +impl JsonBody where - P: Stream + 'static, U: DeserializeOwned + 'static, { /// Create `JsonBody` for request. - pub fn new(req: &HttpRequest, payload: &mut Payload

) -> Self { + pub fn new(req: &HttpRequest, payload: &mut Payload) -> Self { // check content-type let json = if let Ok(Some(mime)) = req.mime_type() { mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) @@ -295,7 +294,7 @@ where return JsonBody { limit: 262_144, length: None, - stream: Payload::None, + stream: None, fut: None, err: Some(JsonPayloadError::ContentType), }; @@ -309,11 +308,12 @@ where } } } + let payload = Decompress::from_headers(payload.take(), req.headers()); JsonBody { limit: 262_144, length: len, - stream: payload.take(), + stream: Some(payload), fut: None, err: None, } @@ -326,9 +326,8 @@ where } } -impl Future for JsonBody +impl Future for JsonBody where - P: Stream + 'static, U: DeserializeOwned + 'static, { type Item = U; @@ -350,7 +349,10 @@ where } } - let fut = std::mem::replace(&mut self.stream, Payload::None) + let fut = self + .stream + .take() + .unwrap() .from_err() .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { if (body.len() + chunk.len()) > limit { @@ -508,7 +510,7 @@ mod tests { #[test] fn test_json_body() { let (req, mut pl) = TestRequest::default().to_http_parts(); - let json = block_on(JsonBody::<_, MyObject>::new(&req, &mut pl)); + let json = block_on(JsonBody::::new(&req, &mut pl)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); let (req, mut pl) = TestRequest::default() @@ -517,7 +519,7 @@ mod tests { header::HeaderValue::from_static("application/text"), ) .to_http_parts(); - let json = block_on(JsonBody::<_, MyObject>::new(&req, &mut pl)); + let json = block_on(JsonBody::::new(&req, &mut pl)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); let (req, mut pl) = TestRequest::default() @@ -531,7 +533,7 @@ mod tests { ) .to_http_parts(); - let json = block_on(JsonBody::<_, MyObject>::new(&req, &mut pl).limit(100)); + let json = block_on(JsonBody::::new(&req, &mut pl).limit(100)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow)); let (req, mut pl) = TestRequest::default() @@ -546,7 +548,7 @@ mod tests { .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .to_http_parts(); - let json = block_on(JsonBody::<_, MyObject>::new(&req, &mut pl)); + let json = block_on(JsonBody::::new(&req, &mut pl)); assert_eq!( json.ok().unwrap(), MyObject { diff --git a/src/types/path.rs b/src/types/path.rs index d8334679a..47ec1f562 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -152,7 +152,7 @@ impl fmt::Display for Path { /// ); /// } /// ``` -impl FromRequest

for Path +impl FromRequest for Path where T: de::DeserializeOwned, { @@ -160,7 +160,7 @@ where type Future = Result; #[inline] - fn from_request(req: &HttpRequest, _: &mut Payload

) -> Self::Future { + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { de::Deserialize::deserialize(PathDeserializer::new(req.match_info())) .map(|inner| Path { inner }) .map_err(ErrorNotFound) diff --git a/src/types/payload.rs b/src/types/payload.rs index 4c7dbdcc6..3dac828cb 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -44,7 +44,7 @@ use crate::request::HttpRequest; /// ); /// } /// ``` -pub struct Payload(crate::dev::Payload>>); +pub struct Payload(crate::dev::Payload); impl Stream for Payload { type Item = Bytes; @@ -85,26 +85,13 @@ impl Stream for Payload { /// ); /// } /// ``` -impl

FromRequest

for Payload -where - P: Stream + 'static, -{ +impl FromRequest for Payload { type Error = Error; type Future = Result; #[inline] - fn from_request(_: &HttpRequest, payload: &mut dev::Payload

) -> Self::Future { - let pl = match payload.take() { - crate::dev::Payload::Stream(s) => { - let pl: Box> = - Box::new(s); - crate::dev::Payload::Stream(pl) - } - crate::dev::Payload::None => crate::dev::Payload::None, - crate::dev::Payload::H1(pl) => crate::dev::Payload::H1(pl), - crate::dev::Payload::H2(pl) => crate::dev::Payload::H2(pl), - }; - Ok(Payload(pl)) + fn from_request(_: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { + Ok(Payload(payload.take())) } } @@ -133,16 +120,13 @@ where /// ); /// } /// ``` -impl

FromRequest

for Bytes -where - P: Stream + 'static, -{ +impl FromRequest for Bytes { type Error = Error; type Future = Either>, FutureResult>; #[inline] - fn from_request(req: &HttpRequest, payload: &mut dev::Payload

) -> Self::Future { + fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { let mut tmp; let cfg = if let Some(cfg) = req.route_data::() { cfg @@ -188,16 +172,13 @@ where /// ); /// } /// ``` -impl

FromRequest

for String -where - P: Stream + 'static, -{ +impl FromRequest for String { type Error = Error; type Future = Either>, FutureResult>; #[inline] - fn from_request(req: &HttpRequest, payload: &mut dev::Payload

) -> Self::Future { + fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { let mut tmp; let cfg = if let Some(cfg) = req.route_data::() { cfg @@ -300,20 +281,17 @@ impl Default for PayloadConfig { /// By default only 256Kb payload reads to a memory, then /// `PayloadError::Overflow` get returned. Use `MessageBody::limit()` /// method to change upper limit. -pub struct HttpMessageBody

{ +pub struct HttpMessageBody { limit: usize, length: Option, - stream: dev::Payload

, + stream: Option>, err: Option, fut: Option>>, } -impl

HttpMessageBody

-where - P: Stream, -{ +impl HttpMessageBody { /// Create `MessageBody` for request. - pub fn new(req: &HttpRequest, payload: &mut dev::Payload

) -> HttpMessageBody

{ + pub fn new(req: &HttpRequest, payload: &mut dev::Payload) -> HttpMessageBody { let mut len = None; if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) { if let Ok(s) = l.to_str() { @@ -328,7 +306,7 @@ where } HttpMessageBody { - stream: payload.take(), + stream: Some(dev::Decompress::from_headers(payload.take(), req.headers())), limit: 262_144, length: len, fut: None, @@ -344,7 +322,7 @@ where fn err(e: PayloadError) -> Self { HttpMessageBody { - stream: dev::Payload::None, + stream: None, limit: 262_144, fut: None, err: Some(e), @@ -353,10 +331,7 @@ where } } -impl

Future for HttpMessageBody

-where - P: Stream + 'static, -{ +impl Future for HttpMessageBody { type Item = Bytes; type Error = PayloadError; @@ -378,7 +353,9 @@ where // future let limit = self.limit; self.fut = Some(Box::new( - std::mem::replace(&mut self.stream, actix_http::Payload::None) + self.stream + .take() + .unwrap() .from_err() .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { if (body.len() + chunk.len()) > limit { diff --git a/src/types/query.rs b/src/types/query.rs index 0d37c45f3..0467ddee4 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -111,7 +111,7 @@ impl fmt::Display for Query { /// .route(web::get().to(index))); // <- use `Query` extractor /// } /// ``` -impl FromRequest

for Query +impl FromRequest for Query where T: de::DeserializeOwned, { @@ -119,7 +119,7 @@ where type Future = Result; #[inline] - fn from_request(req: &HttpRequest, _: &mut Payload

) -> Self::Future { + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { serde_urlencoded::from_str::(req.query_string()) .map(|val| Ok(Query(val))) .unwrap_or_else(|e| { diff --git a/src/web.rs b/src/web.rs index 94c98c22a..a354222c6 100644 --- a/src/web.rs +++ b/src/web.rs @@ -50,7 +50,7 @@ pub use crate::types::*; /// ); /// } /// ``` -pub fn resource(path: &str) -> Resource

{ +pub fn resource(path: &str) -> Resource { Resource::new(path) } @@ -77,12 +77,12 @@ pub fn resource(path: &str) -> Resource

{ /// * /{project_id}/path2 /// * /{project_id}/path3 /// -pub fn scope(path: &str) -> Scope

{ +pub fn scope(path: &str) -> Scope { Scope::new(path) } /// Create *route* without configuration. -pub fn route() -> Route

{ +pub fn route() -> Route { Route::new() } @@ -102,7 +102,7 @@ pub fn route() -> Route

{ /// In the above example, one `GET` route get added: /// * /{project_id} /// -pub fn get() -> Route

{ +pub fn get() -> Route { Route::new().method(Method::GET) } @@ -122,7 +122,7 @@ pub fn get() -> Route

{ /// In the above example, one `POST` route get added: /// * /{project_id} /// -pub fn post() -> Route

{ +pub fn post() -> Route { Route::new().method(Method::POST) } @@ -142,7 +142,7 @@ pub fn post() -> Route

{ /// In the above example, one `PUT` route get added: /// * /{project_id} /// -pub fn put() -> Route

{ +pub fn put() -> Route { Route::new().method(Method::PUT) } @@ -162,7 +162,7 @@ pub fn put() -> Route

{ /// In the above example, one `PATCH` route get added: /// * /{project_id} /// -pub fn patch() -> Route

{ +pub fn patch() -> Route { Route::new().method(Method::PATCH) } @@ -182,7 +182,7 @@ pub fn patch() -> Route

{ /// In the above example, one `DELETE` route get added: /// * /{project_id} /// -pub fn delete() -> Route

{ +pub fn delete() -> Route { Route::new().method(Method::DELETE) } @@ -202,7 +202,7 @@ pub fn delete() -> Route

{ /// In the above example, one `HEAD` route get added: /// * /{project_id} /// -pub fn head() -> Route

{ +pub fn head() -> Route { Route::new().method(Method::HEAD) } @@ -222,7 +222,7 @@ pub fn head() -> Route

{ /// In the above example, one `GET` route get added: /// * /{project_id} /// -pub fn method(method: Method) -> Route

{ +pub fn method(method: Method) -> Route { Route::new().method(method) } @@ -240,10 +240,10 @@ pub fn method(method: Method) -> Route

{ /// web::to(index)) /// ); /// ``` -pub fn to(handler: F) -> Route

+pub fn to(handler: F) -> Route where F: Factory + 'static, - I: FromRequest

+ 'static, + I: FromRequest + 'static, R: Responder + 'static, { Route::new().to(handler) @@ -263,10 +263,10 @@ where /// web::to_async(index)) /// ); /// ``` -pub fn to_async(handler: F) -> Route

+pub fn to_async(handler: F) -> Route where F: AsyncFactory, - I: FromRequest

+ 'static, + I: FromRequest + 'static, R: IntoFuture + 'static, R::Item: Into, R::Error: Into, diff --git a/tests/test_server.rs b/tests/test_server.rs index 597e69300..3ec20bced 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -19,9 +19,7 @@ use rand::{distributions::Alphanumeric, Rng}; use actix_web::{http, test, web, App, HttpResponse, HttpServer}; #[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] -use actix_web::middleware::encoding; -#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] -use actix_web::middleware::encoding::BodyEncoding; +use actix_web::middleware::{BodyEncoding, Compress}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -68,7 +66,7 @@ fn test_body_gzip() { let mut srv = TestServer::new(|| { h1::H1Service::new( App::new() - .wrap(encoding::Compress::new(ContentEncoding::Gzip)) + .wrap(Compress::new(ContentEncoding::Gzip)) .service(web::resource("/").route(web::to(|| Response::Ok().body(STR)))), ) }); @@ -99,13 +97,11 @@ fn test_body_encoding_override() { let mut srv = TestServer::new(|| { h1::H1Service::new( App::new() - .wrap(encoding::Compress::new(ContentEncoding::Gzip)) + .wrap(Compress::new(ContentEncoding::Gzip)) .service(web::resource("/").route(web::to(|| { - use actix_web::middleware::encoding::BodyEncoding; Response::Ok().encoding(ContentEncoding::Deflate).body(STR) }))) .service(web::resource("/raw").route(web::to(|| { - use actix_web::middleware::encoding::BodyEncoding; let body = actix_web::dev::Body::Bytes(STR.into()); let mut response = Response::with_body(actix_web::http::StatusCode::OK, body); @@ -168,7 +164,7 @@ fn test_body_gzip_large() { let data = srv_data.clone(); h1::H1Service::new( App::new() - .wrap(encoding::Compress::new(ContentEncoding::Gzip)) + .wrap(Compress::new(ContentEncoding::Gzip)) .service( web::resource("/") .route(web::to(move || Response::Ok().body(data.clone()))), @@ -209,7 +205,7 @@ fn test_body_gzip_large_random() { let data = srv_data.clone(); h1::H1Service::new( App::new() - .wrap(encoding::Compress::new(ContentEncoding::Gzip)) + .wrap(Compress::new(ContentEncoding::Gzip)) .service( web::resource("/") .route(web::to(move || Response::Ok().body(data.clone()))), @@ -244,7 +240,7 @@ fn test_body_chunked_implicit() { let mut srv = TestServer::new(move || { h1::H1Service::new( App::new() - .wrap(encoding::Compress::new(ContentEncoding::Gzip)) + .wrap(Compress::new(ContentEncoding::Gzip)) .service(web::resource("/").route(web::get().to(move || { Response::Ok().streaming(once(Ok::<_, Error>(Bytes::from_static( STR.as_ref(), @@ -281,15 +277,12 @@ fn test_body_chunked_implicit() { #[cfg(feature = "brotli")] fn test_body_br_streaming() { let mut srv = TestServer::new(move || { - h1::H1Service::new( - App::new() - .wrap(encoding::Compress::new(ContentEncoding::Br)) - .service(web::resource("/").route(web::to(move || { - Response::Ok().streaming(once(Ok::<_, Error>(Bytes::from_static( - STR.as_ref(), - )))) - }))), - ) + h1::H1Service::new(App::new().wrap(Compress::new(ContentEncoding::Br)).service( + web::resource("/").route(web::to(move || { + Response::Ok() + .streaming(once(Ok::<_, Error>(Bytes::from_static(STR.as_ref())))) + })), + )) }); let mut response = srv @@ -361,7 +354,7 @@ fn test_body_deflate() { let mut srv = TestServer::new(move || { h1::H1Service::new( App::new() - .wrap(encoding::Compress::new(ContentEncoding::Deflate)) + .wrap(Compress::new(ContentEncoding::Deflate)) .service( web::resource("/").route(web::to(move || Response::Ok().body(STR))), ), @@ -392,13 +385,9 @@ fn test_body_deflate() { #[cfg(any(feature = "brotli"))] fn test_body_brotli() { let mut srv = TestServer::new(move || { - h1::H1Service::new( - App::new() - .wrap(encoding::Compress::new(ContentEncoding::Br)) - .service( - web::resource("/").route(web::to(move || Response::Ok().body(STR))), - ), - ) + h1::H1Service::new(App::new().wrap(Compress::new(ContentEncoding::Br)).service( + web::resource("/").route(web::to(move || Response::Ok().body(STR))), + )) }); // client request @@ -427,7 +416,7 @@ fn test_body_brotli() { fn test_encoding() { let mut srv = TestServer::new(move || { HttpService::new( - App::new().enable_encoding().service( + App::new().wrap(Compress::default()).service( web::resource("/") .route(web::to(move |body: Bytes| Response::Ok().body(body))), ), @@ -456,7 +445,7 @@ fn test_encoding() { fn test_gzip_encoding() { let mut srv = TestServer::new(move || { HttpService::new( - App::new().chain(encoding::Decompress::new()).service( + App::new().service( web::resource("/") .route(web::to(move |body: Bytes| Response::Ok().body(body))), ), @@ -486,7 +475,7 @@ fn test_gzip_encoding_large() { let data = STR.repeat(10); let mut srv = TestServer::new(move || { h1::H1Service::new( - App::new().chain(encoding::Decompress::new()).service( + App::new().service( web::resource("/") .route(web::to(move |body: Bytes| Response::Ok().body(body))), ), @@ -520,7 +509,7 @@ fn test_reading_gzip_encoding_large_random() { let mut srv = TestServer::new(move || { HttpService::new( - App::new().chain(encoding::Decompress::new()).service( + App::new().service( web::resource("/") .route(web::to(move |body: Bytes| Response::Ok().body(body))), ), @@ -550,7 +539,7 @@ fn test_reading_gzip_encoding_large_random() { fn test_reading_deflate_encoding() { let mut srv = TestServer::new(move || { h1::H1Service::new( - App::new().chain(encoding::Decompress::new()).service( + App::new().service( web::resource("/") .route(web::to(move |body: Bytes| Response::Ok().body(body))), ), @@ -580,7 +569,7 @@ fn test_reading_deflate_encoding_large() { let data = STR.repeat(10); let mut srv = TestServer::new(move || { h1::H1Service::new( - App::new().chain(encoding::Decompress::new()).service( + App::new().service( web::resource("/") .route(web::to(move |body: Bytes| Response::Ok().body(body))), ), @@ -614,7 +603,7 @@ fn test_reading_deflate_encoding_large_random() { let mut srv = TestServer::new(move || { h1::H1Service::new( - App::new().chain(encoding::Decompress::new()).service( + App::new().service( web::resource("/") .route(web::to(move |body: Bytes| Response::Ok().body(body))), ), @@ -644,7 +633,7 @@ fn test_reading_deflate_encoding_large_random() { fn test_brotli_encoding() { let mut srv = TestServer::new(move || { h1::H1Service::new( - App::new().chain(encoding::Decompress::new()).service( + App::new().service( web::resource("/") .route(web::to(move |body: Bytes| Response::Ok().body(body))), ), @@ -674,7 +663,7 @@ fn test_brotli_encoding_large() { let data = STR.repeat(10); let mut srv = TestServer::new(move || { h1::H1Service::new( - App::new().chain(encoding::Decompress::new()).service( + App::new().service( web::resource("/") .route(web::to(move |body: Bytes| Response::Ok().body(body))), ), From ee33f52736b355724718b8d123063d248fe20cd0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 13 Apr 2019 16:35:25 -0700 Subject: [PATCH 068/122] make extractor config type explicit --- CHANGES.md | 2 ++ actix-files/src/lib.rs | 1 + actix-multipart/src/extractor.rs | 1 + actix-session/CHANGES.md | 4 ++++ actix-session/src/lib.rs | 1 + src/data.rs | 2 ++ src/extract.rs | 17 +++++++++++++++++ src/middleware/identity.rs | 1 + src/request.rs | 1 + src/route.rs | 8 +++++--- src/types/form.rs | 7 +++++-- src/types/json.rs | 15 +++++++++------ src/types/path.rs | 1 + src/types/payload.rs | 13 ++++++++++--- src/types/query.rs | 1 + tests/test_server.rs | 4 +--- 16 files changed, 62 insertions(+), 17 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f7693e66e..45ff6b387 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,8 @@ * Removed `Decompress` middleware. Bytes, String, Json, Form extractors automatically decompress payload. +* Make extractor config type explicit. Add `FromRequest::Config` associated type. + ## [1.0.0-alpha.5] - 2019-04-12 diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index fd7ac3f64..89eead562 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -546,6 +546,7 @@ impl PathBufWrp { impl FromRequest for PathBufWrp { type Error = UriSegmentError; type Future = Result; + type Config = (); fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { PathBufWrp::get_pathbuf(req.match_info().path()) diff --git a/actix-multipart/src/extractor.rs b/actix-multipart/src/extractor.rs index 1f2f15c63..7274ed092 100644 --- a/actix-multipart/src/extractor.rs +++ b/actix-multipart/src/extractor.rs @@ -33,6 +33,7 @@ use crate::server::Multipart; impl FromRequest for Multipart { type Error = Error; type Future = Result; + type Config = (); #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { diff --git a/actix-session/CHANGES.md b/actix-session/CHANGES.md index 305fa561e..ce2c2d637 100644 --- a/actix-session/CHANGES.md +++ b/actix-session/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.0-alpha.6] - 2019-04-xx + +* Update actix-web + ## [0.1.0-alpha.4] - 2019-04-08 * Update actix-web diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index 8db875238..b82029647 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -175,6 +175,7 @@ impl Session { impl FromRequest for Session { type Error = Error; type Future = Result; + type Config = (); #[inline] fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { diff --git a/src/data.rs b/src/data.rs index d178d779a..e0eb8fa9d 100644 --- a/src/data.rs +++ b/src/data.rs @@ -89,6 +89,7 @@ impl Clone for Data { } impl FromRequest for Data { + type Config = (); type Error = Error; type Future = Result; @@ -233,6 +234,7 @@ impl Clone for RouteData { } impl FromRequest for RouteData { + type Config = (); type Error = Error; type Future = Result; diff --git a/src/extract.rs b/src/extract.rs index 3f20f3e3f..9023ea49a 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -17,6 +17,9 @@ pub trait FromRequest: Sized { /// Future that resolves to a Self type Future: IntoFuture; + /// Configuration for this extractor + type Config: Default + 'static; + /// Convert request to a Self fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future; @@ -26,6 +29,14 @@ pub trait FromRequest: Sized { fn extract(req: &HttpRequest) -> Self::Future { Self::from_request(req, &mut Payload::None) } + + /// Create and configure config instance. + fn configure(f: F) -> Self::Config + where + F: FnOnce(Self::Config) -> Self::Config, + { + f(Self::Config::default()) + } } /// Optionally extract a field from the request @@ -48,6 +59,7 @@ pub trait FromRequest: Sized { /// impl FromRequest for Thing { /// type Error = Error; /// type Future = Result; +/// type Config = (); /// /// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { /// if rand::random() { @@ -80,6 +92,7 @@ where T: FromRequest, T::Future: 'static, { + type Config = T::Config; type Error = Error; type Future = Box, Error = Error>>; @@ -119,6 +132,7 @@ where /// impl FromRequest for Thing { /// type Error = Error; /// type Future = Result; +/// type Config = (); /// /// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { /// if rand::random() { @@ -149,6 +163,7 @@ where T::Future: 'static, T::Error: 'static, { + type Config = T::Config; type Error = Error; type Future = Box, Error = Error>>; @@ -167,6 +182,7 @@ where #[doc(hidden)] impl FromRequest for () { + type Config = (); type Error = Error; type Future = Result<(), Error>; @@ -183,6 +199,7 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { { type Error = Error; type Future = $fut_type<$($T),+>; + type Config = ($($T::Config),+); fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { $fut_type { diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index 8dd2ddb80..5bc3f923c 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -141,6 +141,7 @@ struct IdentityItem { /// # fn main() {} /// ``` impl FromRequest for Identity { + type Config = (); type Error = Error; type Future = Result; diff --git a/src/request.rs b/src/request.rs index 93ac954f2..082d36b61 100644 --- a/src/request.rs +++ b/src/request.rs @@ -266,6 +266,7 @@ impl Drop for HttpRequest { /// } /// ``` impl FromRequest for HttpRequest { + type Config = (); type Error = Error; type Future = Result; diff --git a/src/route.rs b/src/route.rs index eb911b307..b1c7b2ab4 100644 --- a/src/route.rs +++ b/src/route.rs @@ -292,7 +292,7 @@ impl Route { /// configuration or specific state available via `RouteData` extractor. /// /// ```rust - /// use actix_web::{web, App}; + /// use actix_web::{web, App, FromRequest}; /// /// /// extract text data from request /// fn index(body: String) -> String { @@ -304,13 +304,15 @@ impl Route { /// web::resource("/index.html").route( /// web::get() /// // limit size of the payload - /// .data(web::PayloadConfig::new(4096)) + /// .data(String::configure(|cfg| { + /// cfg.limit(4096) + /// })) /// // register handler /// .to(index) /// )); /// } /// ``` - pub fn data(mut self, data: C) -> Self { + pub fn data(mut self, data: T) -> Self { if self.data.is_none() { self.data = Some(Extensions::new()); } diff --git a/src/types/form.rs b/src/types/form.rs index c2e8c63bc..249f33b3a 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -73,6 +73,7 @@ impl FromRequest for Form where T: DeserializeOwned + 'static, { + type Config = FormConfig; type Error = Error; type Future = Box>; @@ -115,7 +116,7 @@ impl fmt::Display for Form { /// /// ```rust /// #[macro_use] extern crate serde_derive; -/// use actix_web::{web, App, Result}; +/// use actix_web::{web, App, FromRequest, Result}; /// /// #[derive(Deserialize)] /// struct FormData { @@ -133,7 +134,9 @@ impl fmt::Display for Form { /// web::resource("/index.html") /// .route(web::get() /// // change `Form` extractor configuration -/// .data(web::FormConfig::default().limit(4097)) +/// .data( +/// web::Form::::configure(|cfg| cfg.limit(4097)) +/// ) /// .to(index)) /// ); /// } diff --git a/src/types/json.rs b/src/types/json.rs index d59136225..3543975ae 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -168,6 +168,7 @@ impl FromRequest for Json where T: DeserializeOwned + 'static, { + type Config = JsonConfig; type Error = Error; type Future = Box>; @@ -205,7 +206,7 @@ where /// /// ```rust /// #[macro_use] extern crate serde_derive; -/// use actix_web::{error, web, App, HttpResponse}; +/// use actix_web::{error, web, App, FromRequest, HttpResponse}; /// /// #[derive(Deserialize)] /// struct Info { @@ -222,11 +223,13 @@ where /// web::resource("/index.html").route( /// web::post().data( /// // change json extractor configuration -/// web::JsonConfig::default().limit(4096) -/// .error_handler(|err, req| { // <- create custom error response -/// error::InternalError::from_response( -/// err, HttpResponse::Conflict().finish()).into() -/// })) +/// web::Json::::configure(|cfg| { +/// cfg.limit(4096) +/// .error_handler(|err, req| { // <- create custom error response +/// error::InternalError::from_response( +/// err, HttpResponse::Conflict().finish()).into() +/// }) +/// })) /// .to(index)) /// ); /// } diff --git a/src/types/path.rs b/src/types/path.rs index 47ec1f562..13a35d5ea 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -156,6 +156,7 @@ impl FromRequest for Path where T: de::DeserializeOwned, { + type Config = (); type Error = Error; type Future = Result; diff --git a/src/types/payload.rs b/src/types/payload.rs index 3dac828cb..ca4b5de6b 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -86,6 +86,7 @@ impl Stream for Payload { /// } /// ``` impl FromRequest for Payload { + type Config = PayloadConfig; type Error = Error; type Future = Result; @@ -121,6 +122,7 @@ impl FromRequest for Payload { /// } /// ``` impl FromRequest for Bytes { + type Config = PayloadConfig; type Error = Error; type Future = Either>, FutureResult>; @@ -156,7 +158,7 @@ impl FromRequest for Bytes { /// ## Example /// /// ```rust -/// use actix_web::{web, App}; +/// use actix_web::{web, App, FromRequest}; /// /// /// extract text data from request /// fn index(text: String) -> String { @@ -167,12 +169,15 @@ impl FromRequest for Bytes { /// let app = App::new().service( /// web::resource("/index.html").route( /// web::get() -/// .data(web::PayloadConfig::new(4096)) // <- limit size of the payload +/// .data(String::configure(|cfg| { // <- limit size of the payload +/// cfg.limit(4096) +/// })) /// .to(index)) // <- register handler with extractor params /// ); /// } /// ``` impl FromRequest for String { + type Config = PayloadConfig; type Error = Error; type Future = Either>, FutureResult>; @@ -228,7 +233,9 @@ pub struct PayloadConfig { impl PayloadConfig { /// Create `PayloadConfig` instance and set max size of payload. pub fn new(limit: usize) -> Self { - Self::default().limit(limit) + let mut cfg = Self::default(); + cfg.limit = limit; + cfg } /// Change max size of payload. By default max size is 256Kb diff --git a/src/types/query.rs b/src/types/query.rs index 0467ddee4..596254be5 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -115,6 +115,7 @@ impl FromRequest for Query where T: de::DeserializeOwned, { + type Config = (); type Error = Error; type Future = Result; diff --git a/tests/test_server.rs b/tests/test_server.rs index 3ec20bced..718aa7d4f 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -16,10 +16,8 @@ use flate2::Compression; use futures::stream::once; use rand::{distributions::Alphanumeric, Rng}; -use actix_web::{http, test, web, App, HttpResponse, HttpServer}; - -#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] use actix_web::middleware::{BodyEncoding, Compress}; +use actix_web::{http, test, web, App, HttpResponse, HttpServer}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ From 32ac159ba2fcd149b05aa22173ffd14b2831c342 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 13 Apr 2019 16:51:41 -0700 Subject: [PATCH 069/122] update migration --- MIGRATION.md | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 1a8683cda..b16c61cc7 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,7 +1,7 @@ ## 1.0 * Resource registration. 1.0 version uses generalized resource -registration via `.service()` method. + registration via `.service()` method. instead of @@ -44,9 +44,41 @@ registration via `.service()` method. ); ``` +* `.with()`, `.with_async()` registration methods have been renamed to `.to()` and `.to_async()`. + + instead of + + ```rust + App.new().resource("/welcome", |r| r.with(welcome)) + ``` + + use `.to()` or `.to_async()` methods + + ```rust + App.new().service(web::resource("/welcome").to(welcome)) + ``` + +* Passing arguments to handler with extractors, multiple arguments are allowed + + instead of + + ```rust + fn welcome((body, req): (Bytes, HttpRequest)) -> ... { + ... + } + ``` + + use multiple arguments + + ```rust + fn welcome(body: Bytes, req: HttpRequest) -> ... { + ... + } + ``` + * `.f()`, `.a()` and `.h()` handler registration methods have been removed. -Use `.to()` for handlers and `.to_async()` for async handlers. Handler function -must use extractors. + Use `.to()` for handlers and `.to_async()` for async handlers. Handler function + must use extractors. instead of @@ -61,8 +93,8 @@ must use extractors. ``` * `State` is now `Data`. You register Data during the App initialization process -and then access it from handlers either using a Data extractor or using -HttpRequest's api. + and then access it from handlers either using a Data extractor or using + HttpRequest's api. instead of From 5bd5651faab2b3b46b317124384290d33bff62b2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 13 Apr 2019 22:25:00 -0700 Subject: [PATCH 070/122] Allow to use any service as default service --- CHANGES.md | 2 ++ Cargo.toml | 1 + examples/basic.rs | 6 +++--- src/app.rs | 55 +++++++++++++++++++++++++++++++++++++++++------ src/resource.rs | 21 ++++++++++++------ src/scope.rs | 28 ++++++++++++++---------- src/service.rs | 4 ++-- 7 files changed, 87 insertions(+), 30 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 45ff6b387..eaceb97e0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ ### Changed +* Allow to use any service as default service. + * Remove generic type for request payload, always use default. * Removed `Decompress` middleware. Bytes, String, Json, Form extractors diff --git a/Cargo.toml b/Cargo.toml index 442914f07..1ce5c1dd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,6 +100,7 @@ rustls = { version = "^0.15", optional = true } [dev-dependencies] actix-http = { version = "0.1.0-alpha.5", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } +actix-files = { version = "0.1.0-alpha.4" } rand = "0.6" env_logger = "0.6" serde_derive = "1.0" diff --git a/examples/basic.rs b/examples/basic.rs index 911196570..46440d706 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -36,9 +36,9 @@ fn main() -> std::io::Result<()> { .wrap( middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"), ) - .default_resource(|r| { - r.route(web::route().to(|| HttpResponse::MethodNotAllowed())) - }) + .default_service( + web::route().to(|| HttpResponse::MethodNotAllowed()), + ) .route(web::get().to_async(index_async)), ) .service(web::resource("/test1.html").to(|| "Test\r\n")) diff --git a/src/app.rs b/src/app.rs index 39c96cd92..6c34123de 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,5 @@ use std::cell::RefCell; +use std::fmt; use std::marker::PhantomData; use std::rc::Rc; @@ -207,20 +208,56 @@ where self } - /// Default resource to be used if no matching resource could be found. - pub fn default_resource(mut self, f: F) -> Self + /// Default service to be used if no matching resource could be found. + /// + /// It is possible to use services like `Resource`, `Route`. + /// + /// ```rust + /// use actix_web::{web, App, HttpResponse}; + /// + /// fn index() -> &'static str { + /// "Welcome!" + /// } + /// + /// fn main() { + /// let app = App::new() + /// .service( + /// web::resource("/index.html").route(web::get().to(index))) + /// .default_service( + /// web::route().to(|| HttpResponse::NotFound())); + /// } + /// ``` + /// + /// It is also possible to use static files as default service. + /// + /// ```rust + /// use actix_files::Files; + /// use actix_web::{web, App, HttpResponse}; + /// + /// fn main() { + /// let app = App::new() + /// .service( + /// web::resource("/index.html").to(|| HttpResponse::Ok())) + /// .default_service( + /// Files::new("", "./static") + /// ); + /// } + /// ``` + pub fn default_service(mut self, f: F) -> Self where - F: FnOnce(Resource) -> Resource, + F: IntoNewService, U: NewService< Request = ServiceRequest, Response = ServiceResponse, Error = Error, - InitError = (), > + 'static, + U::InitError: fmt::Debug, { // create and configure default resource self.default = Some(Rc::new(boxed::new_service( - f(Resource::new("")).into_new_service().map_init_err(|_| ()), + f.into_new_service().map_init_err(|e| { + log::error!("Can not construct default service: {:?}", e) + }), ))); self @@ -420,10 +457,14 @@ mod tests { .service(web::resource("/test").to(|| HttpResponse::Ok())) .service( web::resource("/test2") - .default_resource(|r| r.to(|| HttpResponse::Created())) + .default_service(|r: ServiceRequest| { + r.into_response(HttpResponse::Created()) + }) .route(web::get().to(|| HttpResponse::Ok())), ) - .default_resource(|r| r.to(|| HttpResponse::MethodNotAllowed())), + .default_service(|r: ServiceRequest| { + r.into_response(HttpResponse::MethodNotAllowed()) + }), ); let req = TestRequest::with_uri("/blah").to_request(); diff --git a/src/resource.rs b/src/resource.rs index f0dea9810..a8268302d 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,4 +1,5 @@ use std::cell::RefCell; +use std::fmt; use std::rc::Rc; use actix_http::{Error, Response}; @@ -313,22 +314,24 @@ where self.wrap(mw) } - /// Default resource to be used if no matching route could be found. + /// Default service to be used if no matching route could be found. /// By default *405* response get returned. Resource does not use /// default handler from `App` or `Scope`. - pub fn default_resource(mut self, f: F) -> Self + pub fn default_service(mut self, f: F) -> Self where - F: FnOnce(Resource) -> R, - R: IntoNewService, + F: IntoNewService, U: NewService< Request = ServiceRequest, Response = ServiceResponse, Error = Error, > + 'static, + U::InitError: fmt::Debug, { // create and configure default resource self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service( - f(Resource::new("")).into_new_service().map_init_err(|_| ()), + f.into_new_service().map_init_err(|e| { + log::error!("Can not construct default service: {:?}", e) + }), ))))); self @@ -626,7 +629,9 @@ mod tests { .service( web::resource("/test").route(web::get().to(|| HttpResponse::Ok())), ) - .default_resource(|r| r.to(|| HttpResponse::BadRequest())), + .default_service(|r: ServiceRequest| { + r.into_response(HttpResponse::BadRequest()) + }), ); let req = TestRequest::with_uri("/test").to_request(); let resp = call_success(&mut srv, req); @@ -642,7 +647,9 @@ mod tests { App::new().service( web::resource("/test") .route(web::get().to(|| HttpResponse::Ok())) - .default_resource(|r| r.to(|| HttpResponse::BadRequest())), + .default_service(|r: ServiceRequest| { + r.into_response(HttpResponse::BadRequest()) + }), ), ); diff --git a/src/scope.rs b/src/scope.rs index 62badc86a..5678158e8 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,4 +1,5 @@ use std::cell::RefCell; +use std::fmt; use std::rc::Rc; use actix_http::Response; @@ -180,22 +181,24 @@ where ) } - /// Default resource to be used if no matching route could be found. + /// Default service to be used if no matching route could be found. /// /// If default resource is not registered, app's default resource is being used. - pub fn default_resource(mut self, f: F) -> Self + pub fn default_service(mut self, f: F) -> Self where - F: FnOnce(Resource) -> Resource, + F: IntoNewService, U: NewService< Request = ServiceRequest, Response = ServiceResponse, Error = Error, - InitError = (), > + 'static, + U::InitError: fmt::Debug, { // create and configure default resource self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service( - f(Resource::new("")).into_new_service().map_init_err(|_| ()), + f.into_new_service().map_init_err(|e| { + log::error!("Can not construct default service: {:?}", e) + }), ))))); self @@ -843,7 +846,9 @@ mod tests { App::new().service( web::scope("/app") .service(web::resource("/path1").to(|| HttpResponse::Ok())) - .default_resource(|r| r.to(|| HttpResponse::BadRequest())), + .default_service(|r: ServiceRequest| { + r.into_response(HttpResponse::BadRequest()) + }), ), ); @@ -860,12 +865,13 @@ mod tests { fn test_default_resource_propagation() { let mut srv = init_service( App::new() - .service( - web::scope("/app1") - .default_resource(|r| r.to(|| HttpResponse::BadRequest())), - ) + .service(web::scope("/app1").default_service( + web::resource("").to(|| HttpResponse::BadRequest()), + )) .service(web::scope("/app2")) - .default_resource(|r| r.to(|| HttpResponse::MethodNotAllowed())), + .default_service(|r: ServiceRequest| { + r.into_response(HttpResponse::MethodNotAllowed()) + }), ); let req = TestRequest::with_uri("/non-exist").to_request(); diff --git a/src/service.rs b/src/service.rs index 2817cc0b8..e5b0896e4 100644 --- a/src/service.rs +++ b/src/service.rs @@ -63,8 +63,8 @@ impl ServiceRequest { /// Create service response #[inline] - pub fn into_response(self, res: Response) -> ServiceResponse { - ServiceResponse::new(self.req, res) + pub fn into_response>>(self, res: R) -> ServiceResponse { + ServiceResponse::new(self.req, res.into()) } /// Create service response for error From 6bc1a0c76bdf5f912d5340fc556802fae0e79cb7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 14 Apr 2019 07:43:53 -0700 Subject: [PATCH 071/122] Do not set default headers for websocket request --- MIGRATION.md | 8 +- awc/CHANGES.md | 7 ++ awc/src/request.rs | 138 ++++++++++++++---------------- awc/src/ws.rs | 180 ++++++++++++++++++++++++++------------- awc/tests/test_client.rs | 32 +------ 5 files changed, 198 insertions(+), 167 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index b16c61cc7..5953452dd 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -182,6 +182,8 @@ } ``` +* `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type + * StaticFiles and NamedFile has been move to separate create. instead of `use actix_web::fs::StaticFile` @@ -198,10 +200,10 @@ use `use actix_multipart::Multipart` -* Request/response compression/decompression is not enabled by default. - To enable use `App::enable_encoding()` method. +* Response compression is not enabled by default. + To enable, use `Compress` middleware, `App::new().wrap(Compress::default())`. -* `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type +* Session middleware moved to actix-session crate * Actors support have been moved to `actix-web-actors` crate diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 4558867fc..12f7470f1 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.1.0-alpha.6] - 2019-04-xx + +### Changed + +* Do not set default headers for websocket request + + ## [0.1.0-alpha.5] - 2019-04-12 ### Changed diff --git a/awc/src/request.rs b/awc/src/request.rs index b21c101c4..1daaa28cb 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -545,101 +545,91 @@ impl fmt::Debug for ClientRequest { #[cfg(test)] mod tests { use super::*; - use crate::{test, Client}; + use crate::Client; #[test] fn test_debug() { - test::run_on(|| { - let request = Client::new().get("/").header("x-test", "111"); - let repr = format!("{:?}", request); - assert!(repr.contains("ClientRequest")); - assert!(repr.contains("x-test")); - }) + let request = Client::new().get("/").header("x-test", "111"); + let repr = format!("{:?}", request); + assert!(repr.contains("ClientRequest")); + assert!(repr.contains("x-test")); } #[test] fn test_client_header() { - test::run_on(|| { - let req = Client::build() - .header(header::CONTENT_TYPE, "111") - .finish() - .get("/"); + let req = Client::build() + .header(header::CONTENT_TYPE, "111") + .finish() + .get("/"); - assert_eq!( - req.head - .headers - .get(header::CONTENT_TYPE) - .unwrap() - .to_str() - .unwrap(), - "111" - ); - }) + assert_eq!( + req.head + .headers + .get(header::CONTENT_TYPE) + .unwrap() + .to_str() + .unwrap(), + "111" + ); } #[test] fn test_client_header_override() { - test::run_on(|| { - let req = Client::build() - .header(header::CONTENT_TYPE, "111") - .finish() - .get("/") - .set_header(header::CONTENT_TYPE, "222"); + let req = Client::build() + .header(header::CONTENT_TYPE, "111") + .finish() + .get("/") + .set_header(header::CONTENT_TYPE, "222"); - assert_eq!( - req.head - .headers - .get(header::CONTENT_TYPE) - .unwrap() - .to_str() - .unwrap(), - "222" - ); - }) + assert_eq!( + req.head + .headers + .get(header::CONTENT_TYPE) + .unwrap() + .to_str() + .unwrap(), + "222" + ); } #[test] fn client_basic_auth() { - test::run_on(|| { - let req = Client::new() - .get("/") - .basic_auth("username", Some("password")); - assert_eq!( - req.head - .headers - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap(), - "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" - ); + let req = Client::new() + .get("/") + .basic_auth("username", Some("password")); + assert_eq!( + req.head + .headers + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap(), + "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" + ); - let req = Client::new().get("/").basic_auth("username", None); - assert_eq!( - req.head - .headers - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap(), - "Basic dXNlcm5hbWU=" - ); - }); + let req = Client::new().get("/").basic_auth("username", None); + assert_eq!( + req.head + .headers + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap(), + "Basic dXNlcm5hbWU=" + ); } #[test] fn client_bearer_auth() { - test::run_on(|| { - let req = Client::new().get("/").bearer_auth("someS3cr3tAutht0k3n"); - assert_eq!( - req.head - .headers - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap(), - "Bearer someS3cr3tAutht0k3n" - ); - }) + let req = Client::new().get("/").bearer_auth("someS3cr3tAutht0k3n"); + assert_eq!( + req.head + .headers + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap(), + "Bearer someS3cr3tAutht0k3n" + ); } } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 967820f3f..4f0983dc5 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -1,13 +1,11 @@ //! Websockets client use std::fmt::Write as FmtWrite; -use std::io::Write; use std::rc::Rc; use std::{fmt, str}; use actix_codec::Framed; use actix_http::cookie::{Cookie, CookieJar}; use actix_http::{ws, Payload, RequestHead}; -use bytes::{BufMut, BytesMut}; use futures::future::{err, Either, Future}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use tokio_timer::Timeout; @@ -33,7 +31,6 @@ pub struct WebsocketsRequest { protocols: Option, max_size: usize, server_mode: bool, - default_headers: bool, cookies: Option, config: Rc, } @@ -63,7 +60,6 @@ impl WebsocketsRequest { max_size: 65_536, server_mode: false, cookies: None, - default_headers: true, } } @@ -119,13 +115,6 @@ impl WebsocketsRequest { self } - /// Do not add default request headers. - /// By default `Date` and `User-Agent` headers are set. - pub fn no_default_headers(mut self) -> Self { - self.default_headers = false; - self - } - /// Append a header. /// /// Header gets appended to existing header. @@ -188,10 +177,9 @@ impl WebsocketsRequest { } /// Set HTTP basic authorization header - pub fn basic_auth(self, username: U, password: Option

) -> Self + pub fn basic_auth(self, username: U, password: Option<&str>) -> Self where U: fmt::Display, - P: fmt::Display, { let auth = match password { Some(password) => format!("{}:{}", username, password), @@ -232,67 +220,36 @@ impl WebsocketsRequest { return Either::A(err(InvalidUrl::UnknownScheme.into())); } - // set default headers - let mut slf = if self.default_headers { - // set request host header - 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 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) => { - self.head.headers.insert(header::HOST, value); - } - Err(e) => return Either::A(err(HttpError::from(e).into())), - } - } - } - - // user agent - self.set_header_if_none( - header::USER_AGENT, - concat!("awc/", env!("CARGO_PKG_VERSION")), - ) - } else { - self - }; - - let mut head = slf.head; - // set cookies - if let Some(ref mut jar) = slf.cookies { + if let Some(ref mut jar) = self.cookies { let mut cookie = String::new(); for c in jar.delta() { let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); 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(), ); } // origin - if let Some(origin) = slf.origin.take() { - head.headers.insert(header::ORIGIN, origin); + if let Some(origin) = self.origin.take() { + self.head.headers.insert(header::ORIGIN, origin); } - head.set_connection_type(ConnectionType::Upgrade); - head.headers + self.head.set_connection_type(ConnectionType::Upgrade); + self.head + .headers .insert(header::UPGRADE, HeaderValue::from_static("websocket")); - head.headers.insert( + self.head.headers.insert( header::SEC_WEBSOCKET_VERSION, HeaderValue::from_static("13"), ); - if let Some(protocols) = slf.protocols.take() { - head.headers.insert( + if let Some(protocols) = self.protocols.take() { + self.head.headers.insert( header::SEC_WEBSOCKET_PROTOCOL, HeaderValue::try_from(protocols.as_str()).unwrap(), ); @@ -304,15 +261,16 @@ impl WebsocketsRequest { let sec_key: [u8; 16] = rand::random(); let key = base64::encode(&sec_key); - head.headers.insert( + self.head.headers.insert( header::SEC_WEBSOCKET_KEY, HeaderValue::try_from(key.as_str()).unwrap(), ); - let max_size = slf.max_size; - let server_mode = slf.server_mode; + let head = self.head; + let max_size = self.max_size; + let server_mode = self.server_mode; - let fut = slf + let fut = self .config .connector .borrow_mut() @@ -387,7 +345,7 @@ impl WebsocketsRequest { }); // set request timeout - if let Some(timeout) = slf.config.timeout { + if let Some(timeout) = self.config.timeout { Either::B(Either::A(Timeout::new(fut, timeout).map_err(|e| { if let Some(e) = e.into_inner() { e @@ -400,3 +358,107 @@ impl WebsocketsRequest { } } } + +impl fmt::Debug for WebsocketsRequest { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!( + f, + "\nWebsocketsRequest {}:{}", + self.head.method, self.head.uri + )?; + writeln!(f, " headers:")?; + for (key, val) in self.head.headers.iter() { + writeln!(f, " {:?}: {:?}", key, val)?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::Client; + + #[test] + fn test_debug() { + let request = Client::new().ws("/").header("x-test", "111"); + let repr = format!("{:?}", request); + assert!(repr.contains("WebsocketsRequest")); + assert!(repr.contains("x-test")); + } + + #[test] + fn test_header_override() { + let req = Client::build() + .header(header::CONTENT_TYPE, "111") + .finish() + .ws("/") + .set_header(header::CONTENT_TYPE, "222"); + + assert_eq!( + req.head + .headers + .get(header::CONTENT_TYPE) + .unwrap() + .to_str() + .unwrap(), + "222" + ); + } + + #[test] + fn basic_auth() { + let req = Client::new() + .ws("/") + .basic_auth("username", Some("password")); + assert_eq!( + req.head + .headers + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap(), + "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" + ); + + let req = Client::new().ws("/").basic_auth("username", None); + assert_eq!( + req.head + .headers + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap(), + "Basic dXNlcm5hbWU=" + ); + } + + #[test] + fn bearer_auth() { + let req = Client::new().ws("/").bearer_auth("someS3cr3tAutht0k3n"); + assert_eq!( + req.head + .headers + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap(), + "Bearer someS3cr3tAutht0k3n" + ); + } + + #[test] + fn basics() { + let req = Client::new() + .ws("/") + .origin("test-origin") + .max_frame_size(100) + .server_mode() + .protocols(&["v1", "v2"]) + .cookie(Cookie::build("cookie1", "value1").finish()); + assert_eq!(req.origin.unwrap().to_str().unwrap(), "test-origin"); + assert_eq!(req.max_size, 100); + assert_eq!(req.server_mode, true); + assert_eq!(req.protocols, Some("v1,v2".to_string())); + } +} diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index a2882708a..39bcf418c 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -10,6 +10,7 @@ use rand::Rng; use actix_http::HttpService; use actix_http_test::TestServer; +use actix_web::http::Cookie; use actix_web::{http::header, web, App, Error, HttpMessage, HttpRequest, HttpResponse}; use awc::error::SendRequestError; @@ -406,7 +407,6 @@ fn test_client_brotli_encoding() { #[test] fn test_client_cookie_handling() { - use actix_web::http::Cookie; fn err() -> Error { use std::io::{Error as IoError, ErrorKind}; // stub some generic error @@ -468,36 +468,6 @@ fn test_client_cookie_handling() { assert_eq!(c2, cookie2); } -// #[test] -// fn test_default_headers() { -// let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - -// let request = srv.get("/").finish().unwrap(); -// let repr = format!("{:?}", request); -// assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\"")); -// assert!(repr.contains(concat!( -// "\"user-agent\": \"actix-web/", -// env!("CARGO_PKG_VERSION"), -// "\"" -// ))); - -// let request_override = srv -// .get("/") -// .header("User-Agent", "test") -// .header("Accept-Encoding", "over_test") -// .finish() -// .unwrap(); -// let repr_override = format!("{:?}", request_override); -// assert!(repr_override.contains("\"user-agent\": \"test\"")); -// assert!(repr_override.contains("\"accept-encoding\": \"over_test\"")); -// assert!(!repr_override.contains("\"accept-encoding\": \"gzip, deflate\"")); -// assert!(!repr_override.contains(concat!( -// "\"user-agent\": \"Actix-web/", -// env!("CARGO_PKG_VERSION"), -// "\"" -// ))); -// } - // #[test] // fn client_read_until_eof() { // let addr = test::TestServer::unused_addr(); From d7040dc303a77b01d406a9abdf4e9cc4b6586020 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 14 Apr 2019 08:09:32 -0700 Subject: [PATCH 072/122] alpha.6 release --- CHANGES.md | 2 +- Cargo.toml | 10 +++++----- actix-files/CHANGES.md | 6 ++++-- actix-files/Cargo.toml | 6 +++--- actix-session/CHANGES.md | 4 ++-- actix-session/Cargo.toml | 4 ++-- actix-web-codegen/CHANGES.md | 4 ++++ actix-web-codegen/Cargo.toml | 4 ++-- awc/CHANGES.md | 2 +- awc/Cargo.toml | 4 ++-- 10 files changed, 26 insertions(+), 20 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index eaceb97e0..d1405086e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [1.0.0-alpha.6] - 2019-04-xx +## [1.0.0-alpha.6] - 2019-04-14 ### Changed diff --git a/Cargo.toml b/Cargo.toml index 1ce5c1dd0..f2835d6c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.0-alpha.5" +version = "1.0.0-alpha.6" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -70,18 +70,18 @@ actix-service = "0.3.6" actix-utils = "0.3.4" actix-router = "0.1.2" actix-rt = "0.2.2" -actix-web-codegen = "0.1.0-alpha.1" +actix-web-codegen = "0.1.0-alpha.6" actix-http = { version = "0.1.0-alpha.5", features=["fail"] } actix-server = "0.4.2" actix-server-config = "0.1.0" actix-threadpool = "0.1.0" -awc = { version = "0.1.0-alpha.5", optional = true } +awc = { version = "0.1.0-alpha.6", optional = true } bytes = "0.4" derive_more = "0.14" encoding = "0.2" futures = "0.1" -hashbrown = "0.1.8" +hashbrown = "0.2.1" log = "0.4" mime = "0.3" net2 = "0.2.33" @@ -100,7 +100,7 @@ rustls = { version = "^0.15", optional = true } [dev-dependencies] actix-http = { version = "0.1.0-alpha.5", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } -actix-files = { version = "0.1.0-alpha.4" } +actix-files = { version = "0.1.0-alpha.6" } rand = "0.6" env_logger = "0.6" serde_derive = "1.0" diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index f7a88ba43..ae22fca19 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,15 +1,17 @@ # Changes +## [0.1.0-alpha.6] - 2019-04-14 + +* Update actix-web to alpha6 + ## [0.1.0-alpha.4] - 2019-04-08 * Update actix-web to alpha4 - ## [0.1.0-alpha.2] - 2019-04-02 * Add default handler support - ## [0.1.0-alpha.1] - 2019-03-28 * Initial impl diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 6cc9c711c..fa9d67393 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.1.0-alpha.4" +version = "0.1.0-alpha.6" authors = ["Nikolay Kim "] description = "Static files support for actix web." readme = "README.md" @@ -18,7 +18,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = "1.0.0-alpha.5" +actix-web = "1.0.0-alpha.6" 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.5", features=["ssl"] } +actix-web = { version = "1.0.0-alpha.6", features=["ssl"] } diff --git a/actix-session/CHANGES.md b/actix-session/CHANGES.md index ce2c2d637..54ea66d9e 100644 --- a/actix-session/CHANGES.md +++ b/actix-session/CHANGES.md @@ -1,8 +1,8 @@ # Changes -## [0.1.0-alpha.6] - 2019-04-xx +## [0.1.0-alpha.6] - 2019-04-14 -* Update actix-web +* Update actix-web alpha.6 ## [0.1.0-alpha.4] - 2019-04-08 diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index 9f1a97096..058fc7068 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-session" -version = "0.1.0-alpha.4" +version = "0.1.0-alpha.6" 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.5" +actix-web = "1.0.0-alpha.6" actix-service = "0.3.4" bytes = "0.4" derive_more = "0.14" diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 95ec1c35f..2ce5bd7db 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.0-alpha.6] - 2019-04-14 + +* Gen code for actix-web 1.0.0-alpha.6 + ## [0.1.0-alpha.1] - 2019-03-28 * Initial impl diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index f4027fbdf..26dbd9b71 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-codegen" -version = "0.1.0-alpha.1" +version = "0.1.0-alpha.6" description = "Actix web proc macros" readme = "README.md" authors = ["Nikolay Kim "] @@ -16,7 +16,7 @@ quote = "0.6" syn = { version = "0.15", features = ["full", "parsing"] } [dev-dependencies] -actix-web = { version = "1.0.0-alpha.5" } +actix-web = { version = "1.0.0-alpha.6" } actix-http = { version = "0.1.0-alpha.5", features=["ssl"] } actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } futures = { version = "0.1" } diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 12f7470f1..ddeefd94c 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.1.0-alpha.6] - 2019-04-xx +## [0.1.0-alpha.6] - 2019-04-14 ### Changed diff --git a/awc/Cargo.toml b/awc/Cargo.toml index bddf63b82..cbca0f477 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "0.1.0-alpha.5" +version = "0.1.0-alpha.6" authors = ["Nikolay Kim "] description = "Actix http client." readme = "README.md" @@ -55,7 +55,7 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.2" -actix-web = { version = "1.0.0-alpha.5", features=["ssl"] } +actix-web = { version = "1.0.0-alpha.6", features=["ssl"] } actix-http = { version = "0.1.0-alpha.5", features=["ssl"] } actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } actix-utils = "0.3.4" From 4cc2b38059db4fcc50220fc3d8abde669c9b1c6a Mon Sep 17 00:00:00 2001 From: Darin Date: Sun, 14 Apr 2019 19:25:45 -0400 Subject: [PATCH 073/122] added read_response_json for testing (#776) * added read_response_json for testing * cleaned up * modied docs for read_response_json * typo in doc * test code in doc should compile now * use type coercion in doc * removed generic R, replaced with Request --- awc/src/ws.rs | 4 +++- src/test.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 4f0983dc5..5ed37945b 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -306,7 +306,9 @@ impl WebsocketsRequest { } } else { log::trace!("Invalid connection header: {:?}", conn); - return Err(WsClientError::InvalidConnectionHeader(conn.clone())); + return Err(WsClientError::InvalidConnectionHeader( + conn.clone(), + )); } } else { log::trace!("Missing connection header"); diff --git a/src/test.rs b/src/test.rs index 7cdf44854..342187048 100644 --- a/src/test.rs +++ b/src/test.rs @@ -11,14 +11,16 @@ use actix_router::{Path, ResourceDef, Url}; use actix_rt::Runtime; use actix_server_config::ServerConfig; use actix_service::{FnService, IntoNewService, NewService, Service}; -use bytes::Bytes; -use futures::future::{lazy, Future}; +use bytes::{Bytes, BytesMut}; +use futures::{future::{lazy, ok, Future}, stream::Stream}; +use serde::de::DeserializeOwned; +use serde_json; pub use actix_http::test::TestBuffer; use crate::config::{AppConfig, AppConfigInner}; use crate::data::RouteData; -use crate::dev::{Body, Payload}; +use crate::dev::{Body, MessageBody, Payload}; use crate::request::HttpRequestPool; use crate::rmap::ResourceMap; use crate::service::{ServiceRequest, ServiceResponse}; @@ -363,4 +365,55 @@ impl TestRequest { { block_on(f) } + + /// Helper function that returns a deserialized response body of a TestRequest + /// This function blocks the current thread until futures complete. + /// + /// ```rust + /// use actix_web::{App, test, web, HttpResponse, http::header}; + /// use serde::{Serialize, Deserialize}; + /// + /// #[derive(Serialize, Deserialize)] + /// pub struct Person { id: String, name: String } + /// + /// #[test] + /// fn test_add_person() { + /// let mut app = test::init_service(App::new().service( + /// web::resource("/people") + /// .route(web::post().to(|person: web::Json| { + /// HttpResponse::Ok() + /// .json(person.into_inner())}) + /// ))); + /// + /// let payload = r#"{"id":"12345","name":"Nikolay Kim"}"#.as_bytes(); + /// + /// let req = test::TestRequest::post() + /// .uri("/people") + /// .header(header::CONTENT_TYPE, "application/json") + /// .set_payload(payload) + /// .to_request(); + /// + /// let result: Person = test::read_response_json(&mut app, req); + /// } + /// ``` + pub fn read_response_json(app: &mut S, req: Request) -> T + where + S: Service, Error = Error>, + B: MessageBody, + T: DeserializeOwned, + { + block_on(app.call(req).and_then(|mut resp: ServiceResponse| { + resp.take_body() + .fold(BytesMut::new(), move |mut body, chunk| { + body.extend_from_slice(&chunk); + Ok::<_, Error>(body) + }) + .and_then(|body: BytesMut| { + ok(serde_json::from_slice(&body).unwrap_or_else(|_| { + panic!("read_response_json failed during deserialization") + })) + }) + })) + .unwrap_or_else(|_| panic!("read_response_json failed at block_on unwrap")) + } } From f9078d41cd0c05089101b9c7df167133d17c0991 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 14 Apr 2019 19:52:12 -0700 Subject: [PATCH 074/122] add test::read_response; fix TestRequest::app_data() --- CHANGES.md | 12 +++ awc/src/ws.rs | 4 +- src/data.rs | 1 + src/test.rs | 255 ++++++++++++++++++++++++++++++++++++++------------ src/web.rs | 12 +-- 5 files changed, 213 insertions(+), 71 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d1405086e..0d40cf31e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,17 @@ # Changes +## [1.0.0-alpha.7] - 2019-04-xx + +### Added + +* Added helper functions for reading test response body, + `test::read_response()` and test::read_response_json()` + +### Fixed + +* Fixed `TestRequest::app_data()` + + ## [1.0.0-alpha.6] - 2019-04-14 ### Changed diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 5ed37945b..4f0983dc5 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -306,9 +306,7 @@ impl WebsocketsRequest { } } else { log::trace!("Invalid connection header: {:?}", conn); - return Err(WsClientError::InvalidConnectionHeader( - conn.clone(), - )); + return Err(WsClientError::InvalidConnectionHeader(conn.clone())); } } else { log::trace!("Missing connection header"); diff --git a/src/data.rs b/src/data.rs index e0eb8fa9d..0c896fcc2 100644 --- a/src/data.rs +++ b/src/data.rs @@ -61,6 +61,7 @@ pub(crate) trait DataFactoryResult { /// web::get().to(index))); /// } /// ``` +#[derive(Debug)] pub struct Data(Arc); impl Data { diff --git a/src/test.rs b/src/test.rs index 342187048..638bcdce6 100644 --- a/src/test.rs +++ b/src/test.rs @@ -4,7 +4,7 @@ use std::rc::Rc; use actix_http::cookie::Cookie; use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; -use actix_http::http::{HttpTryFrom, Method, StatusCode, Version}; +use actix_http::http::{HttpTryFrom, Method, StatusCode, Uri, Version}; use actix_http::test::TestRequest as HttpTestRequest; use actix_http::{Extensions, Request}; use actix_router::{Path, ResourceDef, Url}; @@ -12,14 +12,17 @@ use actix_rt::Runtime; use actix_server_config::ServerConfig; use actix_service::{FnService, IntoNewService, NewService, Service}; use bytes::{Bytes, BytesMut}; -use futures::{future::{lazy, ok, Future}, stream::Stream}; +use futures::{ + future::{lazy, ok, Future}, + stream::Stream, +}; use serde::de::DeserializeOwned; use serde_json; pub use actix_http::test::TestBuffer; use crate::config::{AppConfig, AppConfigInner}; -use crate::data::RouteData; +use crate::data::{Data, RouteData}; use crate::dev::{Body, MessageBody, Payload}; use crate::request::HttpRequestPool; use crate::rmap::ResourceMap; @@ -81,11 +84,12 @@ pub fn default_service( /// This method accepts application builder instance, and constructs /// service. /// -/// ```rust,ignore +/// ```rust /// use actix_service::Service; /// use actix_web::{test, web, App, HttpResponse, http::StatusCode}; /// -/// fn main() { +/// #[test] +/// fn test_init_service() { /// let mut app = test::init_service( /// App::new() /// .service(web::resource("/test").to(|| HttpResponse::Ok())) @@ -118,11 +122,12 @@ where /// Calls service and waits for response future completion. /// -/// ```rust,ignore +/// ```rust /// use actix_web::{test, App, HttpResponse, http::StatusCode}; /// use actix_service::Service; /// -/// fn main() { +/// #[test] +/// fn test_response() { /// let mut app = test::init_service( /// App::new() /// .service(web::resource("/test").to(|| HttpResponse::Ok())) @@ -144,6 +149,101 @@ where block_on(app.call(req)).unwrap() } +/// Helper function that returns a response body of a TestRequest +/// This function blocks the current thread until futures complete. +/// +/// ```rust +/// use actix_web::{test, web, App, HttpResponse, http::header}; +/// use bytes::Bytes; +/// +/// #[test] +/// fn test_index() { +/// let mut app = test::init_service( +/// App::new().service( +/// web::resource("/index.html") +/// .route(web::post().to( +/// || HttpResponse::Ok().body("welcome!"))))); +/// +/// let req = test::TestRequest::post() +/// .uri("/index.html") +/// .header(header::CONTENT_TYPE, "application/json") +/// .to_request(); +/// +/// let result = test::read_response(&mut app, req); +/// assert_eq!(result, Bytes::from_static(b"welcome!")); +/// } +/// ``` +pub fn read_response(app: &mut S, req: Request) -> Bytes +where + S: Service, Error = Error>, + B: MessageBody, +{ + block_on(app.call(req).and_then(|mut resp: ServiceResponse| { + resp.take_body() + .fold(BytesMut::new(), move |mut body, chunk| { + body.extend_from_slice(&chunk); + Ok::<_, Error>(body) + }) + .map(|body: BytesMut| body.freeze()) + })) + .unwrap_or_else(|_| panic!("read_response failed at block_on unwrap")) +} + +/// Helper function that returns a deserialized response body of a TestRequest +/// This function blocks the current thread until futures complete. +/// +/// ```rust +/// use actix_web::{App, test, web, HttpResponse, http::header}; +/// use serde::{Serialize, Deserialize}; +/// +/// #[derive(Serialize, Deserialize)] +/// pub struct Person { +/// id: String, +/// name: String +/// } +/// +/// #[test] +/// fn test_add_person() { +/// let mut app = test::init_service( +/// App::new().service( +/// web::resource("/people") +/// .route(web::post().to(|person: web::Json| { +/// HttpResponse::Ok() +/// .json(person.into_inner())}) +/// ))); +/// +/// let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes(); +/// +/// let req = test::TestRequest::post() +/// .uri("/people") +/// .header(header::CONTENT_TYPE, "application/json") +/// .set_payload(payload) +/// .to_request(); +/// +/// let result: Person = test::read_response_json(&mut app, req); +/// } +/// ``` +pub fn read_response_json(app: &mut S, req: Request) -> T +where + S: Service, Error = Error>, + B: MessageBody, + T: DeserializeOwned, +{ + block_on(app.call(req).and_then(|mut resp: ServiceResponse| { + resp.take_body() + .fold(BytesMut::new(), move |mut body, chunk| { + body.extend_from_slice(&chunk); + Ok::<_, Error>(body) + }) + .and_then(|body: BytesMut| { + ok(serde_json::from_slice(&body).unwrap_or_else(|_| { + panic!("read_response_json failed during deserialization") + })) + }) + })) + .unwrap_or_else(|_| panic!("read_response_json failed at block_on unwrap")) +} + /// Test `Request` builder. /// /// For unit testing, actix provides a request builder type and a simple handler runner. TestRequest implements a builder-like pattern. @@ -153,7 +253,7 @@ where /// * `TestRequest::to_from` creates `ServiceFromRequest` instance, which is used for testing extractors. /// * `TestRequest::to_http_request` creates `HttpRequest` instance, which is used for testing handlers. /// -/// ```rust,ignore +/// ```rust /// # use futures::IntoFuture; /// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage}; /// use actix_web::http::{header, StatusCode}; @@ -166,7 +266,8 @@ where /// } /// } /// -/// fn main() { +/// #[test] +/// fn test_index() { /// let req = test::TestRequest::with_header("content-type", "text/plain") /// .to_http_request(); /// @@ -183,6 +284,7 @@ pub struct TestRequest { rmap: ResourceMap, config: AppConfigInner, route_data: Extensions, + path: Path, } impl Default for TestRequest { @@ -192,6 +294,7 @@ impl Default for TestRequest { rmap: ResourceMap::new(ResourceDef::new("")), config: AppConfigInner::default(), route_data: Extensions::new(), + path: Path::new(Url::new(Uri::default())), } } } @@ -267,6 +370,12 @@ impl TestRequest { self } + /// Set request path pattern parameter + pub fn param(mut self, name: &'static str, value: &'static str) -> Self { + self.path.add_static(name, value); + self + } + /// Set request payload pub fn set_payload>(mut self, data: B) -> Self { self.req.set_payload(data); @@ -276,7 +385,7 @@ impl TestRequest { /// Set application data. This is equivalent of `App::data()` method /// for testing purpose. pub fn app_data(self, data: T) -> Self { - self.config.extensions.borrow_mut().insert(data); + self.config.extensions.borrow_mut().insert(Data::new(data)); self } @@ -302,9 +411,10 @@ impl TestRequest { /// Complete request creation and generate `ServiceRequest` instance pub fn to_srv_request(mut self) -> ServiceRequest { let (head, payload) = self.req.finish().into_parts(); + self.path.get_mut().update(&head.uri); let req = HttpRequest::new( - Path::new(Url::new(head.uri.clone())), + self.path, head, Rc::new(self.rmap), AppConfig::new(self.config), @@ -322,9 +432,10 @@ impl TestRequest { /// Complete request creation and generate `HttpRequest` instance pub fn to_http_request(mut self) -> HttpRequest { let (head, _) = self.req.finish().into_parts(); + self.path.get_mut().update(&head.uri); let mut req = HttpRequest::new( - Path::new(Url::new(head.uri.clone())), + self.path, head, Rc::new(self.rmap), AppConfig::new(self.config), @@ -337,9 +448,10 @@ impl TestRequest { /// Complete request creation and generate `HttpRequest` and `Payload` instances pub fn to_http_parts(mut self) -> (HttpRequest, Payload) { let (head, payload) = self.req.finish().into_parts(); + self.path.get_mut().update(&head.uri); let mut req = HttpRequest::new( - Path::new(Url::new(head.uri.clone())), + self.path, head, Rc::new(self.rmap), AppConfig::new(self.config), @@ -365,55 +477,74 @@ impl TestRequest { { block_on(f) } +} - /// Helper function that returns a deserialized response body of a TestRequest - /// This function blocks the current thread until futures complete. - /// - /// ```rust - /// use actix_web::{App, test, web, HttpResponse, http::header}; - /// use serde::{Serialize, Deserialize}; - /// - /// #[derive(Serialize, Deserialize)] - /// pub struct Person { id: String, name: String } - /// - /// #[test] - /// fn test_add_person() { - /// let mut app = test::init_service(App::new().service( - /// web::resource("/people") - /// .route(web::post().to(|person: web::Json| { - /// HttpResponse::Ok() - /// .json(person.into_inner())}) - /// ))); - /// - /// let payload = r#"{"id":"12345","name":"Nikolay Kim"}"#.as_bytes(); - /// - /// let req = test::TestRequest::post() - /// .uri("/people") - /// .header(header::CONTENT_TYPE, "application/json") - /// .set_payload(payload) - /// .to_request(); - /// - /// let result: Person = test::read_response_json(&mut app, req); - /// } - /// ``` - pub fn read_response_json(app: &mut S, req: Request) -> T - where - S: Service, Error = Error>, - B: MessageBody, - T: DeserializeOwned, - { - block_on(app.call(req).and_then(|mut resp: ServiceResponse| { - resp.take_body() - .fold(BytesMut::new(), move |mut body, chunk| { - body.extend_from_slice(&chunk); - Ok::<_, Error>(body) - }) - .and_then(|body: BytesMut| { - ok(serde_json::from_slice(&body).unwrap_or_else(|_| { - panic!("read_response_json failed during deserialization") - })) - }) - })) - .unwrap_or_else(|_| panic!("read_response_json failed at block_on unwrap")) +#[cfg(test)] +mod tests { + use serde::{Deserialize, Serialize}; + use std::time::SystemTime; + + use super::*; + use crate::{http::header, web, App, HttpResponse}; + + #[test] + fn test_basics() { + let req = TestRequest::with_hdr(header::ContentType::json()) + .version(Version::HTTP_2) + .set(header::Date(SystemTime::now().into())) + .param("test", "123") + .app_data(10u32) + .to_http_request(); + assert!(req.headers().contains_key(header::CONTENT_TYPE)); + assert!(req.headers().contains_key(header::DATE)); + assert_eq!(&req.match_info()["test"], "123"); + assert_eq!(req.version(), Version::HTTP_2); + let data = req.app_data::().unwrap(); + assert_eq!(*data, 10); + assert_eq!(*data.get_ref(), 10); + } + + #[test] + fn test_response() { + let mut app = init_service( + App::new().service( + web::resource("/index.html") + .route(web::post().to(|| HttpResponse::Ok().body("welcome!"))), + ), + ); + + let req = TestRequest::post() + .uri("/index.html") + .header(header::CONTENT_TYPE, "application/json") + .to_request(); + + let result = read_response(&mut app, req); + assert_eq!(result, Bytes::from_static(b"welcome!")); + } + + #[derive(Serialize, Deserialize)] + pub struct Person { + id: String, + name: String, + } + + #[test] + fn test_response_json() { + let mut app = init_service(App::new().service(web::resource("/people").route( + web::post().to(|person: web::Json| { + HttpResponse::Ok().json(person.into_inner()) + }), + ))); + + let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes(); + + let req = TestRequest::post() + .uri("/people") + .header(header::CONTENT_TYPE, "application/json") + .set_payload(payload) + .to_request(); + + let result: Person = read_response_json(&mut app, req); + assert_eq!(&result.id, "12345"); } } diff --git a/src/web.rs b/src/web.rs index a354222c6..ece869b2a 100644 --- a/src/web.rs +++ b/src/web.rs @@ -103,7 +103,7 @@ pub fn route() -> Route { /// * /{project_id} /// pub fn get() -> Route { - Route::new().method(Method::GET) + method(Method::GET) } /// Create *route* with `POST` method guard. @@ -123,7 +123,7 @@ pub fn get() -> Route { /// * /{project_id} /// pub fn post() -> Route { - Route::new().method(Method::POST) + method(Method::POST) } /// Create *route* with `PUT` method guard. @@ -143,7 +143,7 @@ pub fn post() -> Route { /// * /{project_id} /// pub fn put() -> Route { - Route::new().method(Method::PUT) + method(Method::PUT) } /// Create *route* with `PATCH` method guard. @@ -163,7 +163,7 @@ pub fn put() -> Route { /// * /{project_id} /// pub fn patch() -> Route { - Route::new().method(Method::PATCH) + method(Method::PATCH) } /// Create *route* with `DELETE` method guard. @@ -183,7 +183,7 @@ pub fn patch() -> Route { /// * /{project_id} /// pub fn delete() -> Route { - Route::new().method(Method::DELETE) + method(Method::DELETE) } /// Create *route* with `HEAD` method guard. @@ -203,7 +203,7 @@ pub fn delete() -> Route { /// * /{project_id} /// pub fn head() -> Route { - Route::new().method(Method::HEAD) + method(Method::HEAD) } /// Create *route* and add method guard. From ab4fda60842396b424127d6447d5c383163e4b2e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 14 Apr 2019 20:20:33 -0700 Subject: [PATCH 075/122] update tests --- awc/src/request.rs | 17 +++++++++++++++++ awc/src/test.rs | 20 ++++++++++++++++++++ awc/src/ws.rs | 12 +++++++++++- src/middleware/cors.rs | 11 +++++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/awc/src/request.rs b/awc/src/request.rs index 1daaa28cb..c97e08f81 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -544,6 +544,8 @@ impl fmt::Debug for ClientRequest { #[cfg(test)] mod tests { + use std::time::SystemTime; + use super::*; use crate::Client; @@ -555,6 +557,21 @@ mod tests { assert!(repr.contains("x-test")); } + #[test] + fn test_basics() { + let mut req = Client::new() + .put("/") + .version(Version::HTTP_2) + .set(header::Date(SystemTime::now().into())) + .content_type("plain/text") + .content_length(100); + assert!(req.headers().contains_key(header::CONTENT_TYPE)); + assert!(req.headers().contains_key(header::DATE)); + assert_eq!(req.head.version, Version::HTTP_2); + let _ = req.headers_mut(); + let _ = req.send_body(""); + } + #[test] fn test_client_header() { let req = Client::build() diff --git a/awc/src/test.rs b/awc/src/test.rs index fbbadef3a..8df21e8f9 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -134,3 +134,23 @@ impl TestResponse { } } } + +#[cfg(test)] +mod tests { + use std::time::SystemTime; + + use super::*; + use crate::{cookie, http::header}; + + #[test] + fn test_basics() { + let res = TestResponse::default() + .version(Version::HTTP_2) + .set(header::Date(SystemTime::now().into())) + .cookie(cookie::Cookie::build("name", "value").finish()) + .finish(); + assert!(res.headers().contains_key(header::SET_COOKIE)); + assert!(res.headers().contains_key(header::DATE)); + assert_eq!(res.version(), Version::HTTP_2); + } +} diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 4f0983dc5..1ab6d563d 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -455,10 +455,20 @@ mod tests { .max_frame_size(100) .server_mode() .protocols(&["v1", "v2"]) + .set_header_if_none(header::CONTENT_TYPE, "json") + .set_header_if_none(header::CONTENT_TYPE, "text") .cookie(Cookie::build("cookie1", "value1").finish()); - assert_eq!(req.origin.unwrap().to_str().unwrap(), "test-origin"); + assert_eq!( + req.origin.as_ref().unwrap().to_str().unwrap(), + "test-origin" + ); assert_eq!(req.max_size, 100); assert_eq!(req.server_mode, true); assert_eq!(req.protocols, Some("v1,v2".to_string())); + assert_eq!( + req.head.headers.get(header::CONTENT_TYPE).unwrap(), + header::HeaderValue::from_static("json") + ); + let _ = req.connect(); } } diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 1fa6e6690..813822c98 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -852,6 +852,17 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } + #[test] + fn default() { + let mut cors = + block_on(Cors::default().new_transform(test::ok_service())).unwrap(); + let req = TestRequest::with_header("Origin", "https://www.example.com") + .to_srv_request(); + + let resp = test::call_success(&mut cors, req); + assert_eq!(resp.status(), StatusCode::OK); + } + #[test] fn test_preflight() { let mut cors = Cors::new() From 002c41a7cad9e00b7caebe6dc2794704b11bd1b9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 14 Apr 2019 20:45:44 -0700 Subject: [PATCH 076/122] update trust-dns --- actix-http/CHANGES.md | 6 ++ actix-http/Cargo.toml | 2 +- awc/tests/test_client.rs | 117 +++++++++++++++++++++++---------------- 3 files changed, 75 insertions(+), 50 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 98078d5b3..e1e900594 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,11 @@ # Changes + +### Changed + +* use trust-dns-resolver 0.11.0 + + ## [0.1.0-alpha.5] - 2019-04-12 ### Added diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index cf82a733d..f7ca0bce1 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -81,7 +81,7 @@ time = "0.1" tokio-tcp = "0.1.3" tokio-timer = "0.2" tokio-current-thread = "0.1" -trust-dns-resolver = { version="0.11.0-alpha.3", default-features = false } +trust-dns-resolver = { version="0.11.0", default-features = false } # for secure cookie ring = { version = "0.14.6", optional = true } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 39bcf418c..0f0652c48 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -1,8 +1,9 @@ -use std::io::Write; +use std::io::{Read, Write}; use std::time::Duration; use brotli2::write::BrotliEncoder; use bytes::Bytes; +use flate2::read::GzDecoder; use flate2::write::GzEncoder; use flate2::Compression; use futures::future::Future; @@ -11,6 +12,7 @@ use rand::Rng; use actix_http::HttpService; use actix_http_test::TestServer; use actix_web::http::Cookie; +use actix_web::middleware::{BodyEncoding, Compress}; use actix_web::{http::header, web, App, Error, HttpMessage, HttpRequest, HttpResponse}; use awc::error::SendRequestError; @@ -95,11 +97,9 @@ fn test_timeout_override() { )))) }); - let client = srv.execute(|| { - awc::Client::build() - .timeout(Duration::from_millis(50000)) - .finish() - }); + let client = awc::Client::build() + .timeout(Duration::from_millis(50000)) + .finish(); let request = client .get(srv.url("/")) .timeout(Duration::from_millis(50)) @@ -110,58 +110,77 @@ fn test_timeout_override() { } } -// #[test] -// fn test_connection_close() { -// let mut srv = -// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); +#[test] +fn test_connection_close() { + let mut srv = TestServer::new(|| { + HttpService::new( + App::new().service(web::resource("/").to(|| HttpResponse::Ok())), + ) + }); -// let request = srv.get("/").header("Connection", "close").finish().unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); -// } + let res = srv + .block_on(awc::Client::new().get(srv.url("/")).force_close().send()) + .unwrap(); + assert!(res.status().is_success()); +} -// #[test] -// fn test_with_query_parameter() { -// let mut srv = test::TestServer::new(|app| { -// app.handler(|req: &HttpRequest| match req.query().get("qp") { -// Some(_) => HttpResponse::Ok().finish(), -// None => HttpResponse::BadRequest().finish(), -// }) -// }); +#[test] +fn test_with_query_parameter() { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").to( + |req: HttpRequest| { + if req.query_string().contains("qp") { + HttpResponse::Ok() + } else { + HttpResponse::BadRequest() + } + }, + ))) + }); -// let request = srv.get("/").uri(srv.url("/?qp=5").as_str()).finish().unwrap(); + let res = srv + .block_on(awc::Client::new().get(srv.url("/?qp=5")).send()) + .unwrap(); + assert!(res.status().is_success()); +} -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); -// } +#[test] +fn test_no_decompress() { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().wrap(Compress::default()).service( + web::resource("/").route(web::to(|| { + let mut res = HttpResponse::Ok().body(STR); + res.encoding(header::ContentEncoding::Gzip); + res + })), + )) + }); -// #[test] -// fn test_no_decompress() { -// let mut srv = -// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); + let mut res = srv + .block_on(awc::Client::new().get(srv.url("/")).no_decompress().send()) + .unwrap(); + assert!(res.status().is_success()); -// let request = srv.get("/").disable_decompress().finish().unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); + // read response + let bytes = srv.block_on(res.body()).unwrap(); -// // read response -// let bytes = srv.execute(response.body()).unwrap(); + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -// let mut e = GzDecoder::new(&bytes[..]); -// let mut dec = Vec::new(); -// e.read_to_end(&mut dec).unwrap(); -// assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + // POST + let mut res = srv + .block_on(awc::Client::new().post(srv.url("/")).no_decompress().send()) + .unwrap(); + assert!(res.status().is_success()); -// // POST -// let request = srv.post().disable_decompress().finish().unwrap(); -// let response = srv.execute(request.send()).unwrap(); - -// let bytes = srv.execute(response.body()).unwrap(); -// let mut e = GzDecoder::new(&bytes[..]); -// let mut dec = Vec::new(); -// e.read_to_end(&mut dec).unwrap(); -// assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -// } + let bytes = srv.block_on(res.body()).unwrap(); + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); +} #[test] fn test_client_gzip_encoding() { From 1eebd47072ed2db4e7a3eb0a4835033de04e6d69 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 14 Apr 2019 21:00:16 -0700 Subject: [PATCH 077/122] fix warnings --- actix-http/CHANGES.md | 1 + actix-http/src/client/connector.rs | 3 +-- actix-web-actors/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index e1e900594..236436bb1 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,6 @@ # Changes +## [0.1.0] - 2019-04-xx ### Changed diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 1c9a3aab0..ed6207f9f 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -57,8 +57,7 @@ impl Connector<(), ()> { let ssl = { #[cfg(feature = "ssl")] { - use log::error; - use openssl::ssl::{SslConnector, SslMethod}; + use openssl::ssl::SslMethod; let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap(); let _ = ssl diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 084598a13..f56b47fbf 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -18,7 +18,7 @@ name = "actix_web_actors" path = "src/lib.rs" [dependencies] -actix = "0.8.0-alpha.3" +actix = "0.8.0" actix-web = "1.0.0-alpha.5" actix-http = "0.1.0-alpha.5" actix-codec = "0.1.2" From 09cdf1e30268ef6a903a69e7b596d2a77e04a50c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 15 Apr 2019 07:32:49 -0700 Subject: [PATCH 078/122] Rename RouterConfig to ServiceConfig --- CHANGES.md | 4 ++++ actix-files/src/lib.rs | 4 ++-- actix-web-codegen/src/route.rs | 2 +- src/app.rs | 8 ++++---- src/app_service.rs | 5 ++--- src/config.rs | 20 ++++++++++---------- src/lib.rs | 2 +- src/resource.rs | 4 ++-- src/scope.rs | 4 ++-- src/service.rs | 8 ++++---- src/web.rs | 2 +- 11 files changed, 33 insertions(+), 30 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0d40cf31e..3cc6e5ef3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,10 @@ * Added helper functions for reading test response body, `test::read_response()` and test::read_response_json()` +### Changed + +* Rename `RouterConfig` to `ServiceConfig` + ### Fixed * Fixed `TestRequest::app_data()` diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 89eead562..4923536f6 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -10,7 +10,7 @@ use std::{cmp, io}; use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::{IntoNewService, NewService, Service}; use actix_web::dev::{ - HttpServiceFactory, Payload, ResourceDef, ServiceConfig, ServiceRequest, + AppService, HttpServiceFactory, Payload, ResourceDef, ServiceRequest, ServiceResponse, }; use actix_web::error::{BlockingError, Error, ErrorInternalServerError}; @@ -349,7 +349,7 @@ impl Files { } impl HttpServiceFactory for Files { - fn register(self, config: &mut ServiceConfig) { + fn register(self, config: &mut AppService) { if self.default.borrow().is_none() { *self.default.borrow_mut() = Some(config.default_service()); } diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index 348ce86ae..1a5f79298 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -62,7 +62,7 @@ impl fmt::Display for Args { pub struct {name}; impl actix_web::dev::HttpServiceFactory for {name} {{ - fn register(self, config: &mut actix_web::dev::ServiceConfig) {{ + fn register(self, config: &mut actix_web::dev::AppService) {{ {ast} let resource = actix_web::Resource::new(\"{path}\"){guards}.{to}({name}); diff --git a/src/app.rs b/src/app.rs index 6c34123de..bf6f25800 100644 --- a/src/app.rs +++ b/src/app.rs @@ -12,7 +12,7 @@ use actix_service::{ use futures::IntoFuture; use crate::app_service::{AppEntry, AppInit, AppRoutingFactory}; -use crate::config::{AppConfig, AppConfigInner, RouterConfig}; +use crate::config::{AppConfig, AppConfigInner, ServiceConfig}; use crate::data::{Data, DataFactory}; use crate::dev::ResourceDef; use crate::error::Error; @@ -125,7 +125,7 @@ where /// use actix_web::{web, middleware, App, HttpResponse}; /// /// // this function could be located in different module - /// fn config(cfg: &mut web::RouterConfig) { + /// fn config(cfg: &mut web::ServiceConfig) { /// cfg.service(web::resource("/test") /// .route(web::get().to(|| HttpResponse::Ok())) /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) @@ -141,9 +141,9 @@ where /// ``` pub fn configure(mut self, f: F) -> Self where - F: Fn(&mut RouterConfig), + F: Fn(&mut ServiceConfig), { - let mut cfg = RouterConfig::new(); + let mut cfg = ServiceConfig::new(); f(&mut cfg); self.data.extend(cfg.data); self.services.extend(cfg.services); diff --git a/src/app_service.rs b/src/app_service.rs index a5d906363..63bf84e76 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -10,7 +10,7 @@ use actix_service::{fn_service, NewService, Service}; use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, Poll}; -use crate::config::{AppConfig, ServiceConfig}; +use crate::config::{AppConfig, AppService}; use crate::data::{DataFactory, DataFactoryResult}; use crate::error::Error; use crate::guard::Guard; @@ -77,8 +77,7 @@ where loc_cfg.addr = cfg.local_addr(); } - let mut config = - ServiceConfig::new(self.config.borrow().clone(), default.clone()); + let mut config = AppService::new(self.config.borrow().clone(), default.clone()); // register services std::mem::replace(&mut *self.services.borrow_mut(), Vec::new()) diff --git a/src/config.rs b/src/config.rs index c28b66782..07bfebcf1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -23,7 +23,7 @@ type HttpNewService = boxed::BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; /// Application configuration -pub struct ServiceConfig { +pub struct AppService { config: AppConfig, root: bool, default: Rc, @@ -35,10 +35,10 @@ pub struct ServiceConfig { )>, } -impl ServiceConfig { +impl AppService { /// Crate server settings instance pub(crate) fn new(config: AppConfig, default: Rc) -> Self { - ServiceConfig { + AppService { config, default, root: true, @@ -63,7 +63,7 @@ impl ServiceConfig { } pub(crate) fn clone_config(&self) -> Self { - ServiceConfig { + AppService { config: self.config.clone(), default: self.default.clone(), services: Vec::new(), @@ -165,17 +165,17 @@ impl Default for AppConfigInner { } } -/// Router config. It is used for external configuration. +/// Service config 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 struct ServiceConfig { pub(crate) services: Vec>, pub(crate) data: Vec>, pub(crate) external: Vec, } -impl RouterConfig { +impl ServiceConfig { pub(crate) fn new() -> Self { Self { services: Vec::new(), @@ -261,7 +261,7 @@ mod tests { #[test] fn test_data() { - let cfg = |cfg: &mut RouterConfig| { + let cfg = |cfg: &mut ServiceConfig| { cfg.data(10usize); }; @@ -276,7 +276,7 @@ mod tests { #[test] fn test_data_factory() { - let cfg = |cfg: &mut RouterConfig| { + let cfg = |cfg: &mut ServiceConfig| { cfg.data_factory(|| Ok::<_, ()>(10usize)); }; @@ -288,7 +288,7 @@ mod tests { let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let cfg2 = |cfg: &mut RouterConfig| { + let cfg2 = |cfg: &mut ServiceConfig| { cfg.data_factory(|| Ok::<_, ()>(10u32)); }; let mut srv = init_service( diff --git a/src/lib.rs b/src/lib.rs index 6636d96d4..6abf37c1e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -133,7 +133,7 @@ pub mod dev { //! use actix_web::dev::*; //! ``` - pub use crate::config::{AppConfig, ServiceConfig}; + pub use crate::config::{AppConfig, AppService}; pub use crate::info::ConnectionInfo; pub use crate::rmap::ResourceMap; pub use crate::service::{HttpServiceFactory, ServiceRequest, ServiceResponse}; diff --git a/src/resource.rs b/src/resource.rs index a8268302d..15abcadaf 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -10,7 +10,7 @@ use actix_service::{ use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; -use crate::dev::{insert_slash, HttpServiceFactory, ResourceDef, ServiceConfig}; +use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef}; use crate::extract::FromRequest; use crate::guard::Guard; use crate::handler::{AsyncFactory, Factory}; @@ -347,7 +347,7 @@ where InitError = (), > + 'static, { - fn register(mut self, config: &mut ServiceConfig) { + fn register(mut self, config: &mut AppService) { let guards = if self.guards.is_empty() { None } else { diff --git a/src/scope.rs b/src/scope.rs index 5678158e8..0ac73a2e8 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -11,7 +11,7 @@ use actix_service::{ use futures::future::{ok, Either, Future, FutureResult}; use futures::{Async, IntoFuture, Poll}; -use crate::dev::{HttpServiceFactory, ServiceConfig}; +use crate::dev::{AppService, HttpServiceFactory}; use crate::error::Error; use crate::guard::Guard; use crate::resource::Resource; @@ -303,7 +303,7 @@ where InitError = (), > + 'static, { - fn register(self, config: &mut ServiceConfig) { + fn register(self, config: &mut AppService) { // update default resource if needed if self.default.borrow().is_none() { *self.default.borrow_mut() = Some(config.default_service()); diff --git a/src/service.rs b/src/service.rs index e5b0896e4..8bc2ff9a5 100644 --- a/src/service.rs +++ b/src/service.rs @@ -10,16 +10,16 @@ use actix_http::{ use actix_router::{Path, Resource, Url}; use futures::future::{ok, FutureResult, IntoFuture}; -use crate::config::{AppConfig, ServiceConfig}; +use crate::config::{AppConfig, AppService}; use crate::data::Data; use crate::request::HttpRequest; pub trait HttpServiceFactory { - fn register(self, config: &mut ServiceConfig); + fn register(self, config: &mut AppService); } pub(crate) trait ServiceFactory { - fn register(&mut self, config: &mut ServiceConfig); + fn register(&mut self, config: &mut AppService); } pub(crate) struct ServiceFactoryWrapper { @@ -38,7 +38,7 @@ impl ServiceFactory for ServiceFactoryWrapper where T: HttpServiceFactory, { - fn register(&mut self, config: &mut ServiceConfig) { + fn register(&mut self, config: &mut AppService) { if let Some(item) = self.factory.take() { item.register(config) } diff --git a/src/web.rs b/src/web.rs index ece869b2a..079dec516 100644 --- a/src/web.rs +++ b/src/web.rs @@ -13,7 +13,7 @@ use crate::responder::Responder; use crate::route::Route; use crate::scope::Scope; -pub use crate::config::RouterConfig; +pub use crate::config::ServiceConfig; pub use crate::data::{Data, RouteData}; pub use crate::request::HttpRequest; pub use crate::types::*; From 7a28b32f6d12b51d26a51e93a182e3e83504eb68 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 15 Apr 2019 07:44:07 -0700 Subject: [PATCH 079/122] Rename test::call_success to test::call_service --- CHANGES.md | 2 ++ src/app.rs | 10 +++++----- src/config.rs | 26 ++++++++++++++++++++++++-- src/middleware/cors.rs | 30 +++++++++++++++--------------- src/middleware/errhandlers.rs | 4 ++-- src/middleware/identity.rs | 8 ++++---- src/request.rs | 6 +++--- src/resource.rs | 16 ++++++++-------- src/route.rs | 12 ++++++------ src/scope.rs | 6 +++--- src/test.rs | 4 ++-- 11 files changed, 74 insertions(+), 50 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3cc6e5ef3..8909f3e29 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,8 @@ * Rename `RouterConfig` to `ServiceConfig` +* Rename `test::call_success` to `test::call_service` + ### Fixed * Fixed `TestRequest::app_data()` diff --git a/src/app.rs b/src/app.rs index bf6f25800..c0bbc8b2b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -436,7 +436,7 @@ mod tests { use super::*; use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::service::{ServiceRequest, ServiceResponse}; - use crate::test::{block_on, call_success, init_service, TestRequest}; + use crate::test::{block_on, call_service, init_service, TestRequest}; use crate::{web, Error, HttpResponse}; #[test] @@ -527,7 +527,7 @@ mod tests { .route("/test", web::get().to(|| HttpResponse::Ok())), ); let req = TestRequest::with_uri("/test").to_request(); - let resp = call_success(&mut srv, req); + let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -543,7 +543,7 @@ mod tests { .wrap(md), ); let req = TestRequest::with_uri("/test").to_request(); - let resp = call_success(&mut srv, req); + let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -567,7 +567,7 @@ mod tests { .service(web::resource("/test").to(|| HttpResponse::Ok())), ); let req = TestRequest::with_uri("/test").to_request(); - let resp = call_success(&mut srv, req); + let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -591,7 +591,7 @@ mod tests { }), ); let req = TestRequest::with_uri("/test").to_request(); - let resp = call_success(&mut srv, req); + let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), diff --git a/src/config.rs b/src/config.rs index 07bfebcf1..a8caba4d2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -255,8 +255,8 @@ mod tests { use actix_service::Service; use super::*; - use crate::http::StatusCode; - use crate::test::{block_on, init_service, TestRequest}; + use crate::http::{Method, StatusCode}; + use crate::test::{block_on, call_service, init_service, TestRequest}; use crate::{web, App, HttpResponse}; #[test] @@ -300,4 +300,26 @@ mod tests { let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } + + #[test] + fn test_service() { + let mut srv = init_service(App::new().configure(|cfg| { + cfg.service( + web::resource("/test").route(web::get().to(|| HttpResponse::Created())), + ) + .route("/index.html", web::get().to(|| HttpResponse::Ok())); + })); + + let req = TestRequest::with_uri("/test") + .method(Method::GET) + .to_request(); + let resp = call_service(&mut srv, req); + assert_eq!(resp.status(), StatusCode::CREATED); + + let req = TestRequest::with_uri("/index.html") + .method(Method::GET) + .to_request(); + let resp = call_service(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + } } diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 813822c98..12cd0b83a 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -848,7 +848,7 @@ mod tests { let req = TestRequest::with_header("Origin", "https://www.example.com") .to_srv_request(); - let resp = test::call_success(&mut cors, req); + let resp = test::call_service(&mut cors, req); assert_eq!(resp.status(), StatusCode::OK); } @@ -859,7 +859,7 @@ mod tests { let req = TestRequest::with_header("Origin", "https://www.example.com") .to_srv_request(); - let resp = test::call_success(&mut cors, req); + let resp = test::call_service(&mut cors, req); assert_eq!(resp.status(), StatusCode::OK); } @@ -879,7 +879,7 @@ mod tests { 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); + let resp = test::call_service(&mut cors, req); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_header("Origin", "https://www.example.com") @@ -899,7 +899,7 @@ mod tests { .method(Method::OPTIONS) .to_srv_request(); - let resp = test::call_success(&mut cors, req); + let resp = test::call_service(&mut cors, req); assert_eq!( &b"*"[..], resp.headers() @@ -945,7 +945,7 @@ mod tests { .method(Method::OPTIONS) .to_srv_request(); - let resp = test::call_success(&mut cors, req); + let resp = test::call_service(&mut cors, req); assert_eq!(resp.status(), StatusCode::OK); } @@ -984,7 +984,7 @@ mod tests { .method(Method::GET) .to_srv_request(); - let resp = test::call_success(&mut cors, req); + let resp = test::call_service(&mut cors, req); assert_eq!(resp.status(), StatusCode::OK); } @@ -993,7 +993,7 @@ mod tests { let mut cors = Cors::new().disable_preflight().finish(test::ok_service()); let req = TestRequest::default().method(Method::GET).to_srv_request(); - let resp = test::call_success(&mut cors, req); + let resp = test::call_service(&mut cors, req); assert!(resp .headers() .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) @@ -1002,7 +1002,7 @@ mod tests { let req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::OPTIONS) .to_srv_request(); - let resp = test::call_success(&mut cors, req); + let resp = test::call_service(&mut cors, req); assert_eq!( &b"https://www.example.com"[..], resp.headers() @@ -1029,7 +1029,7 @@ mod tests { .method(Method::OPTIONS) .to_srv_request(); - let resp = test::call_success(&mut cors, req); + let resp = test::call_service(&mut cors, req); assert_eq!( &b"*"[..], resp.headers() @@ -1075,7 +1075,7 @@ mod tests { let req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::OPTIONS) .to_srv_request(); - let resp = test::call_success(&mut cors, req); + let resp = test::call_service(&mut cors, req); assert_eq!( &b"Accept, Origin"[..], resp.headers().get(header::VARY).unwrap().as_bytes() @@ -1091,7 +1091,7 @@ mod tests { .method(Method::OPTIONS) .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") .to_srv_request(); - let resp = test::call_success(&mut cors, req); + let resp = test::call_service(&mut cors, req); let origins_str = resp .headers() @@ -1115,7 +1115,7 @@ mod tests { .method(Method::GET) .to_srv_request(); - let resp = test::call_success(&mut cors, req); + let resp = test::call_service(&mut cors, req); assert_eq!( &b"https://example.com"[..], resp.headers() @@ -1128,7 +1128,7 @@ mod tests { .method(Method::GET) .to_srv_request(); - let resp = test::call_success(&mut cors, req); + let resp = test::call_service(&mut cors, req); assert_eq!( &b"https://example.org"[..], resp.headers() @@ -1151,7 +1151,7 @@ mod tests { .method(Method::OPTIONS) .to_srv_request(); - let resp = test::call_success(&mut cors, req); + let resp = test::call_service(&mut cors, req); assert_eq!( &b"https://example.com"[..], resp.headers() @@ -1165,7 +1165,7 @@ mod tests { .method(Method::OPTIONS) .to_srv_request(); - let resp = test::call_success(&mut cors, req); + let resp = test::call_service(&mut cors, req); assert_eq!( &b"https://example.org"[..], resp.headers() diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index 56745630b..aa36b6a4d 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -172,7 +172,7 @@ mod tests { ) .unwrap(); - let resp = test::call_success(&mut mw, TestRequest::default().to_srv_request()); + let resp = test::call_service(&mut mw, TestRequest::default().to_srv_request()); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } @@ -198,7 +198,7 @@ mod tests { ) .unwrap(); - let resp = test::call_success(&mut mw, TestRequest::default().to_srv_request()); + let resp = test::call_service(&mut mw, TestRequest::default().to_srv_request()); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } } diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index 5bc3f923c..6027aaa7d 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -500,15 +500,15 @@ mod tests { })), ); let resp = - test::call_success(&mut srv, TestRequest::with_uri("/index").to_request()); + test::call_service(&mut srv, TestRequest::with_uri("/index").to_request()); assert_eq!(resp.status(), StatusCode::OK); let resp = - test::call_success(&mut srv, TestRequest::with_uri("/login").to_request()); + test::call_service(&mut srv, TestRequest::with_uri("/login").to_request()); assert_eq!(resp.status(), StatusCode::OK); let c = resp.response().cookies().next().unwrap().to_owned(); - let resp = test::call_success( + let resp = test::call_service( &mut srv, TestRequest::with_uri("/index") .cookie(c.clone()) @@ -516,7 +516,7 @@ mod tests { ); assert_eq!(resp.status(), StatusCode::CREATED); - let resp = test::call_success( + let resp = test::call_service( &mut srv, TestRequest::with_uri("/logout") .cookie(c.clone()) diff --git a/src/request.rs b/src/request.rs index 082d36b61..5823c08ca 100644 --- a/src/request.rs +++ b/src/request.rs @@ -324,7 +324,7 @@ mod tests { use super::*; use crate::dev::{ResourceDef, ResourceMap}; use crate::http::{header, StatusCode}; - use crate::test::{call_success, init_service, TestRequest}; + use crate::test::{call_service, init_service, TestRequest}; use crate::{web, App, HttpResponse}; #[test] @@ -453,7 +453,7 @@ mod tests { )); let req = TestRequest::default().to_request(); - let resp = call_success(&mut srv, req); + let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::OK); let mut srv = init_service(App::new().data(10u32).service( @@ -467,7 +467,7 @@ mod tests { )); let req = TestRequest::default().to_request(); - let resp = call_success(&mut srv, req); + let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } } diff --git a/src/resource.rs b/src/resource.rs index 15abcadaf..1f1e6e157 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -545,7 +545,7 @@ mod tests { use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::service::{ServiceRequest, ServiceResponse}; - use crate::test::{call_success, init_service, TestRequest}; + use crate::test::{call_service, init_service, TestRequest}; use crate::{web, App, Error, HttpResponse}; fn md( @@ -577,7 +577,7 @@ mod tests { ), ); let req = TestRequest::with_uri("/test").to_request(); - let resp = call_success(&mut srv, req); + let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -603,7 +603,7 @@ mod tests { ), ); let req = TestRequest::with_uri("/test").to_request(); - let resp = call_success(&mut srv, req); + let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -618,7 +618,7 @@ mod tests { sleep(Duration::from_millis(100)).then(|_| HttpResponse::Ok()) }))); let req = TestRequest::with_uri("/test").to_request(); - let resp = call_success(&mut srv, req); + let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::OK); } @@ -634,13 +634,13 @@ mod tests { }), ); let req = TestRequest::with_uri("/test").to_request(); - let resp = call_success(&mut srv, req); + let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/test") .method(Method::POST) .to_request(); - let resp = call_success(&mut srv, req); + let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); let mut srv = init_service( @@ -654,13 +654,13 @@ mod tests { ); let req = TestRequest::with_uri("/test").to_request(); - let resp = call_success(&mut srv, req); + let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/test") .method(Method::POST) .to_request(); - let resp = call_success(&mut srv, req); + let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } } diff --git a/src/route.rs b/src/route.rs index b1c7b2ab4..7b9f36a64 100644 --- a/src/route.rs +++ b/src/route.rs @@ -422,7 +422,7 @@ mod tests { use tokio_timer::sleep; use crate::http::{Method, StatusCode}; - use crate::test::{call_success, init_service, TestRequest}; + use crate::test::{call_service, init_service, TestRequest}; use crate::{error, web, App, HttpResponse}; #[test] @@ -450,31 +450,31 @@ mod tests { let req = TestRequest::with_uri("/test") .method(Method::GET) .to_request(); - let resp = call_success(&mut srv, req); + let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/test") .method(Method::POST) .to_request(); - let resp = call_success(&mut srv, req); + let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::CREATED); let req = TestRequest::with_uri("/test") .method(Method::PUT) .to_request(); - let resp = call_success(&mut srv, req); + let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/test") .method(Method::DELETE) .to_request(); - let resp = call_success(&mut srv, req); + let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/test") .method(Method::HEAD) .to_request(); - let resp = call_success(&mut srv, req); + let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); } } diff --git a/src/scope.rs b/src/scope.rs index 0ac73a2e8..81bf84d23 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -535,7 +535,7 @@ mod tests { use crate::dev::{Body, ResponseBody}; use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::service::{ServiceRequest, ServiceResponse}; - use crate::test::{block_on, call_success, init_service, TestRequest}; + use crate::test::{block_on, call_service, init_service, TestRequest}; use crate::{guard, web, App, Error, HttpRequest, HttpResponse}; #[test] @@ -912,7 +912,7 @@ mod tests { web::resource("/test").route(web::get().to(|| HttpResponse::Ok())), ))); let req = TestRequest::with_uri("/app/test").to_request(); - let resp = call_success(&mut srv, req); + let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -938,7 +938,7 @@ mod tests { ), ); let req = TestRequest::with_uri("/app/test").to_request(); - let resp = call_success(&mut srv, req); + let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), diff --git a/src/test.rs b/src/test.rs index 638bcdce6..89562c61a 100644 --- a/src/test.rs +++ b/src/test.rs @@ -137,11 +137,11 @@ where /// let req = test::TestRequest::with_uri("/test").to_request(); /// /// // Call application -/// let resp = test::call_success(&mut app, req); +/// let resp = test::call_service(&mut app, req); /// assert_eq!(resp.status(), StatusCode::OK); /// } /// ``` -pub fn call_success(app: &mut S, req: R) -> S::Response +pub fn call_service(app: &mut S, req: R) -> S::Response where S: Service, Error = E>, E: std::fmt::Debug, From 14252f5ef2987337ac218d375118781dfd939dcd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 15 Apr 2019 09:09:21 -0700 Subject: [PATCH 080/122] use test::call_service --- actix-files/src/lib.rs | 32 ++++++++++++++++---------------- actix-web-actors/src/context.rs | 4 ++-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 4923536f6..8ff6b932c 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -775,7 +775,7 @@ mod tests { ); let request = TestRequest::get().uri("/").to_request(); - let response = test::call_success(&mut srv, request); + let response = test::call_service(&mut srv, request); assert_eq!(response.status(), StatusCode::OK); let content_disposition = response @@ -799,7 +799,7 @@ mod tests { .uri("/t%65st/Cargo.toml") .header(header::RANGE, "bytes=10-20") .to_request(); - let response = test::call_success(&mut srv, request); + let response = test::call_service(&mut srv, request); assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); // Invalid range header @@ -807,7 +807,7 @@ mod tests { .uri("/t%65st/Cargo.toml") .header(header::RANGE, "bytes=1-0") .to_request(); - let response = test::call_success(&mut srv, request); + let response = test::call_service(&mut srv, request); assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); } @@ -824,7 +824,7 @@ mod tests { .header(header::RANGE, "bytes=10-20") .to_request(); - let response = test::call_success(&mut srv, request); + let response = test::call_service(&mut srv, request); let contentrange = response .headers() .get(header::CONTENT_RANGE) @@ -839,7 +839,7 @@ mod tests { .uri("/t%65st/tests/test.binary") .header(header::RANGE, "bytes=10-5") .to_request(); - let response = test::call_success(&mut srv, request); + let response = test::call_service(&mut srv, request); let contentrange = response .headers() @@ -862,7 +862,7 @@ mod tests { .uri("/t%65st/tests/test.binary") .header(header::RANGE, "bytes=10-20") .to_request(); - let response = test::call_success(&mut srv, request); + let response = test::call_service(&mut srv, request); let contentlength = response .headers() @@ -878,7 +878,7 @@ mod tests { .uri("/t%65st/tests/test.binary") .header(header::RANGE, "bytes=10-8") .to_request(); - let response = test::call_success(&mut srv, request); + let response = test::call_service(&mut srv, request); assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); // Without range header @@ -886,7 +886,7 @@ mod tests { .uri("/t%65st/tests/test.binary") // .no_default_headers() .to_request(); - let response = test::call_success(&mut srv, request); + let response = test::call_service(&mut srv, request); let contentlength = response .headers() @@ -901,7 +901,7 @@ mod tests { let request = TestRequest::get() .uri("/t%65st/tests/test.binary") .to_request(); - let mut response = test::call_success(&mut srv, request); + let mut response = test::call_service(&mut srv, request); // with enabled compression // { @@ -932,7 +932,7 @@ mod tests { let request = TestRequest::get() .uri("/tests/test%20space.binary") .to_request(); - let mut response = test::call_success(&mut srv, request); + let mut response = test::call_service(&mut srv, request); assert_eq!(response.status(), StatusCode::OK); let bytes = @@ -975,7 +975,7 @@ mod tests { .uri("/") .header(header::ACCEPT_ENCODING, "gzip") .to_request(); - let res = test::call_success(&mut srv, request); + let res = test::call_service(&mut srv, request); assert_eq!(res.status(), StatusCode::OK); assert!(!res.headers().contains_key(header::CONTENT_ENCODING)); } @@ -994,7 +994,7 @@ mod tests { .uri("/") .header(header::ACCEPT_ENCODING, "gzip") .to_request(); - let res = test::call_success(&mut srv, request); + let res = test::call_service(&mut srv, request); assert_eq!(res.status(), StatusCode::OK); assert_eq!( res.headers() @@ -1021,20 +1021,20 @@ mod tests { ); let req = TestRequest::with_uri("/missing").to_request(); - let resp = test::call_success(&mut srv, req); + let resp = test::call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::NOT_FOUND); let mut srv = test::init_service(App::new().service(Files::new("/", "."))); let req = TestRequest::default().to_request(); - let resp = test::call_success(&mut srv, req); + let resp = test::call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::NOT_FOUND); let mut srv = test::init_service( App::new().service(Files::new("/", ".").show_files_listing()), ); let req = TestRequest::with_uri("/tests").to_request(); - let mut resp = test::call_success(&mut srv, req); + let mut resp = test::call_service(&mut srv, req); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/html; charset=utf-8" @@ -1067,7 +1067,7 @@ mod tests { .unwrap(); let req = TestRequest::with_uri("/missing").to_srv_request(); - let mut resp = test::call_success(&mut st, req); + let mut resp = test::call_service(&mut st, req); assert_eq!(resp.status(), StatusCode::OK); let bytes = test::block_on(resp.take_body().fold(BytesMut::new(), |mut b, c| { diff --git a/actix-web-actors/src/context.rs b/actix-web-actors/src/context.rs index da473ff3f..31b29500a 100644 --- a/actix-web-actors/src/context.rs +++ b/actix-web-actors/src/context.rs @@ -199,7 +199,7 @@ mod tests { use actix::Actor; use actix_web::http::StatusCode; - use actix_web::test::{block_on, call_success, init_service, TestRequest}; + use actix_web::test::{block_on, call_service, init_service, TestRequest}; use actix_web::{web, App, HttpResponse}; use bytes::{Bytes, BytesMut}; @@ -237,7 +237,7 @@ mod tests { }))); let req = TestRequest::with_uri("/test").to_request(); - let mut resp = call_success(&mut srv, req); + let mut resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::OK); let body = block_on(resp.take_body().fold( From 7f674febb18ad7347fff98bd081e573472930a14 Mon Sep 17 00:00:00 2001 From: Travis Harmon Date: Mon, 15 Apr 2019 19:55:06 -0400 Subject: [PATCH 081/122] add 422 to httpcodes.rs (#782) --- actix-http/src/httpcodes.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/actix-http/src/httpcodes.rs b/actix-http/src/httpcodes.rs index 85c384374..e7eda2da8 100644 --- a/actix-http/src/httpcodes.rs +++ b/actix-http/src/httpcodes.rs @@ -60,6 +60,7 @@ impl Response { STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE); STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); + STATIC_RESP!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY); STATIC_RESP!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR); STATIC_RESP!(NotImplemented, StatusCode::NOT_IMPLEMENTED); From a116c4c2c799e24f21bd57edde03d38ae64abea7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 16 Apr 2019 09:54:02 -0700 Subject: [PATCH 082/122] Expose peer addr via Request::peer_addr() and RequestHead::peer_addr --- Cargo.toml | 8 ++--- actix-http/CHANGES.md | 6 +++- actix-http/Cargo.toml | 14 ++++---- actix-http/src/h1/codec.rs | 9 ++--- actix-http/src/h1/dispatcher.rs | 20 ++++++----- actix-http/src/h1/service.rs | 20 +++++------ actix-http/src/h2/dispatcher.rs | 19 +++++----- actix-http/src/h2/service.rs | 58 +++++++++++++++++------------- actix-http/src/message.rs | 3 ++ actix-http/src/request.rs | 12 ++++++- actix-http/src/service.rs | 64 ++++++++++++++++++++++++++------- actix-http/src/test.rs | 15 ++++++++ actix-http/tests/test_server.rs | 7 +++- 13 files changed, 170 insertions(+), 85 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f2835d6c3..68979d096 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,8 +72,8 @@ actix-router = "0.1.2" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-alpha.6" actix-http = { version = "0.1.0-alpha.5", features=["fail"] } -actix-server = "0.4.2" -actix-server-config = "0.1.0" +actix-server = "0.4.3" +actix-server-config = "0.1.1" actix-threadpool = "0.1.0" awc = { version = "0.1.0-alpha.6", optional = true } @@ -81,7 +81,7 @@ bytes = "0.4" derive_more = "0.14" encoding = "0.2" futures = "0.1" -hashbrown = "0.2.1" +hashbrown = "0.2.2" log = "0.4" mime = "0.3" net2 = "0.2.33" @@ -121,4 +121,4 @@ 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" } \ No newline at end of file +awc = { path = "awc" } diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 236436bb1..dc56b0401 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,10 @@ # Changes -## [0.1.0] - 2019-04-xx +## [0.1.0] - 2019-04-16 + +### Added + +* Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr` ### Changed diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index f7ca0bce1..746699a07 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.1.0-alpha.5" +version = "0.1.0" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" @@ -26,7 +26,7 @@ path = "src/lib.rs" default = [] # openssl -ssl = ["openssl", "actix-connect/ssl"] +ssl = ["openssl", "actix-connect/ssl", "actix-server-config/ssl"] # brotli encoding, requires c compiler brotli = ["brotli2"] @@ -48,7 +48,7 @@ actix-service = "0.3.6" actix-codec = "0.1.2" actix-connect = "0.1.4" actix-utils = "0.3.5" -actix-server-config = "0.1.0" +actix-server-config = "0.1.1" actix-threadpool = "0.1.0" base64 = "0.10" @@ -60,7 +60,7 @@ derive_more = "0.14" either = "1.5.2" encoding = "0.2" futures = "0.1" -hashbrown = "0.2.0" +hashbrown = "0.2.2" h2 = "0.1.16" http = "0.1.17" httparse = "1.3" @@ -76,10 +76,10 @@ serde = "1.0" serde_json = "1.0" sha1 = "0.6" slab = "0.4" -serde_urlencoded = "0.5.3" +serde_urlencoded = "0.5.5" time = "0.1" tokio-tcp = "0.1.3" -tokio-timer = "0.2" +tokio-timer = "0.2.8" tokio-current-thread = "0.1" trust-dns-resolver = { version="0.11.0", default-features = false } @@ -96,7 +96,7 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.2" -actix-server = { version = "0.4.1", features=["ssl"] } +actix-server = { version = "0.4.3", features=["ssl"] } actix-connect = { version = "0.1.4", features=["ssl"] } actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } env_logger = "0.6" diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index 1f3983adb..1e1e1602f 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -1,6 +1,6 @@ #![allow(unused_imports, unused_variables, dead_code)] -use std::fmt; -use std::io::{self, Write}; +use std::io::Write; +use std::{fmt, io, net}; use actix_codec::{Decoder, Encoder}; use bitflags::bitflags; @@ -40,7 +40,6 @@ pub struct Codec { // encoder part flags: Flags, encoder: encoder::MessageEncoder>, - // headers_size: u32, } impl Default for Codec { @@ -67,13 +66,11 @@ impl Codec { }; Codec { config, + flags, decoder: decoder::MessageDecoder::default(), payload: None, version: Version::HTTP_11, ctype: ConnectionType::Close, - - flags, - // headers_size: 0, encoder: encoder::MessageEncoder::default(), } } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index cf39b8232..758466837 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -1,8 +1,9 @@ use std::collections::VecDeque; use std::time::Instant; -use std::{fmt, io}; +use std::{fmt, io, net}; -use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed, FramedParts}; +use actix_codec::{Decoder, Encoder, Framed, FramedParts}; +use actix_server_config::IoStream; use actix_service::Service; use actix_utils::cloneable::CloneableService; use bitflags::bitflags; @@ -81,6 +82,7 @@ where expect: CloneableService, upgrade: Option>, flags: Flags, + peer_addr: Option, error: Option, state: State, @@ -161,7 +163,7 @@ impl PartialEq for PollResponse { impl Dispatcher where - T: AsyncRead + AsyncWrite, + T: IoStream, S: Service, S::Error: Into, S::Response: Into>, @@ -220,14 +222,15 @@ where Dispatcher { inner: DispatcherState::Normal(InnerDispatcher { - io, - codec, - read_buf, write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), payload: None, state: State::None, error: None, + peer_addr: io.peer_addr(), messages: VecDeque::new(), + io, + codec, + read_buf, service, expect, upgrade, @@ -241,7 +244,7 @@ where impl InnerDispatcher where - T: AsyncRead + AsyncWrite, + T: IoStream, S: Service, S::Error: Into, S::Response: Into>, @@ -490,6 +493,7 @@ where match msg { Message::Item(mut req) => { let pl = self.codec.message_type(); + req.head_mut().peer_addr = self.peer_addr; if pl == MessageType::Stream && self.upgrade.is_some() { self.messages.push_back(DispatcherMessage::Upgrade(req)); @@ -649,7 +653,7 @@ where impl Future for Dispatcher where - T: AsyncRead + AsyncWrite, + T: IoStream, S: Service, S::Error: Into, S::Response: Into>, diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index f92fd0c89..ecf6c8b93 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -1,8 +1,8 @@ use std::fmt; use std::marker::PhantomData; -use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_server_config::{Io, ServerConfig as SrvConfig}; +use actix_codec::Framed; +use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig}; use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use futures::future::{ok, FutureResult}; @@ -104,7 +104,7 @@ where impl NewService for H1Service where - T: AsyncRead + AsyncWrite, + T: IoStream, S: NewService, S::Error: Into, S::Response: Into>, @@ -161,7 +161,7 @@ where impl Future for H1ServiceResponse where - T: AsyncRead + AsyncWrite, + T: IoStream, S: NewService, S::Error: Into, S::Response: Into>, @@ -245,7 +245,7 @@ where impl Service for H1ServiceHandler where - T: AsyncRead + AsyncWrite, + T: IoStream, S: Service, S::Error: Into, S::Response: Into>, @@ -309,7 +309,7 @@ pub struct OneRequest { impl OneRequest where - T: AsyncRead + AsyncWrite, + T: IoStream, { /// Create new `H1SimpleService` instance. pub fn new() -> Self { @@ -322,7 +322,7 @@ where impl NewService for OneRequest where - T: AsyncRead + AsyncWrite, + T: IoStream, { type Request = Io; type Response = (Request, Framed); @@ -348,7 +348,7 @@ pub struct OneRequestService { impl Service for OneRequestService where - T: AsyncRead + AsyncWrite, + T: IoStream, { type Request = Io; type Response = (Request, Framed); @@ -372,14 +372,14 @@ where #[doc(hidden)] pub struct OneRequestServiceResponse where - T: AsyncRead + AsyncWrite, + T: IoStream, { framed: Option>, } impl Future for OneRequestServiceResponse where - T: AsyncRead + AsyncWrite, + T: IoStream, { type Item = (Request, Framed); type Error = ParseError; diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index de0b761f5..e66ff63c3 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -1,9 +1,10 @@ use std::collections::VecDeque; use std::marker::PhantomData; use std::time::Instant; -use std::{fmt, mem}; +use std::{fmt, mem, net}; use actix_codec::{AsyncRead, AsyncWrite}; +use actix_server_config::IoStream; use actix_service::Service; use actix_utils::cloneable::CloneableService; use bitflags::bitflags; @@ -29,14 +30,11 @@ use crate::response::Response; const CHUNK_SIZE: usize = 16_384; /// Dispatcher for HTTP/2 protocol -pub struct Dispatcher< - T: AsyncRead + AsyncWrite, - S: Service, - B: MessageBody, -> { +pub struct Dispatcher, B: MessageBody> { service: CloneableService, connection: Connection, config: ServiceConfig, + peer_addr: Option, ka_expire: Instant, ka_timer: Option, _t: PhantomData, @@ -44,7 +42,7 @@ pub struct Dispatcher< impl Dispatcher where - T: AsyncRead + AsyncWrite, + T: IoStream, S: Service, S::Error: Into, S::Future: 'static, @@ -56,6 +54,7 @@ where connection: Connection, config: ServiceConfig, timeout: Option, + peer_addr: Option, ) -> Self { // let keepalive = config.keep_alive_enabled(); // let flags = if keepalive { @@ -76,9 +75,10 @@ where Dispatcher { service, config, + peer_addr, + connection, ka_expire, ka_timer, - connection, _t: PhantomData, } } @@ -86,7 +86,7 @@ where impl Future for Dispatcher where - T: AsyncRead + AsyncWrite, + T: IoStream, S: Service, S::Error: Into, S::Future: 'static, @@ -117,6 +117,7 @@ where head.method = parts.method; head.version = parts.version; head.headers = parts.headers.into(); + head.peer_addr = self.peer_addr; tokio_current_thread::spawn(ServiceResponse:: { state: ServiceResponseState::ServiceCall( self.service.call(req), diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 8ab244b50..42b8d8d82 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use std::{io, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_server_config::{Io, ServerConfig as SrvConfig}; +use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig}; use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use bytes::Bytes; @@ -63,7 +63,7 @@ where impl NewService for H2Service where - T: AsyncRead + AsyncWrite, + T: IoStream, S: NewService, S::Error: Into, S::Response: Into>, @@ -95,7 +95,7 @@ pub struct H2ServiceResponse, impl Future for H2ServiceResponse where - T: AsyncRead + AsyncWrite, + T: IoStream, S: NewService, S::Error: Into, S::Response: Into>, @@ -140,7 +140,7 @@ where impl Service for H2ServiceHandler where - T: AsyncRead + AsyncWrite, + T: IoStream, S: Service, S::Error: Into, S::Future: 'static, @@ -161,17 +161,20 @@ where } fn call(&mut self, req: Self::Request) -> Self::Future { + let io = req.into_parts().0; + let peer_addr = io.peer_addr(); H2ServiceHandlerResponse { state: State::Handshake( Some(self.srv.clone()), Some(self.cfg.clone()), - server::handshake(req.into_parts().0), + peer_addr, + server::handshake(io), ), } } } -enum State, B: MessageBody> +enum State, B: MessageBody> where S::Future: 'static, { @@ -179,13 +182,14 @@ where Handshake( Option>, Option, + Option, Handshake, ), } pub struct H2ServiceHandlerResponse where - T: AsyncRead + AsyncWrite, + T: IoStream, S: Service, S::Error: Into, S::Future: 'static, @@ -197,7 +201,7 @@ where impl Future for H2ServiceHandlerResponse where - T: AsyncRead + AsyncWrite, + T: IoStream, S: Service, S::Error: Into, S::Future: 'static, @@ -210,24 +214,28 @@ where fn poll(&mut self) -> Poll { match self.state { State::Incoming(ref mut disp) => disp.poll(), - State::Handshake(ref mut srv, ref mut config, ref mut handshake) => { - match handshake.poll() { - Ok(Async::Ready(conn)) => { - self.state = State::Incoming(Dispatcher::new( - srv.take().unwrap(), - conn, - config.take().unwrap(), - None, - )); - self.poll() - } - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(err) => { - trace!("H2 handshake error: {}", err); - Err(err.into()) - } + State::Handshake( + ref mut srv, + ref mut config, + ref peer_addr, + ref mut handshake, + ) => match handshake.poll() { + Ok(Async::Ready(conn)) => { + self.state = State::Incoming(Dispatcher::new( + srv.take().unwrap(), + conn, + config.take().unwrap(), + None, + peer_addr.clone(), + )); + self.poll() } - } + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(err) => { + trace!("H2 handshake error: {}", err); + Err(err.into()) + } + }, } } } diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 61ca5161e..7f2dc603f 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -1,4 +1,5 @@ use std::cell::{Ref, RefCell, RefMut}; +use std::net; use std::rc::Rc; use bitflags::bitflags; @@ -43,6 +44,7 @@ pub struct RequestHead { pub version: Version, pub headers: HeaderMap, pub extensions: RefCell, + pub peer_addr: Option, flags: Flags, } @@ -54,6 +56,7 @@ impl Default for RequestHead { version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), flags: Flags::empty(), + peer_addr: None, extensions: RefCell::new(Extensions::new()), } } diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index 468b4e337..5ba07929a 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -1,5 +1,5 @@ use std::cell::{Ref, RefMut}; -use std::fmt; +use std::{fmt, net}; use http::{header, Method, Uri, Version}; @@ -139,6 +139,7 @@ impl

Request

{ } /// Check if request requires connection upgrade + #[inline] pub fn upgrade(&self) -> bool { if let Some(conn) = self.head().headers.get(header::CONNECTION) { if let Ok(s) = conn.to_str() { @@ -147,6 +148,15 @@ impl

Request

{ } self.head().method == Method::CONNECT } + + /// Peer socket address + /// + /// Peer address is actual socket address, if proxy is used in front of + /// actix http server, then peer address would be address of this proxy. + #[inline] + pub fn peer_addr(&self) -> Option { + self.head().peer_addr + } } impl

fmt::Debug for Request

{ diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 2af1238b1..dd3af1db0 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -1,8 +1,10 @@ use std::marker::PhantomData; -use std::{fmt, io}; +use std::{fmt, io, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_server_config::{Io as ServerIo, Protocol, ServerConfig as SrvConfig}; +use actix_server_config::{ + Io as ServerIo, IoStream, Protocol, ServerConfig as SrvConfig, +}; use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use bytes::{Buf, BufMut, Bytes, BytesMut}; @@ -128,7 +130,7 @@ where impl NewService for HttpService where - T: AsyncRead + AsyncWrite, + T: IoStream, S: NewService, S::Error: Into, S::InitError: fmt::Debug, @@ -182,7 +184,7 @@ pub struct HttpServiceResponse< impl Future for HttpServiceResponse where - T: AsyncRead + AsyncWrite, + T: IoStream, S: NewService, S::Error: Into, S::InitError: fmt::Debug, @@ -268,7 +270,7 @@ where impl Service for HttpServiceHandler where - T: AsyncRead + AsyncWrite, + T: IoStream, S: Service, S::Error: Into, S::Future: 'static, @@ -317,6 +319,7 @@ where let (io, _, proto) = req.into_parts(); match proto { Protocol::Http2 => { + let peer_addr = io.peer_addr(); let io = Io { inner: io, unread: None, @@ -326,6 +329,7 @@ where server::handshake(io), self.cfg.clone(), self.srv.clone(), + peer_addr, ))), } } @@ -357,7 +361,7 @@ where S: Service, S::Future: 'static, S::Error: Into, - T: AsyncRead + AsyncWrite, + T: IoStream, B: MessageBody, X: Service, X::Error: Into, @@ -376,12 +380,19 @@ where Option>, )>, ), - Handshake(Option<(Handshake, Bytes>, ServiceConfig, CloneableService)>), + Handshake( + Option<( + Handshake, Bytes>, + ServiceConfig, + CloneableService, + Option, + )>, + ), } pub struct HttpServiceHandlerResponse where - T: AsyncRead + AsyncWrite, + T: IoStream, S: Service, S::Error: Into, S::Future: 'static, @@ -399,7 +410,7 @@ const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; impl Future for HttpServiceHandlerResponse where - T: AsyncRead + AsyncWrite, + T: IoStream, S: Service, S::Error: Into, S::Future: 'static, @@ -437,12 +448,17 @@ where } let (io, buf, cfg, srv, expect, upgrade) = data.take().unwrap(); if buf[..14] == HTTP2_PREFACE[..] { + let peer_addr = io.peer_addr(); let io = Io { inner: io, unread: Some(buf), }; - self.state = - State::Handshake(Some((server::handshake(io), cfg, srv))); + self.state = State::Handshake(Some(( + server::handshake(io), + cfg, + srv, + peer_addr, + ))); } else { self.state = State::H1(h1::Dispatcher::with_timeout( io, @@ -470,8 +486,8 @@ where } else { panic!() }; - let (_, cfg, srv) = data.take().unwrap(); - self.state = State::H2(Dispatcher::new(srv, conn, cfg, None)); + let (_, cfg, srv, peer_addr) = data.take().unwrap(); + self.state = State::H2(Dispatcher::new(srv, conn, cfg, None, peer_addr)); self.poll() } } @@ -523,3 +539,25 @@ impl AsyncWrite for Io { self.inner.write_buf(buf) } } + +impl IoStream for Io { + #[inline] + fn peer_addr(&self) -> Option { + self.inner.peer_addr() + } + + #[inline] + fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { + self.inner.set_nodelay(nodelay) + } + + #[inline] + fn set_linger(&mut self, dur: Option) -> io::Result<()> { + self.inner.set_linger(dur) + } + + #[inline] + fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { + self.inner.set_keepalive(dur) + } +} diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index ce55912f7..b4344a676 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -4,6 +4,7 @@ use std::io; use std::str::FromStr; use actix_codec::{AsyncRead, AsyncWrite}; +use actix_server_config::IoStream; use bytes::{Buf, Bytes, BytesMut}; use futures::{Async, Poll}; use http::header::{self, HeaderName, HeaderValue}; @@ -253,3 +254,17 @@ impl AsyncWrite for TestBuffer { Ok(Async::NotReady) } } + +impl IoStream for TestBuffer { + fn set_nodelay(&mut self, _nodelay: bool) -> io::Result<()> { + Ok(()) + } + + fn set_linger(&mut self, _dur: Option) -> io::Result<()> { + Ok(()) + } + + fn set_keepalive(&mut self, _dur: Option) -> io::Result<()> { + Ok(()) + } +} diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index e53ff0212..4b56e4b2c 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -35,7 +35,10 @@ fn test_h1() { .keep_alive(KeepAlive::Disabled) .client_timeout(1000) .client_disconnect(1000) - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .h1(|req: Request| { + assert!(req.peer_addr().is_some()); + future::ok::<_, ()>(Response::Ok().finish()) + }) }); let response = srv.block_on(srv.get("/").send()).unwrap(); @@ -50,6 +53,7 @@ fn test_h1_2() { .client_timeout(1000) .client_disconnect(1000) .finish(|req: Request| { + assert!(req.peer_addr().is_some()); assert_eq!(req.version(), http::Version::HTTP_11); future::ok::<_, ()>(Response::Ok().finish()) }) @@ -115,6 +119,7 @@ fn test_h2_1() -> std::io::Result<()> { .and_then( HttpService::build() .finish(|req: Request| { + assert!(req.peer_addr().is_some()); assert_eq!(req.version(), http::Version::HTTP_2); future::ok::<_, Error>(Response::Ok().finish()) }) From 420d3064c5b748d40c64473f5ac0de2ad851ef26 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 16 Apr 2019 10:11:38 -0700 Subject: [PATCH 083/122] Add .peer_addr() #744 --- CHANGES.md | 6 ++++-- src/info.rs | 10 +++++----- src/middleware/logger.rs | 15 ++++++++------- src/request.rs | 13 ++++++++++++- src/service.rs | 20 +++++++++++++++++++- 5 files changed, 48 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8909f3e29..00518764d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,12 +1,14 @@ # Changes -## [1.0.0-alpha.7] - 2019-04-xx +## [1.0.0-beta.1] - 2019-04-xx ### Added -* Added helper functions for reading test response body, +* Add helper functions for reading test response body, `test::read_response()` and test::read_response_json()` +* Add `.peer_addr()` #744 + ### Changed * Rename `RouterConfig` to `ServiceConfig` diff --git a/src/info.rs b/src/info.rs index ece17bf04..e9b375875 100644 --- a/src/info.rs +++ b/src/info.rs @@ -30,7 +30,7 @@ impl ConnectionInfo { let mut host = None; let mut scheme = None; let mut remote = None; - let peer = None; + let mut peer = None; // load forwarded header for hdr in req.headers.get_all(&header::FORWARDED) { @@ -116,10 +116,10 @@ impl ConnectionInfo { remote = h.split(',').next().map(|v| v.trim()); } } - // if remote.is_none() { - // get peeraddr from socketaddr - // peer = req.peer_addr().map(|addr| format!("{}", addr)); - // } + if remote.is_none() { + // get peeraddr from socketaddr + peer = req.peer_addr.map(|addr| format!("{}", addr)); + } } ConnectionInfo { diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index d5fca526b..43893bc0f 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -363,13 +363,6 @@ impl FormatText { let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000.0; fmt.write_fmt(format_args!("{:.6}", rt)) } - // FormatText::RemoteAddr => { - // if let Some(remote) = req.connection_info().remote() { - // return remote.fmt(fmt); - // } else { - // "-".fmt(fmt) - // } - // } FormatText::EnvironHeader(ref name) => { if let Ok(val) = env::var(name) { fmt.write_fmt(format_args!("{}", val)) @@ -441,6 +434,14 @@ impl FormatText { }; *self = FormatText::Str(s.to_string()); } + FormatText::RemoteAddr => { + let s = if let Some(remote) = req.connection_info().remote() { + FormatText::Str(remote.to_string()) + } else { + FormatText::Str("-".to_string()) + }; + *self = s; + } _ => (), } } diff --git a/src/request.rs b/src/request.rs index 5823c08ca..ad5b2488f 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,6 +1,6 @@ use std::cell::{Ref, RefCell, RefMut}; -use std::fmt; use std::rc::Rc; +use std::{fmt, net}; use actix_http::http::{HeaderMap, Method, Uri, Version}; use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead}; @@ -170,6 +170,17 @@ impl HttpRequest { self.url_for(name, &NO_PARAMS) } + /// Peer socket address + /// + /// Peer address is actual socket address, if proxy is used in front of + /// actix http server, then peer address would be address of this proxy. + /// + /// To get client connection information `.connection_info()` should be used. + #[inline] + pub fn peer_addr(&self) -> Option { + self.head().peer_addr + } + /// Get *ConnectionInfo* for the current request. #[inline] pub fn connection_info(&self) -> Ref { diff --git a/src/service.rs b/src/service.rs index 8bc2ff9a5..5303436ce 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,5 +1,5 @@ use std::cell::{Ref, RefMut}; -use std::fmt; +use std::{fmt, net}; use actix_http::body::{Body, MessageBody, ResponseBody}; use actix_http::http::{HeaderMap, Method, StatusCode, Uri, Version}; @@ -12,6 +12,7 @@ use futures::future::{ok, FutureResult, IntoFuture}; use crate::config::{AppConfig, AppService}; use crate::data::Data; +use crate::info::ConnectionInfo; use crate::request::HttpRequest; pub trait HttpServiceFactory { @@ -134,6 +135,23 @@ impl ServiceRequest { } } + /// Peer socket address + /// + /// Peer address is actual socket address, if proxy is used in front of + /// actix http server, then peer address would be address of this proxy. + /// + /// To get client connection information `ConnectionInfo` should be used. + #[inline] + pub fn peer_addr(&self) -> Option { + self.head().peer_addr + } + + /// Get *ConnectionInfo* for the current request. + #[inline] + pub fn connection_info(&self) -> Ref { + ConnectionInfo::get(self.head(), &*self.app_config()) + } + /// Get a reference to the Path parameters. /// /// Params is a container for url parameters. From 3744957804c7fae209aa4eb6f5d1fb180f7eda36 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 16 Apr 2019 10:27:58 -0700 Subject: [PATCH 084/122] actix_http::encoding always available --- actix-http/CHANGES.md | 2 ++ actix-http/src/lib.rs | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index dc56b0401..3f2ccd4eb 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -8,6 +8,8 @@ ### Changed +* `actix_http::encoding` always available + * use trust-dns-resolver 0.11.0 diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 5af802601..ac085eaea 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -12,7 +12,6 @@ pub mod body; mod builder; pub mod client; mod config; -#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust", feature = "brotli"))] pub mod encoding; mod extensions; mod header; From 2986077a2839314f61133ed339b3e7fb04f77b02 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 16 Apr 2019 10:32:48 -0700 Subject: [PATCH 085/122] no need for feature --- actix-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 746699a07..205f39cd7 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -26,7 +26,7 @@ path = "src/lib.rs" default = [] # openssl -ssl = ["openssl", "actix-connect/ssl", "actix-server-config/ssl"] +ssl = ["openssl", "actix-connect/ssl"] # brotli encoding, requires c compiler brotli = ["brotli2"] From ddfd7523f7f4b81c29d4aa91256d41e1742bdc39 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 16 Apr 2019 10:49:38 -0700 Subject: [PATCH 086/122] prepare awc release --- Cargo.toml | 4 ++-- awc/CHANGES.md | 5 +++++ awc/Cargo.toml | 8 ++++---- awc/README.md | 32 ++++++++++++++++++++++++++++++++ test-server/Cargo.toml | 4 ++-- 5 files changed, 45 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 68979d096..e77292c05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,7 @@ actix-utils = "0.3.4" actix-router = "0.1.2" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-alpha.6" -actix-http = { version = "0.1.0-alpha.5", features=["fail"] } +actix-http = { version = "0.1.0", features=["fail"] } actix-server = "0.4.3" actix-server-config = "0.1.1" actix-threadpool = "0.1.0" @@ -98,7 +98,7 @@ openssl = { version="0.10", optional = true } rustls = { version = "^0.15", optional = true } [dev-dependencies] -actix-http = { version = "0.1.0-alpha.5", features=["ssl", "brotli", "flate2-zlib"] } +actix-http = { version = "0.1.0", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } actix-files = { version = "0.1.0-alpha.6" } rand = "0.6" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index ddeefd94c..fa85ef3bb 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## [0.1.0] - 2019-04-16 + +* No changes + + ## [0.1.0-alpha.6] - 2019-04-14 ### Changed diff --git a/awc/Cargo.toml b/awc/Cargo.toml index cbca0f477..681131218 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "0.1.0-alpha.6" +version = "0.1.0" authors = ["Nikolay Kim "] description = "Actix http client." readme = "README.md" @@ -38,7 +38,7 @@ flate2-rust = ["actix-http/flate2-rust"] [dependencies] actix-codec = "0.1.2" actix-service = "0.3.6" -actix-http = "0.1.0-alpha.5" +actix-http = "0.1.0" base64 = "0.10.1" bytes = "0.4" derive_more = "0.14" @@ -56,10 +56,10 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.2" actix-web = { version = "1.0.0-alpha.6", features=["ssl"] } -actix-http = { version = "0.1.0-alpha.5", features=["ssl"] } +actix-http = { version = "0.1.0", 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"] } +actix-server = { version = "0.4.3", features=["ssl"] } brotli2 = { version="0.3.2" } flate2 = { version="1.0.2" } env_logger = "0.6" diff --git a/awc/README.md b/awc/README.md index bb64559c1..d9eb45f8c 100644 --- a/awc/README.md +++ b/awc/README.md @@ -1 +1,33 @@ # Actix http client [![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/awc)](https://crates.io/crates/awc) [![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) + +An HTTP Client + +## Documentation & community resources + +* [User Guide](https://actix.rs/docs/) +* [API Documentation](https://docs.rs/awc/) +* [Chat on gitter](https://gitter.im/actix/actix) +* Cargo package: [actix-http](https://crates.io/crates/awc) +* Minimum supported Rust version: 1.33 or later + +## Example + +```rust +# use futures::future::{Future, lazy}; +use actix_rt::System; +use awc::Client; + +fn main() { + System::new("test").block_on(lazy(|| { + let mut client = Client::default(); + + client.get("http://www.rust-lang.org") // <- Create request builder + .header("User-Agent", "Actix-web") + .send() // <- Send http request + .and_then(|response| { // <- server http response + println!("Response: {:?}", response); + Ok(()) + }) + })); +} +``` diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 873eaea87..5b84533cf 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -33,7 +33,7 @@ ssl = ["openssl", "actix-server/ssl", "awc/ssl"] actix-codec = "0.1.2" actix-rt = "0.2.2" actix-service = "0.3.6" -actix-server = "0.4.1" +actix-server = "0.4.3" actix-utils = "0.3.5" awc = "0.1.0-alpha.5" @@ -56,4 +56,4 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-web = "1.0.0-alpha.5" -actix-http = "0.1.0-alpha.5" +actix-http = "0.1.0" From e7ec77aa81dad46092878ec6ef1e201efbfcd155 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 16 Apr 2019 10:50:37 -0700 Subject: [PATCH 087/122] update readme --- awc/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awc/README.md b/awc/README.md index d9eb45f8c..3b0034d76 100644 --- a/awc/README.md +++ b/awc/README.md @@ -7,15 +7,15 @@ An HTTP Client * [User Guide](https://actix.rs/docs/) * [API Documentation](https://docs.rs/awc/) * [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [actix-http](https://crates.io/crates/awc) +* Cargo package: [awc](https://crates.io/crates/awc) * Minimum supported Rust version: 1.33 or later ## Example ```rust -# use futures::future::{Future, lazy}; use actix_rt::System; use awc::Client; +use futures::future::{Future, lazy}; fn main() { System::new("test").block_on(lazy(|| { From 4c0ebd55d3a759d1b1360857f92579c3341c435f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 16 Apr 2019 11:02:26 -0700 Subject: [PATCH 088/122] prepare actix-http-test release --- Cargo.toml | 2 +- test-server/CHANGES.md | 5 +++++ test-server/Cargo.toml | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e77292c05..dc7530932 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,7 +75,7 @@ actix-http = { version = "0.1.0", features=["fail"] } actix-server = "0.4.3" actix-server-config = "0.1.1" actix-threadpool = "0.1.0" -awc = { version = "0.1.0-alpha.6", optional = true } +awc = { version = "0.1.0", optional = true } bytes = "0.4" derive_more = "0.14" diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md index 14a8ce628..cec01fde6 100644 --- a/test-server/CHANGES.md +++ b/test-server/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## [0.1.0] - 2019-04-16 + +* No changes + + ## [0.1.0-alpha.3] - 2019-04-02 * Request functions accept path #743 diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 5b84533cf..657dd2615 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.3" +version = "0.1.0" authors = ["Nikolay Kim "] description = "Actix http test server" readme = "README.md" @@ -35,7 +35,7 @@ actix-rt = "0.2.2" actix-service = "0.3.6" actix-server = "0.4.3" actix-utils = "0.3.5" -awc = "0.1.0-alpha.5" +awc = "0.1.0" base64 = "0.10" bytes = "0.4" From c943e95812ecb71ac0c3abc4c2513bc185606d3a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 16 Apr 2019 11:17:29 -0700 Subject: [PATCH 089/122] update dependencies --- Cargo.toml | 3 +-- actix-framed/Cargo.toml | 6 +++--- actix-http/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 4 ++-- actix-web-actors/Cargo.toml | 4 ++-- actix-web-codegen/Cargo.toml | 4 ++-- awc/Cargo.toml | 2 +- 7 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dc7530932..535bcb82d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,7 +99,7 @@ rustls = { version = "^0.15", optional = true } [dev-dependencies] actix-http = { version = "0.1.0", features=["ssl", "brotli", "flate2-zlib"] } -actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } +actix-http-test = { version = "0.1.0", features=["ssl"] } actix-files = { version = "0.1.0-alpha.6" } rand = "0.6" env_logger = "0.6" @@ -116,7 +116,6 @@ codegen-units = 1 [patch.crates-io] 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" } diff --git a/actix-framed/Cargo.toml b/actix-framed/Cargo.toml index ba433e17e..38f12f440 100644 --- a/actix-framed/Cargo.toml +++ b/actix-framed/Cargo.toml @@ -25,13 +25,13 @@ actix-service = "0.3.6" actix-utils = "0.3.4" actix-router = "0.1.2" actix-rt = "0.2.2" -actix-http = "0.1.0-alpha.5" +actix-http = "0.1.0" bytes = "0.4" futures = "0.1.25" log = "0.4" [dev-dependencies] -actix-server = { version = "0.4.1", features=["ssl"] } +actix-server = { version = "0.4.3", features=["ssl"] } actix-connect = { version = "0.1.4", features=["ssl"] } -actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } +actix-http-test = { version = "0.1.0", features=["ssl"] } diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 205f39cd7..575a08a0e 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -98,7 +98,7 @@ openssl = { version="0.10", optional = true } actix-rt = "0.2.2" actix-server = { version = "0.4.3", features=["ssl"] } actix-connect = { version = "0.1.4", features=["ssl"] } -actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } +actix-http-test = { version = "0.1.0", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" openssl = { version="0.10" } diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 006f7066a..58ab45230 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -18,7 +18,7 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = "1.0.0-alpha.3" +actix-web = "1.0.0-alpha.6" actix-service = "0.3.4" bytes = "0.4" derive_more = "0.14" @@ -31,4 +31,4 @@ twoway = "0.2" [dev-dependencies] actix-rt = "0.2.2" -actix-http = "0.1.0-alpha.3" \ No newline at end of file +actix-http = "0.1.0" \ No newline at end of file diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index f56b47fbf..3e67c2313 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -20,11 +20,11 @@ path = "src/lib.rs" [dependencies] actix = "0.8.0" actix-web = "1.0.0-alpha.5" -actix-http = "0.1.0-alpha.5" +actix-http = "0.1.0" 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.3", features=["ssl"] } +actix-http-test = { version = "0.1.0", features=["ssl"] } diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 26dbd9b71..13928f67a 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -17,6 +17,6 @@ syn = { version = "0.15", features = ["full", "parsing"] } [dev-dependencies] actix-web = { version = "1.0.0-alpha.6" } -actix-http = { version = "0.1.0-alpha.5", features=["ssl"] } -actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } +actix-http = { version = "0.1.0", features=["ssl"] } +actix-http-test = { version = "0.1.0", features=["ssl"] } futures = { version = "0.1" } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 681131218..bbc3d9287 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -57,7 +57,7 @@ openssl = { version="0.10", optional = true } actix-rt = "0.2.2" actix-web = { version = "1.0.0-alpha.6", features=["ssl"] } actix-http = { version = "0.1.0", features=["ssl"] } -actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } +actix-http-test = { version = "0.1.0", features=["ssl"] } actix-utils = "0.3.4" actix-server = { version = "0.4.3", features=["ssl"] } brotli2 = { version="0.3.2" } From 5740f1e63ac9568ce623c2b46b0c1d429ae4ea90 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 16 Apr 2019 11:18:47 -0700 Subject: [PATCH 090/122] prepare actix-framed release --- actix-framed/Cargo.toml | 2 +- actix-framed/changes.md | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/actix-framed/Cargo.toml b/actix-framed/Cargo.toml index 38f12f440..f0622fd90 100644 --- a/actix-framed/Cargo.toml +++ b/actix-framed/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-framed" -version = "0.1.0-alpha.1" +version = "0.1.0" authors = ["Nikolay Kim "] description = "Actix framed app server" readme = "README.md" diff --git a/actix-framed/changes.md b/actix-framed/changes.md index 125e22bcd..9cef3c057 100644 --- a/actix-framed/changes.md +++ b/actix-framed/changes.md @@ -1,5 +1,10 @@ # Changes +## [0.1.0] - 2019-04-16 + +* Update tests + + ## [0.1.0-alpha.1] - 2019-04-12 * Initial release From cc8420377e93c0a82d3b1b3f5865b75a67789cc4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 16 Apr 2019 15:43:55 -0700 Subject: [PATCH 091/122] pass request ownership to closure instead of ref --- awc/CHANGES.md | 7 +++++++ awc/src/request.rs | 14 ++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index fa85ef3bb..a4f43d292 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.1.1] - 2019-04-xx + +### Changed + +* `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref + + ## [0.1.0] - 2019-04-16 * No changes diff --git a/awc/src/request.rs b/awc/src/request.rs index c97e08f81..d6716cdcf 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -333,24 +333,26 @@ impl ClientRequest { /// value is `true`. pub fn if_true(mut self, value: bool, f: F) -> Self where - F: FnOnce(&mut ClientRequest), + F: FnOnce(ClientRequest) -> ClientRequest, { if value { - f(&mut self); + f(self) + } else { + self } - self } /// This method calls provided closure with builder reference if /// value is `Some`. pub fn if_some(mut self, value: Option, f: F) -> Self where - F: FnOnce(T, &mut ClientRequest), + F: FnOnce(T, ClientRequest) -> ClientRequest, { if let Some(val) = value { - f(val, &mut self); + f(val, self) + } else { + self } - self } /// Complete request construction and send body. From b64851c5ec286a547c36bd00a5a720fb60500ffb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 17 Apr 2019 10:28:27 -0700 Subject: [PATCH 092/122] enable runtime for test:: methods --- awc/src/request.rs | 4 +-- src/responder.rs | 4 +-- src/test.rs | 65 +++++++++++++++++++--------------------------- 3 files changed, 31 insertions(+), 42 deletions(-) diff --git a/awc/src/request.rs b/awc/src/request.rs index d6716cdcf..c868d052b 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -331,7 +331,7 @@ impl ClientRequest { /// This method calls provided closure with builder reference if /// value is `true`. - pub fn if_true(mut self, value: bool, f: F) -> Self + pub fn if_true(self, value: bool, f: F) -> Self where F: FnOnce(ClientRequest) -> ClientRequest, { @@ -344,7 +344,7 @@ impl ClientRequest { /// This method calls provided closure with builder reference if /// value is `Some`. - pub fn if_some(mut self, value: Option, f: F) -> Self + pub fn if_some(self, value: Option, f: F) -> Self where F: FnOnce(T, ClientRequest) -> ClientRequest, { diff --git a/src/responder.rs b/src/responder.rs index 3e0676289..103009e75 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -307,11 +307,11 @@ pub(crate) mod tests { ); let req = TestRequest::with_uri("/none").to_request(); - let resp = TestRequest::block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/some").to_request(); - let resp = TestRequest::block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); match resp.response().body() { ResponseBody::Body(Body::Bytes(ref b)) => { diff --git a/src/test.rs b/src/test.rs index 89562c61a..ad40a0321 100644 --- a/src/test.rs +++ b/src/test.rs @@ -58,7 +58,7 @@ where /// This function panics on nested call. pub fn run_on(f: F) -> R where - F: Fn() -> R, + F: FnOnce() -> R, { RT.with(move |rt| rt.borrow_mut().block_on(lazy(|| Ok::<_, ()>(f())))) .unwrap() @@ -117,7 +117,9 @@ where S::InitError: std::fmt::Debug, { let cfg = ServerConfig::new("127.0.0.1:8080".parse().unwrap()); - block_on(app.into_new_service().new_service(&cfg)).unwrap() + let srv = app.into_new_service(); + let fut = run_on(move || srv.new_service(&cfg)); + block_on(fut).unwrap() } /// Calls service and waits for response future completion. @@ -146,7 +148,7 @@ where S: Service, Error = E>, E: std::fmt::Debug, { - block_on(app.call(req)).unwrap() + block_on(run_on(move || app.call(req))).unwrap() } /// Helper function that returns a response body of a TestRequest @@ -178,13 +180,15 @@ where S: Service, Error = Error>, B: MessageBody, { - block_on(app.call(req).and_then(|mut resp: ServiceResponse| { - resp.take_body() - .fold(BytesMut::new(), move |mut body, chunk| { - body.extend_from_slice(&chunk); - Ok::<_, Error>(body) - }) - .map(|body: BytesMut| body.freeze()) + block_on(run_on(move || { + app.call(req).and_then(|mut resp: ServiceResponse| { + resp.take_body() + .fold(BytesMut::new(), move |mut body, chunk| { + body.extend_from_slice(&chunk); + Ok::<_, Error>(body) + }) + .map(|body: BytesMut| body.freeze()) + }) })) .unwrap_or_else(|_| panic!("read_response failed at block_on unwrap")) } @@ -229,17 +233,19 @@ where B: MessageBody, T: DeserializeOwned, { - block_on(app.call(req).and_then(|mut resp: ServiceResponse| { - resp.take_body() - .fold(BytesMut::new(), move |mut body, chunk| { - body.extend_from_slice(&chunk); - Ok::<_, Error>(body) - }) - .and_then(|body: BytesMut| { - ok(serde_json::from_slice(&body).unwrap_or_else(|_| { - panic!("read_response_json failed during deserialization") - })) - }) + block_on(run_on(move || { + app.call(req).and_then(|mut resp: ServiceResponse| { + resp.take_body() + .fold(BytesMut::new(), move |mut body, chunk| { + body.extend_from_slice(&chunk); + Ok::<_, Error>(body) + }) + .and_then(|body: BytesMut| { + ok(serde_json::from_slice(&body).unwrap_or_else(|_| { + panic!("read_response_json failed during deserialization") + })) + }) + }) })) .unwrap_or_else(|_| panic!("read_response_json failed at block_on unwrap")) } @@ -460,23 +466,6 @@ impl TestRequest { req.set_route_data(Some(Rc::new(self.route_data))); (req, payload) } - - /// Runs the provided future, blocking the current thread until the future - /// completes. - /// - /// This function can be used to synchronously block the current thread - /// until the provided `future` has resolved either successfully or with an - /// error. The result of the future is then returned from this function - /// call. - /// - /// Note that this function is intended to be used only for testing purpose. - /// This function panics on nested call. - pub fn block_on(f: F) -> Result - where - F: Future, - { - block_on(f) - } } #[cfg(test)] From 85b598a614c6c29a010cf853f9fa44752171258c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 17 Apr 2019 11:02:03 -0700 Subject: [PATCH 093/122] add cookie session test --- actix-session/src/cookie.rs | 65 +++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index 634288b45..4f7614dc6 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -17,6 +17,7 @@ use std::collections::HashMap; use std::rc::Rc; +use std::time::Duration; use actix_service::{Service, Transform}; use actix_web::cookie::{Cookie, CookieJar, Key, SameSite}; @@ -27,7 +28,6 @@ use derive_more::{Display, From}; use futures::future::{ok, Future, FutureResult}; use futures::Poll; use serde_json::error::Error as JsonError; -use time::Duration; use crate::Session; @@ -98,7 +98,7 @@ impl CookieSessionInner { } if let Some(max_age) = self.max_age { - cookie.set_max_age(max_age); + cookie.set_max_age(time::Duration::from_std(max_age).unwrap()); } if let Some(same_site) = self.same_site { @@ -317,6 +317,7 @@ where mod tests { use super::*; use actix_web::{test, web, App}; + use bytes::Bytes; #[test] fn cookie_session() { @@ -338,6 +339,26 @@ mod tests { .is_some()); } + #[test] + fn private_cookie() { + let mut app = test::init_service( + App::new() + .wrap(CookieSession::private(&[0; 32]).secure(false)) + .service(web::resource("/").to(|ses: Session| { + let _ = ses.set("counter", 100); + "test" + })), + ); + + 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()); + } + #[test] fn cookie_session_extractor() { let mut app = test::init_service( @@ -357,4 +378,44 @@ mod tests { .find(|c| c.name() == "actix-session") .is_some()); } + + #[test] + fn basics() { + let mut app = test::init_service( + App::new() + .wrap( + CookieSession::signed(&[0; 32]) + .path("/test/") + .name("actix-test") + .domain("localhost") + .http_only(true) + .same_site(SameSite::Lax) + .max_age(Duration::from_secs(100)), + ) + .service(web::resource("/").to(|ses: Session| { + let _ = ses.set("counter", 100); + "test" + })) + .service(web::resource("/test/").to(|ses: Session| { + let val: usize = ses.get("counter").unwrap().unwrap(); + format!("counter: {}", val) + })), + ); + + let request = test::TestRequest::get().to_request(); + let response = test::block_on(app.call(request)).unwrap(); + let cookie = response + .response() + .cookies() + .find(|c| c.name() == "actix-test") + .unwrap() + .clone(); + assert_eq!(cookie.path().unwrap(), "/test/"); + + let request = test::TestRequest::with_uri("/test/") + .cookie(cookie) + .to_request(); + let body = test::read_response(&mut app, request); + assert_eq!(body, Bytes::from_static(b"counter: 100")); + } } From 163ca89cf4ccc010163e7a447d3bf54c2a49fc9e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 17 Apr 2019 17:48:25 -0700 Subject: [PATCH 094/122] more tests --- actix-http/src/request.rs | 25 +++++++++++++++++++++++++ tests/test_server.rs | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index 5ba07929a..e9252a829 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -178,3 +178,28 @@ impl

fmt::Debug for Request

{ Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use http::HttpTryFrom; + + #[test] + fn test_basics() { + let msg = Message::new(); + let mut req = Request::from(msg); + req.headers_mut().insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/plain"), + ); + assert!(req.headers().contains_key(header::CONTENT_TYPE)); + + *req.uri_mut() = Uri::try_from("/index.html?q=1").unwrap(); + assert_eq!(req.uri().path(), "/index.html"); + assert_eq!(req.uri().query(), Some("q=1")); + + let s = format!("{:?}", req); + println!("T: {:?}", s); + assert!(s.contains("Request HTTP/1.1 GET:/index.html")); + } +} diff --git a/tests/test_server.rs b/tests/test_server.rs index 718aa7d4f..33c18b001 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -17,7 +17,7 @@ use futures::stream::once; use rand::{distributions::Alphanumeric, Rng}; use actix_web::middleware::{BodyEncoding, Compress}; -use actix_web::{http, test, web, App, HttpResponse, HttpServer}; +use actix_web::{dev, http, test, web, App, HttpResponse, HttpServer}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -89,6 +89,39 @@ fn test_body_gzip() { assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } +#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] +#[test] +fn test_body_gzip2() { + let mut srv = TestServer::new(|| { + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service(web::resource("/").route(web::to(|| { + Response::Ok().body(STR).into_body::() + }))), + ) + }); + + let mut response = srv + .block_on( + srv.get("/") + .no_decompress() + .header(ACCEPT_ENCODING, "gzip") + .send(), + ) + .unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + + // decode + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); +} + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[test] fn test_body_encoding_override() { From e659e09e29228bad11041089d94e4bad35a57f1d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 18 Apr 2019 11:01:04 -0700 Subject: [PATCH 095/122] update tests --- src/extract.rs | 57 +-------------------------------------------- src/types/form.rs | 4 ---- src/types/path.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++ src/types/query.rs | 32 +++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 60 deletions(-) diff --git a/src/extract.rs b/src/extract.rs index 9023ea49a..6d414fbcc 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -265,13 +265,12 @@ tuple_from_req!(TupleFromRequest10, (0, A), (1, B), (2, C), (3, D), (4, E), (5, #[cfg(test)] mod tests { use actix_http::http::header; - use actix_router::ResourceDef; use bytes::Bytes; use serde_derive::Deserialize; use super::*; use crate::test::{block_on, TestRequest}; - use crate::types::{Form, FormConfig, Path, Query}; + use crate::types::{Form, FormConfig}; #[derive(Deserialize, Debug, PartialEq)] struct Info { @@ -350,58 +349,4 @@ mod tests { block_on(Result::, Error>::from_request(&req, &mut pl)).unwrap(); assert!(r.is_err()); } - - #[derive(Deserialize)] - struct MyStruct { - key: String, - value: String, - } - - #[derive(Deserialize)] - struct Id { - id: String, - } - - #[derive(Deserialize)] - struct Test2 { - key: String, - value: u32, - } - - #[test] - fn test_request_extract() { - let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); - - let resource = ResourceDef::new("/{key}/{value}/"); - resource.match_path(req.match_info_mut()); - - let (req, mut pl) = req.into_parts(); - let s = Path::::from_request(&req, &mut pl).unwrap(); - assert_eq!(s.key, "name"); - assert_eq!(s.value, "user1"); - - let s = Path::<(String, String)>::from_request(&req, &mut pl).unwrap(); - assert_eq!(s.0, "name"); - assert_eq!(s.1, "user1"); - - let s = Query::::from_request(&req, &mut pl).unwrap(); - assert_eq!(s.id, "test"); - - let mut req = TestRequest::with_uri("/name/32/").to_srv_request(); - let resource = ResourceDef::new("/{key}/{value}/"); - resource.match_path(req.match_info_mut()); - - let (req, mut pl) = req.into_parts(); - let s = Path::::from_request(&req, &mut pl).unwrap(); - assert_eq!(s.as_ref().key, "name"); - assert_eq!(s.value, 32); - - let s = Path::<(String, u8)>::from_request(&req, &mut pl).unwrap(); - assert_eq!(s.0, "name"); - assert_eq!(s.1, 32); - - let res = Path::>::from_request(&req, &mut pl).unwrap(); - assert_eq!(res[0], "name".to_owned()); - assert_eq!(res[1], "32".to_owned()); - } } diff --git a/src/types/form.rs b/src/types/form.rs index 249f33b3a..e8f78c496 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -331,10 +331,6 @@ mod tests { fn eq(err: UrlencodedError, other: UrlencodedError) -> bool { match err { - UrlencodedError::Chunked => match other { - UrlencodedError::Chunked => true, - _ => false, - }, UrlencodedError::Overflow => match other { UrlencodedError::Overflow => true, _ => false, diff --git a/src/types/path.rs b/src/types/path.rs index 13a35d5ea..5f0a05af9 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -171,10 +171,25 @@ where #[cfg(test)] mod tests { use actix_router::ResourceDef; + use derive_more::Display; + use serde_derive::Deserialize; use super::*; use crate::test::{block_on, TestRequest}; + #[derive(Deserialize, Debug, Display)] + #[display(fmt = "MyStruct({}, {})", key, value)] + struct MyStruct { + key: String, + value: String, + } + + #[derive(Deserialize)] + struct Test2 { + key: String, + value: u32, + } + #[test] fn test_extract_path_single() { let resource = ResourceDef::new("/{value}/"); @@ -184,6 +199,7 @@ mod tests { let (req, mut pl) = req.into_parts(); assert_eq!(*Path::::from_request(&req, &mut pl).unwrap(), 32); + assert!(Path::::from_request(&req, &mut pl).is_err()); } #[test] @@ -213,4 +229,46 @@ mod tests { let () = <()>::from_request(&req, &mut pl).unwrap(); } + #[test] + fn test_request_extract() { + let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); + + let resource = ResourceDef::new("/{key}/{value}/"); + resource.match_path(req.match_info_mut()); + + let (req, mut pl) = req.into_parts(); + let mut s = Path::::from_request(&req, &mut pl).unwrap(); + assert_eq!(s.key, "name"); + assert_eq!(s.value, "user1"); + s.value = "user2".to_string(); + assert_eq!(s.value, "user2"); + assert_eq!( + format!("{}, {:?}", s, s), + "MyStruct(name, user2), MyStruct { key: \"name\", value: \"user2\" }" + ); + let s = s.into_inner(); + assert_eq!(s.value, "user2"); + + let s = Path::<(String, String)>::from_request(&req, &mut pl).unwrap(); + assert_eq!(s.0, "name"); + assert_eq!(s.1, "user1"); + + let mut req = TestRequest::with_uri("/name/32/").to_srv_request(); + let resource = ResourceDef::new("/{key}/{value}/"); + resource.match_path(req.match_info_mut()); + + let (req, mut pl) = req.into_parts(); + let s = Path::::from_request(&req, &mut pl).unwrap(); + assert_eq!(s.as_ref().key, "name"); + assert_eq!(s.value, 32); + + let s = Path::<(String, u8)>::from_request(&req, &mut pl).unwrap(); + assert_eq!(s.0, "name"); + assert_eq!(s.1, 32); + + let res = Path::>::from_request(&req, &mut pl).unwrap(); + assert_eq!(res[0], "name".to_owned()); + assert_eq!(res[1], "32".to_owned()); + } + } diff --git a/src/types/query.rs b/src/types/query.rs index 596254be5..f9f545d61 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -133,3 +133,35 @@ where }) } } + +#[cfg(test)] +mod tests { + use derive_more::Display; + use serde_derive::Deserialize; + + use super::*; + use crate::test::TestRequest; + + #[derive(Deserialize, Debug, Display)] + struct Id { + id: String, + } + + #[test] + fn test_request_extract() { + let req = TestRequest::with_uri("/name/user1/").to_srv_request(); + let (req, mut pl) = req.into_parts(); + assert!(Query::::from_request(&req, &mut pl).is_err()); + + let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); + let (req, mut pl) = req.into_parts(); + + let mut s = Query::::from_request(&req, &mut pl).unwrap(); + assert_eq!(s.id, "test"); + assert_eq!(format!("{}, {:?}", s, s), "test, Id { id: \"test\" }"); + + s.id = "test1".to_string(); + let s = s.into_inner(); + assert_eq!(s.id, "test1"); + } +} From 75e340137d48a0564f440e3d522c7a9806623316 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 18 Apr 2019 12:23:56 -0700 Subject: [PATCH 096/122] use local version of http-test --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 535bcb82d..229cb6dce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,6 +116,7 @@ codegen-units = 1 [patch.crates-io] 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" } From da86b6e062655e9aa228965985edbde612f0304d Mon Sep 17 00:00:00 2001 From: dowwie Date: Thu, 18 Apr 2019 18:06:32 -0400 Subject: [PATCH 097/122] added put and patch to TestRequest, docs, and test --- src/test.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/test.rs b/src/test.rs index ad40a0321..a8eed3881 100644 --- a/src/test.rs +++ b/src/test.rs @@ -335,6 +335,16 @@ impl TestRequest { pub fn post() -> TestRequest { TestRequest::default().method(Method::POST) } + + /// Create TestRequest and set method to `Method::PUT` + pub fn put() -> TestRequest { + TestRequest::default().method(Method::PUT) + } + + /// Create TestRequest and set method to `Method::PATCH` + pub fn patch() -> TestRequest { + TestRequest::default().method(Method::PATCH) + } /// Set HTTP version of this request pub fn version(mut self, ver: Version) -> Self { @@ -493,6 +503,34 @@ mod tests { assert_eq!(*data.get_ref(), 10); } + #[test] + fn test_request_methods() { + let mut app = init_service( + App::new().service( + web::resource("/index.html") + .route(web::put().to(|| HttpResponse::Ok().body("put!"))) + .route(web::patch().to(|| HttpResponse::Ok().body("patch!"))), + ), + ); + + let put_req = TestRequest::put() + .uri("/index.html") + .header(header::CONTENT_TYPE, "application/json") + .to_request(); + + let result = read_response(&mut app, put_req); + assert_eq!(result, Bytes::from_static(b"put!")); + + + let patch_req = TestRequest::patch() + .uri("/index.html") + .header(header::CONTENT_TYPE, "application/json") + .to_request(); + + let result = read_response(&mut app, patch_req); + assert_eq!(result, Bytes::from_static(b"patch!")); + } + #[test] fn test_response() { let mut app = init_service( From aa255298ef12c7642dbed6a31fc64d287eb14245 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 18 Apr 2019 16:03:13 -0700 Subject: [PATCH 098/122] make ServiceRequest::from_parts private, as it is not safe to create from parts --- CHANGES.md | 2 ++ actix-files/src/lib.rs | 32 ++++++++++++++++---------------- src/service.rs | 2 +- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 00518764d..c37cb51cf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,8 @@ * Rename `test::call_success` to `test::call_service` +* Removed `ServiceRequest::from_parts()` as it is unsafe to create from parts. + ### Fixed * Fixed `TestRequest::app_data()` diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 8ff6b932c..4038a5487 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -411,17 +411,16 @@ impl FilesService { fn handle_err( &mut self, e: io::Error, - req: HttpRequest, - payload: Payload, + req: ServiceRequest, ) -> Either< FutureResult, Box>, > { log::debug!("Files: Failed to handle {}: {}", req.path(), e); if let Some(ref mut default) = self.default { - default.call(ServiceRequest::from_parts(req, payload)) + default.call(req) } else { - Either::A(ok(ServiceResponse::from_err(e, req.clone()))) + Either::A(ok(req.error_response(e))) } } } @@ -440,17 +439,17 @@ impl Service for FilesService { } fn call(&mut self, req: ServiceRequest) -> Self::Future { - let (req, pl) = req.into_parts(); + // let (req, pl) = req.into_parts(); let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) { Ok(item) => item, - Err(e) => return Either::A(ok(ServiceResponse::from_err(e, req.clone()))), + Err(e) => return Either::A(ok(req.error_response(e))), }; // full filepath let path = match self.directory.join(&real_path.0).canonicalize() { Ok(path) => path, - Err(e) => return self.handle_err(e, req, pl), + Err(e) => return self.handle_err(e, req), }; if path.is_dir() { @@ -466,24 +465,26 @@ impl Service for FilesService { } named_file.flags = self.file_flags; + let (req, _) = req.into_parts(); Either::A(ok(match named_file.respond_to(&req) { - Ok(item) => ServiceResponse::new(req.clone(), item), - Err(e) => ServiceResponse::from_err(e, req.clone()), + Ok(item) => ServiceResponse::new(req, item), + Err(e) => ServiceResponse::from_err(e, req), })) } - Err(e) => return self.handle_err(e, req, pl), + Err(e) => return self.handle_err(e, req), } } else if self.show_index { let dir = Directory::new(self.directory.clone(), path); + let (req, _) = req.into_parts(); let x = (self.renderer)(&dir, &req); match x { Ok(resp) => Either::A(ok(resp)), - Err(e) => return self.handle_err(e, req, pl), + Err(e) => return Either::A(ok(ServiceResponse::from_err(e, req))), } } else { Either::A(ok(ServiceResponse::from_err( FilesError::IsDirectory, - req.clone(), + req.into_parts().0, ))) } } else { @@ -496,16 +497,15 @@ impl Service for FilesService { } named_file.flags = self.file_flags; + let (req, _) = req.into_parts(); match named_file.respond_to(&req) { Ok(item) => { Either::A(ok(ServiceResponse::new(req.clone(), item))) } - Err(e) => { - Either::A(ok(ServiceResponse::from_err(e, req.clone()))) - } + Err(e) => Either::A(ok(ServiceResponse::from_err(e, req))), } } - Err(e) => self.handle_err(e, req, pl), + Err(e) => self.handle_err(e, req), } } } diff --git a/src/service.rs b/src/service.rs index 5303436ce..396daab4b 100644 --- a/src/service.rs +++ b/src/service.rs @@ -53,7 +53,7 @@ pub struct ServiceRequest { impl ServiceRequest { /// Construct service request from parts - pub fn from_parts(req: HttpRequest, payload: Payload) -> Self { + pub(crate) fn from_parts(req: HttpRequest, payload: Payload) -> Self { ServiceRequest { req, payload } } From bfe0df5ab0b8a25db7ae2dc004ba133c9cee15a8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 18 Apr 2019 21:28:23 -0700 Subject: [PATCH 099/122] update tests --- actix-framed/tests/test_server.rs | 7 +++- awc/src/request.rs | 12 +++++++ awc/src/ws.rs | 53 +++++++++++++++++++------------ awc/tests/test_client.rs | 33 +++++++++++++++++++ 4 files changed, 83 insertions(+), 22 deletions(-) diff --git a/actix-framed/tests/test_server.rs b/actix-framed/tests/test_server.rs index 964403e15..00f6a97d8 100644 --- a/actix-framed/tests/test_server.rs +++ b/actix-framed/tests/test_server.rs @@ -1,5 +1,5 @@ use actix_codec::{AsyncRead, AsyncWrite}; -use actix_http::{body, ws, Error, HttpService, Response}; +use actix_http::{body, http::StatusCode, ws, Error, HttpService, Response}; use actix_http_test::TestServer; use actix_service::{IntoNewService, NewService}; use actix_utils::framed::FramedTransport; @@ -99,6 +99,11 @@ fn test_service() { ) }); + // non ws request + let res = srv.block_on(srv.get("/index.html").send()).unwrap(); + assert_eq!(res.status(), StatusCode::BAD_REQUEST); + + // not found assert!(srv.ws_at("/test").is_err()); // client service diff --git a/awc/src/request.rs b/awc/src/request.rs index c868d052b..a280dfce1 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -566,9 +566,21 @@ mod tests { .version(Version::HTTP_2) .set(header::Date(SystemTime::now().into())) .content_type("plain/text") + .if_true(true, |req| req.header(header::SERVER, "awc")) + .if_true(false, |req| req.header(header::EXPECT, "awc")) + .if_some(Some("server"), |val, req| { + req.header(header::USER_AGENT, val) + }) + .if_some(Option::<&str>::None, |_, req| { + req.header(header::ALLOW, "1") + }) .content_length(100); assert!(req.headers().contains_key(header::CONTENT_TYPE)); assert!(req.headers().contains_key(header::DATE)); + assert!(req.headers().contains_key(header::SERVER)); + assert!(req.headers().contains_key(header::USER_AGENT)); + assert!(!req.headers().contains_key(header::ALLOW)); + assert!(!req.headers().contains_key(header::EXPECT)); assert_eq!(req.head.version, Version::HTTP_2); let _ = req.headers_mut(); let _ = req.send_body(""); diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 1ab6d563d..028330ab4 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -445,30 +445,41 @@ mod tests { .unwrap(), "Bearer someS3cr3tAutht0k3n" ); + let _ = req.connect(); } #[test] fn basics() { - let req = Client::new() - .ws("/") - .origin("test-origin") - .max_frame_size(100) - .server_mode() - .protocols(&["v1", "v2"]) - .set_header_if_none(header::CONTENT_TYPE, "json") - .set_header_if_none(header::CONTENT_TYPE, "text") - .cookie(Cookie::build("cookie1", "value1").finish()); - assert_eq!( - req.origin.as_ref().unwrap().to_str().unwrap(), - "test-origin" - ); - assert_eq!(req.max_size, 100); - assert_eq!(req.server_mode, true); - assert_eq!(req.protocols, Some("v1,v2".to_string())); - assert_eq!( - req.head.headers.get(header::CONTENT_TYPE).unwrap(), - header::HeaderValue::from_static("json") - ); - let _ = req.connect(); + actix_http_test::run_on(|| { + let req = Client::new() + .ws("http://localhost/") + .origin("test-origin") + .max_frame_size(100) + .server_mode() + .protocols(&["v1", "v2"]) + .set_header_if_none(header::CONTENT_TYPE, "json") + .set_header_if_none(header::CONTENT_TYPE, "text") + .cookie(Cookie::build("cookie1", "value1").finish()); + assert_eq!( + req.origin.as_ref().unwrap().to_str().unwrap(), + "test-origin" + ); + assert_eq!(req.max_size, 100); + assert_eq!(req.server_mode, true); + assert_eq!(req.protocols, Some("v1,v2".to_string())); + assert_eq!( + req.head.headers.get(header::CONTENT_TYPE).unwrap(), + header::HeaderValue::from_static("json") + ); + let _ = req.connect(); + }); + + assert!(Client::new().ws("/").connect().poll().is_err()); + assert!(Client::new().ws("http:///test").connect().poll().is_err()); + assert!(Client::new() + .ws("hmm://test.com/") + .connect() + .poll() + .is_err()); } } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 0f0652c48..afccdff86 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::io::{Read, Write}; use std::time::Duration; @@ -63,6 +64,38 @@ fn test_simple() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } +#[test] +fn test_json() { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service( + web::resource("/").route(web::to(|_: web::Json| HttpResponse::Ok())), + )) + }); + + let request = srv + .get("/") + .header("x-test", "111") + .send_json(&"TEST".to_string()); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); +} + +#[test] +fn test_form() { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to( + |_: web::Form>| HttpResponse::Ok(), + )))) + }); + + let mut data = HashMap::new(); + let _ = data.insert("key".to_string(), "TEST".to_string()); + + let request = srv.get("/").header("x-test", "111").send_form(&data); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); +} + #[test] fn test_timeout() { let mut srv = TestServer::new(|| { From 1e7f97a111c7fb0b047253bd4b0882d2fd471fb8 Mon Sep 17 00:00:00 2001 From: Douman Date: Fri, 19 Apr 2019 23:53:49 +0300 Subject: [PATCH 100/122] Add Normalization middleware for in place (#783) --- src/middleware/mod.rs | 1 + src/middleware/normalize.rs | 109 ++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 src/middleware/normalize.rs diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 59d467c03..3df926251 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -6,6 +6,7 @@ pub mod cors; mod defaultheaders; pub mod errhandlers; mod logger; +pub mod normalize; pub use self::defaultheaders::DefaultHeaders; pub use self::logger::Logger; diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs new file mode 100644 index 000000000..4b0afe7a9 --- /dev/null +++ b/src/middleware/normalize.rs @@ -0,0 +1,109 @@ +//! `Middleware` to normalize request's URI + +use regex::Regex; +use actix_service::{Service, Transform}; +use futures::future::{self, FutureResult}; + +use crate::service::{ServiceRequest, ServiceResponse}; + +#[derive(Default, Clone, Copy)] +/// `Middleware` to normalize request's URI in place +/// +/// Performs following: +/// +/// - Merges multiple slashes into one. +pub struct NormalizePath; + +impl Transform for NormalizePath +where + S: Service, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = S::Error; + type InitError = (); + type Transform = NormalizePathNormalization; + type Future = FutureResult; + + fn new_transform(&self, service: S) -> Self::Future { + future::ok(NormalizePathNormalization { + service, + merge_slash: Regex::new("//+").unwrap() + }) + } +} + +pub struct NormalizePathNormalization { + service: S, + merge_slash: Regex, +} + +impl Service for NormalizePathNormalization +where + S: Service, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = S::Error; + type Future = S::Future; + + fn poll_ready(&mut self) -> futures::Poll<(), Self::Error> { + self.service.poll_ready() + } + + fn call(&mut self, mut req: ServiceRequest) -> Self::Future { + let head = req.head_mut(); + + let path = head.uri.path(); + let original_len = path.len(); + let path = self.merge_slash.replace_all(path, "/"); + + if original_len != path.len() { + head.uri = path.parse().unwrap(); + } + + self.service.call(req) + } +} + +#[cfg(test)] +mod tests { + use actix_service::FnService; + + use super::*; + use crate::dev::ServiceRequest; + use crate::http::header::CONTENT_TYPE; + use crate::test::{block_on, TestRequest}; + use crate::HttpResponse; + + #[test] + fn test_in_place_normalization() { + let srv = FnService::new(|req: ServiceRequest| { + assert_eq!("/v1/something/", req.path()); + req.into_response(HttpResponse::Ok().finish()) + }); + + let mut normalize = block_on(NormalizePath.new_transform(srv)).unwrap(); + + let req = TestRequest::with_uri("/v1//something////").to_srv_request(); + let res = block_on(normalize.call(req)).unwrap(); + assert!(res.status().is_success()); + } + + #[test] + fn should_normalize_nothing() { + const URI: &str = "/v1/something/"; + + let srv = FnService::new(|req: ServiceRequest| { + assert_eq!(URI, req.path()); + req.into_response(HttpResponse::Ok().finish()) + }); + + let mut normalize = block_on(NormalizePath.new_transform(srv)).unwrap(); + + let req = TestRequest::with_uri(URI).to_srv_request(); + let res = block_on(normalize.call(req)).unwrap(); + assert!(res.status().is_success()); + } + +} From 791f22bbc82c3fe809238ea32ad64e4385993783 Mon Sep 17 00:00:00 2001 From: Kilerd Chan Date: Sat, 20 Apr 2019 04:54:44 +0800 Subject: [PATCH 101/122] replate `time::Duration` with `chrono::Duration` and add `max_age_time` method (#789) * feat: replate time::Duration with chrono::Duration * feat: rename max_age method which accepts `Duration` to max_age_time and add new max_age method accepting isize of seconds * feat: replace `time:Duration` with `chrono:Duration` in repo `actix-http` --- Cargo.toml | 7 ++-- actix-http/Cargo.toml | 1 + actix-http/src/cookie/builder.rs | 7 ++-- actix-http/src/cookie/jar.rs | 8 ++-- actix-http/src/cookie/mod.rs | 10 +++-- actix-http/src/cookie/parse.rs | 5 ++- src/middleware/identity.rs | 63 ++++++++++++++++++++++++++++++-- 7 files changed, 82 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 229cb6dce..911accbef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,9 @@ license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] edition = "2018" +[package.metadata.docs.rs] +features = ["ssl", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls"] + [badges] travis-ci = { repository = "actix/actix-web", branch = "master" } codecov = { repository = "actix/actix-web", branch = "master", service = "github" } @@ -37,9 +40,6 @@ members = [ "test-server", ] -[package.metadata.docs.rs] -features = ["ssl", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls"] - [features] default = ["brotli", "flate2-zlib", "secure-cookies", "client"] @@ -96,6 +96,7 @@ url = { version="1.7", features=["query_encoding"] } # ssl support openssl = { version="0.10", optional = true } rustls = { version = "^0.15", optional = true } +chrono = "0.4.6" [dev-dependencies] actix-http = { version = "0.1.0", features=["ssl", "brotli", "flate2-zlib"] } diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 575a08a0e..d5a65b7b5 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -93,6 +93,7 @@ flate2 = { version="1.0.7", optional = true, default-features = false } # optional deps failure = { version = "0.1.5", optional = true } openssl = { version="0.10", optional = true } +chrono = "0.4.6" [dev-dependencies] actix-rt = "0.2.2" diff --git a/actix-http/src/cookie/builder.rs b/actix-http/src/cookie/builder.rs index 2635572ab..71c9bd19a 100644 --- a/actix-http/src/cookie/builder.rs +++ b/actix-http/src/cookie/builder.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; -use time::{Duration, Tm}; +use time::Tm; +use chrono::Duration; use super::{Cookie, SameSite}; @@ -16,7 +17,7 @@ use super::{Cookie, SameSite}; /// /// ```rust /// use actix_http::cookie::Cookie; -/// use time::Duration; +/// use chrono::Duration; /// /// # fn main() { /// let cookie: Cookie = Cookie::build("name", "value") @@ -200,7 +201,7 @@ impl CookieBuilder { /// /// ```rust /// use actix_http::cookie::Cookie; - /// use time::Duration; + /// use chrono::Duration; /// /// # fn main() { /// let c = Cookie::build("foo", "bar") diff --git a/actix-http/src/cookie/jar.rs b/actix-http/src/cookie/jar.rs index b60d73fe6..8b67c37d8 100644 --- a/actix-http/src/cookie/jar.rs +++ b/actix-http/src/cookie/jar.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use std::mem::replace; -use time::{self, Duration}; +use chrono::Duration; use super::delta::DeltaCookie; use super::Cookie; @@ -188,7 +188,7 @@ impl CookieJar { /// /// ```rust /// use actix_http::cookie::{CookieJar, Cookie}; - /// use time::Duration; + /// use chrono::Duration; /// /// # fn main() { /// let mut jar = CookieJar::new(); @@ -241,7 +241,7 @@ impl CookieJar { /// /// ```rust /// use actix_http::cookie::{CookieJar, Cookie}; - /// use time::Duration; + /// use chrono::Duration; /// /// # fn main() { /// let mut jar = CookieJar::new(); @@ -538,7 +538,7 @@ mod test { #[cfg(feature = "secure-cookies")] fn delta() { use std::collections::HashMap; - use time::Duration; + use chrono::Duration; let mut c = CookieJar::new(); diff --git a/actix-http/src/cookie/mod.rs b/actix-http/src/cookie/mod.rs index 0f5f45488..eef41a121 100644 --- a/actix-http/src/cookie/mod.rs +++ b/actix-http/src/cookie/mod.rs @@ -66,7 +66,8 @@ use std::fmt; use std::str::FromStr; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; -use time::{Duration, Tm}; +use time::Tm; +use chrono::Duration; pub use self::builder::CookieBuilder; pub use self::draft::*; @@ -624,7 +625,7 @@ impl<'c> Cookie<'c> { /// /// ```rust /// use actix_http::cookie::Cookie; - /// use time::Duration; + /// use chrono::Duration; /// /// # fn main() { /// let mut c = Cookie::new("name", "value"); @@ -703,7 +704,7 @@ impl<'c> Cookie<'c> { /// /// ```rust /// use actix_http::cookie::Cookie; - /// use time::Duration; + /// use chrono::Duration; /// /// # fn main() { /// let mut c = Cookie::new("foo", "bar"); @@ -977,7 +978,8 @@ impl<'a, 'b> PartialEq> for Cookie<'a> { #[cfg(test)] mod tests { use super::{Cookie, SameSite}; - use time::{strptime, Duration}; + use time::strptime; + use chrono::Duration; #[test] fn format() { diff --git a/actix-http/src/cookie/parse.rs b/actix-http/src/cookie/parse.rs index ebbd6ffc9..cc042733e 100644 --- a/actix-http/src/cookie/parse.rs +++ b/actix-http/src/cookie/parse.rs @@ -6,7 +6,7 @@ use std::fmt; use std::str::Utf8Error; use percent_encoding::percent_decode; -use time::{self, Duration}; +use chrono::Duration; use super::{Cookie, CookieStr, SameSite}; @@ -220,7 +220,8 @@ where #[cfg(test)] mod tests { use super::{Cookie, SameSite}; - use time::{strptime, Duration}; + use time::strptime; + use chrono::Duration; macro_rules! assert_eq_parse { ($string:expr, $expected:expr) => { diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index 6027aaa7d..bf739636f 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -53,7 +53,7 @@ use std::rc::Rc; use actix_service::{Service, Transform}; use futures::future::{ok, Either, FutureResult}; use futures::{Future, IntoFuture, Poll}; -use time::Duration; +use chrono::Duration; use crate::cookie::{Cookie, CookieJar, Key, SameSite}; use crate::error::{Error, Result}; @@ -427,11 +427,16 @@ impl CookieIdentityPolicy { self } - /// Sets the `max-age` field in the session cookie being built. - pub fn max_age(mut self, value: Duration) -> CookieIdentityPolicy { + /// Sets the `max-age` field in the session cookie being built with `chrono::Duration`. + pub fn max_age_time(mut self, value: Duration) -> CookieIdentityPolicy { Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); self } + /// Sets the `max-age` field in the session cookie being built with given number of seconds. + pub fn max_age(mut self, seconds: isize) -> CookieIdentityPolicy { + Rc::get_mut(&mut self.0).unwrap().max_age = Some(Duration::seconds(seconds as i64)); + self + } /// Sets the `same_site` field in the session cookie being built. pub fn same_site(mut self, same_site: SameSite) -> Self { @@ -525,4 +530,56 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert!(resp.headers().contains_key(header::SET_COOKIE)) } + + #[test] + fn test_identity_max_age_time() { + let duration = Duration::days(1); + let mut srv = test::init_service( + App::new() + .wrap(IdentityService::new( + CookieIdentityPolicy::new(&[0; 32]) + .domain("www.rust-lang.org") + .name("actix_auth") + .path("/") + .max_age_time(duration) + .secure(true), + )) + .service(web::resource("/login").to(|id: Identity| { + id.remember("test".to_string()); + HttpResponse::Ok() + })) + ); + let resp = + test::call_service(&mut srv, TestRequest::with_uri("/login").to_request()); + assert_eq!(resp.status(), StatusCode::OK); + assert!(resp.headers().contains_key(header::SET_COOKIE)); + let c = resp.response().cookies().next().unwrap().to_owned(); + assert_eq!(duration, c.max_age().unwrap()); + } + + #[test] + fn test_identity_max_age() { + let seconds = 60isize; + let mut srv = test::init_service( + App::new() + .wrap(IdentityService::new( + CookieIdentityPolicy::new(&[0; 32]) + .domain("www.rust-lang.org") + .name("actix_auth") + .path("/") + .max_age(seconds) + .secure(true), + )) + .service(web::resource("/login").to(|id: Identity| { + id.remember("test".to_string()); + HttpResponse::Ok() + })) + ); + let resp = + test::call_service(&mut srv, TestRequest::with_uri("/login").to_request()); + assert_eq!(resp.status(), StatusCode::OK); + assert!(resp.headers().contains_key(header::SET_COOKIE)); + let c = resp.response().cookies().next().unwrap().to_owned(); + assert_eq!(Duration::seconds(seconds as i64), c.max_age().unwrap()); + } } From a3844c1bfd031c763b239de5e3ce6e337dee4b5a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 19 Apr 2019 13:55:36 -0700 Subject: [PATCH 102/122] update version --- Cargo.toml | 2 +- MIGRATION.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 911accbef..8003956fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.0-alpha.6" +version = "1.0.0-beta.1" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" diff --git a/MIGRATION.md b/MIGRATION.md index 5953452dd..9d433ed06 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -128,7 +128,7 @@ ``` -* AsyncResponder is removed. +* AsyncResponder is removed, use `.to_async()` registration method and `impl Future<>` as result type. instead of From 7292d0b6961a9f1ea6f47472586b968b3b28b382 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 19 Apr 2019 17:23:17 -0700 Subject: [PATCH 103/122] drop chrono and use i64 for max age --- CHANGES.md | 2 ++ Cargo.toml | 3 +-- actix-http/CHANGES.md | 9 +++++++++ actix-http/Cargo.toml | 2 +- actix-http/src/cookie/builder.rs | 29 ++++++++++++++++++++++++----- actix-http/src/cookie/jar.rs | 2 +- actix-http/src/cookie/mod.rs | 7 ++----- actix-http/src/cookie/parse.rs | 8 +++----- actix-http/src/response.rs | 2 +- actix-session/CHANGES.md | 3 +++ actix-session/Cargo.toml | 2 +- actix-session/src/cookie.rs | 14 +++++++++----- src/middleware/identity.rs | 18 +++++++++--------- src/middleware/normalize.rs | 5 ++--- src/test.rs | 5 ++--- 15 files changed, 70 insertions(+), 41 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c37cb51cf..573d287c1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,8 @@ * Removed `ServiceRequest::from_parts()` as it is unsafe to create from parts. +* `CookieIdentityPolicy::max_age()` accepts value in seconds + ### Fixed * Fixed `TestRequest::app_data()` diff --git a/Cargo.toml b/Cargo.toml index 8003956fb..cfc3d59fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,13 +90,12 @@ regex = "1.0" serde = { version = "1.0", features=["derive"] } serde_json = "1.0" serde_urlencoded = "0.5.3" -time = "0.1" +time = "0.1.42" url = { version="1.7", features=["query_encoding"] } # ssl support openssl = { version="0.10", optional = true } rustls = { version = "^0.15", optional = true } -chrono = "0.4.6" [dev-dependencies] actix-http = { version = "0.1.0", features=["ssl", "brotli", "flate2-zlib"] } diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 3f2ccd4eb..a82b864b3 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,14 @@ # Changes +## [0.1.1] - 2019-04-19 + +### Changes + +* Cookie::max_age() accepts value in seconds + +* Cookie::max_age_time() accepts value in time::Duration + + ## [0.1.0] - 2019-04-16 ### Added diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index d5a65b7b5..b94e12d87 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -77,7 +77,7 @@ serde_json = "1.0" sha1 = "0.6" slab = "0.4" serde_urlencoded = "0.5.5" -time = "0.1" +time = "0.1.42" tokio-tcp = "0.1.3" tokio-timer = "0.2.8" tokio-current-thread = "0.1" diff --git a/actix-http/src/cookie/builder.rs b/actix-http/src/cookie/builder.rs index 71c9bd19a..efeddbb62 100644 --- a/actix-http/src/cookie/builder.rs +++ b/actix-http/src/cookie/builder.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; -use time::Tm; use chrono::Duration; +use time::Tm; use super::{Cookie, SameSite}; @@ -17,7 +17,6 @@ use super::{Cookie, SameSite}; /// /// ```rust /// use actix_http::cookie::Cookie; -/// use chrono::Duration; /// /// # fn main() { /// let cookie: Cookie = Cookie::build("name", "value") @@ -25,7 +24,7 @@ use super::{Cookie, SameSite}; /// .path("/") /// .secure(true) /// .http_only(true) -/// .max_age(Duration::days(1)) +/// .max_age(84600) /// .finish(); /// # } /// ``` @@ -80,6 +79,26 @@ impl CookieBuilder { self } + /// Sets the `max_age` field in seconds in the cookie being built. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// # fn main() { + /// let c = Cookie::build("foo", "bar") + /// .max_age(1800) + /// .finish(); + /// + /// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60))); + /// # } + /// ``` + #[inline] + pub fn max_age(self, seconds: i64) -> CookieBuilder { + self.max_age_time(Duration::seconds(seconds)) + } + /// Sets the `max_age` field in the cookie being built. /// /// # Example @@ -89,14 +108,14 @@ impl CookieBuilder { /// /// # fn main() { /// let c = Cookie::build("foo", "bar") - /// .max_age(time::Duration::minutes(30)) + /// .max_age_time(time::Duration::minutes(30)) /// .finish(); /// /// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60))); /// # } /// ``` #[inline] - pub fn max_age(mut self, value: Duration) -> CookieBuilder { + pub fn max_age_time(mut self, value: Duration) -> CookieBuilder { self.cookie.set_max_age(value); self } diff --git a/actix-http/src/cookie/jar.rs b/actix-http/src/cookie/jar.rs index 8b67c37d8..cc67536c6 100644 --- a/actix-http/src/cookie/jar.rs +++ b/actix-http/src/cookie/jar.rs @@ -537,8 +537,8 @@ mod test { #[test] #[cfg(feature = "secure-cookies")] fn delta() { - use std::collections::HashMap; use chrono::Duration; + use std::collections::HashMap; let mut c = CookieJar::new(); diff --git a/actix-http/src/cookie/mod.rs b/actix-http/src/cookie/mod.rs index eef41a121..ddcb12bbf 100644 --- a/actix-http/src/cookie/mod.rs +++ b/actix-http/src/cookie/mod.rs @@ -65,9 +65,9 @@ use std::borrow::Cow; use std::fmt; use std::str::FromStr; +use chrono::Duration; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use time::Tm; -use chrono::Duration; pub use self::builder::CookieBuilder; pub use self::draft::*; @@ -979,7 +979,6 @@ impl<'a, 'b> PartialEq> for Cookie<'a> { mod tests { use super::{Cookie, SameSite}; use time::strptime; - use chrono::Duration; #[test] fn format() { @@ -989,9 +988,7 @@ mod tests { let cookie = Cookie::build("foo", "bar").http_only(true).finish(); assert_eq!(&cookie.to_string(), "foo=bar; HttpOnly"); - let cookie = Cookie::build("foo", "bar") - .max_age(Duration::seconds(10)) - .finish(); + let cookie = Cookie::build("foo", "bar").max_age(10).finish(); assert_eq!(&cookie.to_string(), "foo=bar; Max-Age=10"); let cookie = Cookie::build("foo", "bar").secure(true).finish(); diff --git a/actix-http/src/cookie/parse.rs b/actix-http/src/cookie/parse.rs index cc042733e..42a2c1fcf 100644 --- a/actix-http/src/cookie/parse.rs +++ b/actix-http/src/cookie/parse.rs @@ -5,8 +5,8 @@ use std::error::Error; use std::fmt; use std::str::Utf8Error; -use percent_encoding::percent_decode; use chrono::Duration; +use percent_encoding::percent_decode; use super::{Cookie, CookieStr, SameSite}; @@ -220,8 +220,8 @@ where #[cfg(test)] mod tests { use super::{Cookie, SameSite}; - use time::strptime; use chrono::Duration; + use time::strptime; macro_rules! assert_eq_parse { ($string:expr, $expected:expr) => { @@ -419,9 +419,7 @@ mod tests { #[test] fn do_not_panic_on_large_max_ages() { let max_seconds = Duration::max_value().num_seconds(); - let expected = Cookie::build("foo", "bar") - .max_age(Duration::seconds(max_seconds)) - .finish(); + let expected = Cookie::build("foo", "bar").max_age(max_seconds).finish(); assert_eq_parse!(format!(" foo=bar; Max-Age={:?}", max_seconds + 1), expected); } } diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 6125ae1cb..fd51e54c7 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -860,7 +860,7 @@ mod tests { .domain("www.rust-lang.org") .path("/test") .http_only(true) - .max_age(time::Duration::days(1)) + .max_age_time(time::Duration::days(1)) .finish(), ) .del_cookie(&cookies[1]) diff --git a/actix-session/CHANGES.md b/actix-session/CHANGES.md index 54ea66d9e..dfa3033c9 100644 --- a/actix-session/CHANGES.md +++ b/actix-session/CHANGES.md @@ -1,5 +1,8 @@ # Changes + +* `CookieSession::max_age()` accepts value in seconds + ## [0.1.0-alpha.6] - 2019-04-14 * Update actix-web alpha.6 diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index 058fc7068..e21bb732f 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -32,7 +32,7 @@ futures = "0.1.25" hashbrown = "0.2.0" serde = "1.0" serde_json = "1.0" -time = "0.1" +time = "0.1.42" [dev-dependencies] actix-rt = "0.2.2" diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index 4f7614dc6..ac08d1146 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -17,7 +17,6 @@ use std::collections::HashMap; use std::rc::Rc; -use std::time::Duration; use actix_service::{Service, Transform}; use actix_web::cookie::{Cookie, CookieJar, Key, SameSite}; @@ -57,7 +56,7 @@ struct CookieSessionInner { domain: Option, secure: bool, http_only: bool, - max_age: Option, + max_age: Option, same_site: Option, } @@ -98,7 +97,7 @@ impl CookieSessionInner { } if let Some(max_age) = self.max_age { - cookie.set_max_age(time::Duration::from_std(max_age).unwrap()); + cookie.set_max_age(max_age); } if let Some(same_site) = self.same_site { @@ -250,7 +249,12 @@ impl CookieSession { } /// Sets the `max-age` field in the session cookie being built. - pub fn max_age(mut self, value: Duration) -> CookieSession { + pub fn max_age(self, seconds: i64) -> CookieSession { + self.max_age_time(time::Duration::seconds(seconds)) + } + + /// Sets the `max-age` field in the session cookie being built. + pub fn max_age_time(mut self, value: time::Duration) -> CookieSession { Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); self } @@ -390,7 +394,7 @@ mod tests { .domain("localhost") .http_only(true) .same_site(SameSite::Lax) - .max_age(Duration::from_secs(100)), + .max_age(100), ) .service(web::resource("/").to(|ses: Session| { let _ = ses.set("counter", 100); diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index bf739636f..ba03366fa 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -53,7 +53,7 @@ use std::rc::Rc; use actix_service::{Service, Transform}; use futures::future::{ok, Either, FutureResult}; use futures::{Future, IntoFuture, Poll}; -use chrono::Duration; +use time::Duration; use crate::cookie::{Cookie, CookieJar, Key, SameSite}; use crate::error::{Error, Result}; @@ -427,16 +427,16 @@ impl CookieIdentityPolicy { self } + /// Sets the `max-age` field in the session cookie being built with given number of seconds. + pub fn max_age(self, seconds: i64) -> CookieIdentityPolicy { + self.max_age_time(Duration::seconds(seconds)) + } + /// Sets the `max-age` field in the session cookie being built with `chrono::Duration`. pub fn max_age_time(mut self, value: Duration) -> CookieIdentityPolicy { Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); self } - /// Sets the `max-age` field in the session cookie being built with given number of seconds. - pub fn max_age(mut self, seconds: isize) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().max_age = Some(Duration::seconds(seconds as i64)); - self - } /// Sets the `same_site` field in the session cookie being built. pub fn same_site(mut self, same_site: SameSite) -> Self { @@ -547,7 +547,7 @@ mod tests { .service(web::resource("/login").to(|id: Identity| { id.remember("test".to_string()); HttpResponse::Ok() - })) + })), ); let resp = test::call_service(&mut srv, TestRequest::with_uri("/login").to_request()); @@ -559,7 +559,7 @@ mod tests { #[test] fn test_identity_max_age() { - let seconds = 60isize; + let seconds = 60; let mut srv = test::init_service( App::new() .wrap(IdentityService::new( @@ -573,7 +573,7 @@ mod tests { .service(web::resource("/login").to(|id: Identity| { id.remember("test".to_string()); HttpResponse::Ok() - })) + })), ); let resp = test::call_service(&mut srv, TestRequest::with_uri("/login").to_request()); diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index 4b0afe7a9..060331a6b 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -1,8 +1,8 @@ //! `Middleware` to normalize request's URI -use regex::Regex; use actix_service::{Service, Transform}; use futures::future::{self, FutureResult}; +use regex::Regex; use crate::service::{ServiceRequest, ServiceResponse}; @@ -28,7 +28,7 @@ where fn new_transform(&self, service: S) -> Self::Future { future::ok(NormalizePathNormalization { service, - merge_slash: Regex::new("//+").unwrap() + merge_slash: Regex::new("//+").unwrap(), }) } } @@ -72,7 +72,6 @@ mod tests { use super::*; use crate::dev::ServiceRequest; - use crate::http::header::CONTENT_TYPE; use crate::test::{block_on, TestRequest}; use crate::HttpResponse; diff --git a/src/test.rs b/src/test.rs index a8eed3881..d932adfd3 100644 --- a/src/test.rs +++ b/src/test.rs @@ -335,12 +335,12 @@ impl TestRequest { pub fn post() -> TestRequest { TestRequest::default().method(Method::POST) } - + /// Create TestRequest and set method to `Method::PUT` pub fn put() -> TestRequest { TestRequest::default().method(Method::PUT) } - + /// Create TestRequest and set method to `Method::PATCH` pub fn patch() -> TestRequest { TestRequest::default().method(Method::PATCH) @@ -521,7 +521,6 @@ mod tests { let result = read_response(&mut app, put_req); assert_eq!(result, Bytes::from_static(b"put!")); - let patch_req = TestRequest::patch() .uri("/index.html") .header(header::CONTENT_TYPE, "application/json") From fc9b14a933f49c40fa43f31335913e6e92a701d7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 19 Apr 2019 18:03:44 -0700 Subject: [PATCH 104/122] allow to specify server address for http and ws requests --- actix-http/CHANGES.md | 2 + actix-http/Cargo.toml | 2 +- actix-http/src/client/connector.rs | 106 ++++++++++++++--------------- actix-http/src/client/mod.rs | 8 +++ actix-http/src/client/pool.rs | 22 +++--- awc/CHANGES.md | 6 +- awc/Cargo.toml | 2 +- awc/src/builder.rs | 6 +- awc/src/connect.rs | 24 +++++-- awc/src/request.rs | 15 +++- awc/src/ws.rs | 14 +++- 11 files changed, 129 insertions(+), 78 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index a82b864b3..fc33ff411 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -8,6 +8,8 @@ * Cookie::max_age_time() accepts value in time::Duration +* Allow to specify server address for client connector + ## [0.1.0] - 2019-04-16 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index b94e12d87..1f8056b36 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -46,7 +46,7 @@ secure-cookies = ["ring"] [dependencies] actix-service = "0.3.6" actix-codec = "0.1.2" -actix-connect = "0.1.4" +actix-connect = "0.1.5" actix-utils = "0.3.5" actix-server-config = "0.1.1" actix-threadpool = "0.1.0" diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index ed6207f9f..639afb755 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -14,6 +14,7 @@ use tokio_tcp::TcpStream; use super::connection::Connection; use super::error::ConnectError; use super::pool::{ConnectionPool, Protocol}; +use super::Connect; #[cfg(feature = "ssl")] use openssl::ssl::SslConnector; @@ -177,15 +178,17 @@ where /// its combinator chain. pub fn finish( self, - ) -> impl Service + Clone - { + ) -> impl Service + + Clone { #[cfg(not(feature = "ssl"))] { let connector = TimeoutService::new( self.timeout, - apply_fn(self.connector, |msg: Uri, srv| srv.call(msg.into())) - .map_err(ConnectError::from) - .map(|stream| (stream.into_parts().0, Protocol::Http1)), + apply_fn(self.connector, |msg: Connect, srv| { + srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) + }) + .map_err(ConnectError::from) + .map(|stream| (stream.into_parts().0, Protocol::Http1)), ) .map_err(|e| match e { TimeoutError::Service(e) => e, @@ -209,26 +212,28 @@ where let ssl_service = TimeoutService::new( self.timeout, - apply_fn(self.connector.clone(), |msg: Uri, srv| srv.call(msg.into())) - .map_err(ConnectError::from) - .and_then( - OpensslConnector::service(self.ssl) - .map_err(ConnectError::from) - .map(|stream| { - let sock = stream.into_parts().0; - let h2 = sock - .get_ref() - .ssl() - .selected_alpn_protocol() - .map(|protos| protos.windows(2).any(|w| w == H2)) - .unwrap_or(false); - if h2 { - (sock, Protocol::Http2) - } else { - (sock, Protocol::Http1) - } - }), - ), + apply_fn(self.connector.clone(), |msg: Connect, srv| { + srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) + }) + .map_err(ConnectError::from) + .and_then( + OpensslConnector::service(self.ssl) + .map_err(ConnectError::from) + .map(|stream| { + let sock = stream.into_parts().0; + let h2 = sock + .get_ref() + .ssl() + .selected_alpn_protocol() + .map(|protos| protos.windows(2).any(|w| w == H2)) + .unwrap_or(false); + if h2 { + (sock, Protocol::Http2) + } else { + (sock, Protocol::Http1) + } + }), + ), ) .map_err(|e| match e { TimeoutError::Service(e) => e, @@ -237,9 +242,11 @@ where let tcp_service = TimeoutService::new( self.timeout, - apply_fn(self.connector.clone(), |msg: Uri, srv| srv.call(msg.into())) - .map_err(ConnectError::from) - .map(|stream| (stream.into_parts().0, Protocol::Http1)), + apply_fn(self.connector.clone(), |msg: Connect, srv| { + srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) + }) + .map_err(ConnectError::from) + .map(|stream| (stream.into_parts().0, Protocol::Http1)), ) .map_err(|e| match e { TimeoutError::Service(e) => e, @@ -264,15 +271,6 @@ where } } } - - #[doc(hidden)] - #[deprecated(since = "0.1.0-alpha4", note = "please use `.finish()` method")] - pub fn service( - self, - ) -> impl Service + Clone - { - self.finish() - } } #[cfg(not(feature = "ssl"))] @@ -286,7 +284,7 @@ mod connect_impl { pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { pub(crate) tcp_pool: ConnectionPool, } @@ -294,7 +292,7 @@ mod connect_impl { impl Clone for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service + T: Service + Clone, { fn clone(&self) -> Self { @@ -307,9 +305,9 @@ mod connect_impl { impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { - type Request = Uri; + type Request = Connect; type Response = IoConnection; type Error = ConnectError; type Future = Either< @@ -346,8 +344,8 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service, - T2: Service, + T1: Service, + T2: Service, { pub(crate) tcp_pool: ConnectionPool, pub(crate) ssl_pool: ConnectionPool, @@ -357,9 +355,9 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service + T1: Service + Clone, - T2: Service + T2: Service + Clone, { fn clone(&self) -> Self { @@ -374,10 +372,10 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service, - T2: Service, + T1: Service, + T2: Service, { - type Request = Uri; + type Request = Connect; type Response = EitherConnection; type Error = ConnectError; type Future = Either< @@ -392,8 +390,8 @@ mod connect_impl { self.tcp_pool.poll_ready() } - fn call(&mut self, req: Uri) -> Self::Future { - match req.scheme_str() { + fn call(&mut self, req: Connect) -> Self::Future { + match req.uri.scheme_str() { Some("https") | Some("wss") => { Either::B(Either::B(InnerConnectorResponseB { fut: self.ssl_pool.call(req), @@ -411,7 +409,7 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseA where Io1: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { fut: as Service>::Future, _t: PhantomData, @@ -419,7 +417,7 @@ mod connect_impl { impl Future for InnerConnectorResponseA where - T: Service, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { @@ -437,7 +435,7 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseB where Io2: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { fut: as Service>::Future, _t: PhantomData, @@ -445,7 +443,7 @@ mod connect_impl { impl Future for InnerConnectorResponseB where - T: Service, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { diff --git a/actix-http/src/client/mod.rs b/actix-http/src/client/mod.rs index cf526e25e..1d10117cd 100644 --- a/actix-http/src/client/mod.rs +++ b/actix-http/src/client/mod.rs @@ -1,4 +1,6 @@ //! Http client api +use http::Uri; + mod connection; mod connector; mod error; @@ -10,3 +12,9 @@ pub use self::connection::Connection; pub use self::connector::Connector; pub use self::error::{ConnectError, InvalidUrl, SendRequestError}; pub use self::pool::Protocol; + +#[derive(Clone)] +pub struct Connect { + pub uri: Uri, + pub addr: Option, +} diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 68ac6fbc8..7b138dbaa 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -13,13 +13,14 @@ use futures::unsync::oneshot; use futures::{Async, Future, Poll}; use h2::client::{handshake, Handshake}; use hashbrown::HashMap; -use http::uri::{Authority, Uri}; +use http::uri::Authority; use indexmap::IndexSet; use slab::Slab; use tokio_timer::{sleep, Delay}; use super::connection::{ConnectionType, IoConnection}; use super::error::ConnectError; +use super::Connect; #[derive(Clone, Copy, PartialEq)] /// Protocol version @@ -48,7 +49,7 @@ pub(crate) struct ConnectionPool( impl ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { pub(crate) fn new( connector: T, @@ -87,9 +88,9 @@ where impl Service for ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { - type Request = Uri; + type Request = Connect; type Response = IoConnection; type Error = ConnectError; type Future = Either< @@ -101,8 +102,8 @@ where self.0.poll_ready() } - fn call(&mut self, req: Uri) -> Self::Future { - let key = if let Some(authority) = req.authority_part() { + fn call(&mut self, req: Connect) -> Self::Future { + let key = if let Some(authority) = req.uri.authority_part() { authority.clone().into() } else { return Either::A(err(ConnectError::Unresolverd)); @@ -292,7 +293,10 @@ pub(crate) struct Inner { limit: usize, acquired: usize, available: HashMap>>, - waiters: Slab<(Uri, oneshot::Sender, ConnectError>>)>, + waiters: Slab<( + Connect, + oneshot::Sender, ConnectError>>, + )>, waiters_queue: IndexSet<(Key, usize)>, task: AtomicTask, } @@ -331,14 +335,14 @@ where /// connection is not available, wait fn wait_for( &mut self, - connect: Uri, + connect: Connect, ) -> ( oneshot::Receiver, ConnectError>>, usize, ) { let (tx, rx) = oneshot::channel(); - let key: Key = connect.authority_part().unwrap().clone().into(); + let key: Key = connect.uri.authority_part().unwrap().clone().into(); let entry = self.waiters.vacant_entry(); let token = entry.key(); entry.insert((connect, tx)); diff --git a/awc/CHANGES.md b/awc/CHANGES.md index a4f43d292..30fd4a6d2 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,10 @@ # Changes -## [0.1.1] - 2019-04-xx +## [0.1.1] - 2019-04-19 + +### Added + +* Allow to specify server address for http and ws requests. ### Changed diff --git a/awc/Cargo.toml b/awc/Cargo.toml index bbc3d9287..b8c4f9898 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "0.1.0" +version = "0.1.1" authors = ["Nikolay Kim "] description = "Actix http client." readme = "README.md" diff --git a/awc/src/builder.rs b/awc/src/builder.rs index ddefed439..c460f1357 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -3,8 +3,8 @@ use std::fmt; use std::rc::Rc; use std::time::Duration; -use actix_http::client::{ConnectError, Connection, Connector}; -use actix_http::http::{header, HeaderMap, HeaderName, HttpTryFrom, Uri}; +use actix_http::client::{Connect, ConnectError, Connection, Connector}; +use actix_http::http::{header, HeaderMap, HeaderName, HttpTryFrom}; use actix_service::Service; use crate::connect::ConnectorWrapper; @@ -40,7 +40,7 @@ impl ClientBuilder { /// Use custom connector service. pub fn connector(mut self, connector: T) -> Self where - T: Service + 'static, + T: Service + 'static, T::Response: Connection, ::Future: 'static, T::Future: 'static, diff --git a/awc/src/connect.rs b/awc/src/connect.rs index bfc9da05f..4b564d777 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -1,10 +1,12 @@ -use std::{fmt, io}; +use std::{fmt, io, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::body::Body; -use actix_http::client::{ConnectError, Connection, SendRequestError}; +use actix_http::client::{ + Connect as ClientConnect, ConnectError, Connection, SendRequestError, +}; use actix_http::h1::ClientCodec; -use actix_http::{http, RequestHead, ResponseHead}; +use actix_http::{RequestHead, ResponseHead}; use actix_service::Service; use futures::{Future, Poll}; @@ -17,12 +19,14 @@ pub(crate) trait Connect { &mut self, head: RequestHead, body: Body, + addr: Option, ) -> Box>; /// Send request, returns Response and Framed fn open_tunnel( &mut self, head: RequestHead, + addr: Option, ) -> Box< Future< Item = (ResponseHead, Framed), @@ -33,7 +37,7 @@ pub(crate) trait Connect { impl Connect for ConnectorWrapper where - T: Service, + T: Service, T::Response: Connection, ::Io: 'static, ::Future: 'static, @@ -44,11 +48,15 @@ where &mut self, head: RequestHead, body: Body, + addr: Option, ) -> Box> { Box::new( self.0 // connect to the host - .call(head.uri.clone()) + .call(ClientConnect { + uri: head.uri.clone(), + addr, + }) .from_err() // send request .and_then(move |connection| connection.send_request(head, body)) @@ -59,6 +67,7 @@ where fn open_tunnel( &mut self, head: RequestHead, + addr: Option, ) -> Box< Future< Item = (ResponseHead, Framed), @@ -68,7 +77,10 @@ where Box::new( self.0 // connect to the host - .call(head.uri.clone()) + .call(ClientConnect { + uri: head.uri.clone(), + addr, + }) .from_err() // send request .and_then(move |connection| connection.open_tunnel(head)) diff --git a/awc/src/request.rs b/awc/src/request.rs index a280dfce1..2e6032649 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -1,8 +1,8 @@ -use std::fmt; use std::fmt::Write as FmtWrite; use std::io::Write; use std::rc::Rc; use std::time::Duration; +use std::{fmt, net}; use bytes::{BufMut, Bytes, BytesMut}; use futures::future::{err, Either}; @@ -60,6 +60,7 @@ const HTTPS_ENCODING: &str = "gzip, deflate"; pub struct ClientRequest { pub(crate) head: RequestHead, err: Option, + addr: Option, cookies: Option, response_decompress: bool, timeout: Option, @@ -76,6 +77,7 @@ impl ClientRequest { config, head: RequestHead::default(), err: None, + addr: None, cookies: None, timeout: None, response_decompress: true, @@ -97,6 +99,15 @@ impl ClientRequest { self } + /// Set socket address of the server. + /// + /// This address is used for connection. If address is not + /// provided url's host name get resolved. + pub fn address(mut self, addr: net::SocketAddr) -> Self { + self.addr = Some(addr); + self + } + /// Set HTTP method of this request. #[inline] pub fn method(mut self, method: Method) -> Self { @@ -435,7 +446,7 @@ impl ClientRequest { let fut = config .connector .borrow_mut() - .send_request(head, body.into()) + .send_request(head, body.into(), slf.addr) .map(move |res| { res.map_body(|head, payload| { if response_decompress { diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 028330ab4..94a90535b 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -1,5 +1,6 @@ //! Websockets client use std::fmt::Write as FmtWrite; +use std::net::SocketAddr; use std::rc::Rc; use std::{fmt, str}; @@ -29,6 +30,7 @@ pub struct WebsocketsRequest { err: Option, origin: Option, protocols: Option, + addr: Option, max_size: usize, server_mode: bool, cookies: Option, @@ -55,6 +57,7 @@ impl WebsocketsRequest { head, err, config, + addr: None, origin: None, protocols: None, max_size: 65_536, @@ -63,6 +66,15 @@ impl WebsocketsRequest { } } + /// Set socket address of the server. + /// + /// This address is used for connection. If address is not + /// provided url's host name get resolved. + pub fn address(mut self, addr: SocketAddr) -> Self { + self.addr = Some(addr); + self + } + /// Set supported websocket protocols pub fn protocols(mut self, protos: U) -> Self where @@ -274,7 +286,7 @@ impl WebsocketsRequest { .config .connector .borrow_mut() - .open_tunnel(head) + .open_tunnel(head, self.addr) .from_err() .and_then(move |(head, framed)| { // verify response From 6decfdda1f5da983c07c35260afa36894f2d8151 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 19 Apr 2019 18:06:34 -0700 Subject: [PATCH 105/122] update deps --- awc/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awc/Cargo.toml b/awc/Cargo.toml index b8c4f9898..a254d69c6 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -38,7 +38,7 @@ flate2-rust = ["actix-http/flate2-rust"] [dependencies] actix-codec = "0.1.2" actix-service = "0.3.6" -actix-http = "0.1.0" +actix-http = "0.1.1" base64 = "0.10.1" bytes = "0.4" derive_more = "0.14" @@ -56,7 +56,7 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.2" actix-web = { version = "1.0.0-alpha.6", features=["ssl"] } -actix-http = { version = "0.1.0", features=["ssl"] } +actix-http = { version = "0.1.1", features=["ssl"] } actix-http-test = { version = "0.1.0", features=["ssl"] } actix-utils = "0.3.4" actix-server = { version = "0.4.3", features=["ssl"] } From 9f421b81b85b0d9282fc42eefe98e01348d3e732 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 19 Apr 2019 18:10:53 -0700 Subject: [PATCH 106/122] fix non-ssl connector --- actix-http/src/client/connector.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 639afb755..14df2eee8 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -319,8 +319,8 @@ mod connect_impl { self.tcp_pool.poll_ready() } - fn call(&mut self, req: Uri) -> Self::Future { - match req.scheme_str() { + fn call(&mut self, req: Connect) -> Self::Future { + match req.uri.scheme_str() { Some("https") | Some("wss") => { Either::B(err(ConnectError::SslIsNotSupported)) } From 5e4e95fb0ae6d66f0d2faafaf67db5184c31ff7d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 19 Apr 2019 18:13:05 -0700 Subject: [PATCH 107/122] update create version --- actix-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 1f8056b36..ac15667f7 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.1.0" +version = "0.1.1" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" From 01b1350dcc2382d11907b5c78228a77eb380619b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 19 Apr 2019 18:16:01 -0700 Subject: [PATCH 108/122] update versions --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cfc3d59fa..f49884c85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,11 +71,11 @@ actix-utils = "0.3.4" actix-router = "0.1.2" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-alpha.6" -actix-http = { version = "0.1.0", features=["fail"] } +actix-http = { version = "0.1.1", features=["fail"] } actix-server = "0.4.3" actix-server-config = "0.1.1" actix-threadpool = "0.1.0" -awc = { version = "0.1.0", optional = true } +awc = { version = "0.1.1", optional = true } bytes = "0.4" derive_more = "0.14" @@ -98,7 +98,7 @@ openssl = { version="0.10", optional = true } rustls = { version = "^0.15", optional = true } [dev-dependencies] -actix-http = { version = "0.1.0", features=["ssl", "brotli", "flate2-zlib"] } +actix-http = { version = "0.1.1", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.1.0", features=["ssl"] } actix-files = { version = "0.1.0-alpha.6" } rand = "0.6" From 891f85754761572906b5b0b6db3056a93a1103f3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 20 Apr 2019 11:18:04 -0700 Subject: [PATCH 109/122] update changes --- CHANGES.md | 2 ++ src/middleware/mod.rs | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 573d287c1..ea4cdc5dc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,8 @@ * Add `.peer_addr()` #744 +* Add `NormalizePath` middleware + ### Changed * Rename `RouterConfig` to `ServiceConfig` diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 3df926251..5266f7c1a 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -6,10 +6,11 @@ pub mod cors; mod defaultheaders; pub mod errhandlers; mod logger; -pub mod normalize; +mod normalize; pub use self::defaultheaders::DefaultHeaders; pub use self::logger::Logger; +pub use self::normalize::NormalizePath; #[cfg(feature = "secure-cookies")] pub mod identity; From 7e480ab2f77659e0694f23447b2ecb7f08cea994 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 20 Apr 2019 21:16:51 -0700 Subject: [PATCH 110/122] beta.1 release --- CHANGES.md | 2 +- Cargo.toml | 4 ++-- actix-files/CHANGES.md | 4 ++++ actix-files/Cargo.toml | 6 +++--- actix-session/CHANGES.md | 3 +++ actix-session/Cargo.toml | 6 +++--- actix-web-codegen/CHANGES.md | 4 ++++ actix-web-codegen/Cargo.toml | 4 ++-- 8 files changed, 22 insertions(+), 11 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ea4cdc5dc..eed851a76 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [1.0.0-beta.1] - 2019-04-xx +## [1.0.0-beta.1] - 2019-04-20 ### Added diff --git a/Cargo.toml b/Cargo.toml index f49884c85..c4e01e9fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ actix-service = "0.3.6" actix-utils = "0.3.4" actix-router = "0.1.2" actix-rt = "0.2.2" -actix-web-codegen = "0.1.0-alpha.6" +actix-web-codegen = "0.1.0-beta.1" actix-http = { version = "0.1.1", features=["fail"] } actix-server = "0.4.3" actix-server-config = "0.1.1" @@ -100,7 +100,7 @@ rustls = { version = "^0.15", optional = true } [dev-dependencies] actix-http = { version = "0.1.1", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.1.0", features=["ssl"] } -actix-files = { version = "0.1.0-alpha.6" } +actix-files = { version = "0.1.0-beta.1" } rand = "0.6" env_logger = "0.6" serde_derive = "1.0" diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index ae22fca19..05a5e5805 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.0-beta.1] - 2019-04-20 + +* Update actix-web to beta.1 + ## [0.1.0-alpha.6] - 2019-04-14 * Update actix-web to alpha6 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index fa9d67393..8d242a724 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.1.0-alpha.6" +version = "0.1.0-betsa.1" authors = ["Nikolay Kim "] description = "Static files support for actix web." readme = "README.md" @@ -18,7 +18,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = "1.0.0-alpha.6" +actix-web = "1.0.0-beta.1" 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.6", features=["ssl"] } +actix-web = { version = "1.0.0-beta.1", features=["ssl"] } diff --git a/actix-session/CHANGES.md b/actix-session/CHANGES.md index dfa3033c9..a60d1e668 100644 --- a/actix-session/CHANGES.md +++ b/actix-session/CHANGES.md @@ -1,5 +1,8 @@ # Changes +## [0.1.0-beta.1] - 2019-04-20 + +* Update actix-web to beta.1 * `CookieSession::max_age()` accepts value in seconds diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index e21bb732f..83f9807f7 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-session" -version = "0.1.0-alpha.6" +version = "0.1.0-beta.1" authors = ["Nikolay Kim "] description = "Session for actix web framework." readme = "README.md" @@ -24,12 +24,12 @@ default = ["cookie-session"] cookie-session = ["actix-web/secure-cookies"] [dependencies] -actix-web = "1.0.0-alpha.6" +actix-web = "1.0.0-beta.1" actix-service = "0.3.4" bytes = "0.4" derive_more = "0.14" futures = "0.1.25" -hashbrown = "0.2.0" +hashbrown = "0.2.2" serde = "1.0" serde_json = "1.0" time = "0.1.42" diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 2ce5bd7db..3173ff1f0 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.0-beta.1] - 2019-04-20 + +* Gen code for actix-web 1.0.0-beta.1 + ## [0.1.0-alpha.6] - 2019-04-14 * Gen code for actix-web 1.0.0-alpha.6 diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 13928f67a..4108d879a 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-codegen" -version = "0.1.0-alpha.6" +version = "0.1.0-beta.1" description = "Actix web proc macros" readme = "README.md" authors = ["Nikolay Kim "] @@ -17,6 +17,6 @@ syn = { version = "0.15", features = ["full", "parsing"] } [dev-dependencies] actix-web = { version = "1.0.0-alpha.6" } -actix-http = { version = "0.1.0", features=["ssl"] } +actix-http = { version = "0.1.1", features=["ssl"] } actix-http-test = { version = "0.1.0", features=["ssl"] } futures = { version = "0.1" } From f0789aad055065f9e556a0ca146a301dc3d1b3bb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 21 Apr 2019 09:03:46 -0700 Subject: [PATCH 111/122] update dep versions --- actix-files/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 8d242a724..5e37fc090 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.1.0-betsa.1" +version = "0.1.0-beta.1" authors = ["Nikolay Kim "] description = "Static files support for actix web." readme = "README.md" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 3e67c2313..22fdf613d 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -19,8 +19,8 @@ path = "src/lib.rs" [dependencies] actix = "0.8.0" -actix-web = "1.0.0-alpha.5" -actix-http = "0.1.0" +actix-web = "1.0.0-beta.1" +actix-http = "0.1.1" actix-codec = "0.1.2" bytes = "0.4" futures = "0.1.25" From 895e409d57178666aeb0faff97458052ab6d68ca Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 21 Apr 2019 15:41:01 -0700 Subject: [PATCH 112/122] Optimize multipart handling #634, #769 --- actix-multipart/CHANGES.md | 4 +- actix-multipart/Cargo.toml | 8 +- actix-multipart/src/server.rs | 140 ++++++++++++++++++++-------------- 3 files changed, 90 insertions(+), 62 deletions(-) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index fec3e50f8..9f8fa052a 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,7 +1,9 @@ # Changes -## [0.1.0-alpha.1] - 2019-04-xx +## [0.1.0-beta.1] - 2019-04-21 * Do not support nested multipart * Split multipart support to separate crate + +* Optimize multipart handling #634, #769 \ No newline at end of file diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 58ab45230..8e1714e7d 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.1.0-alpha.1" +version = "0.1.0-beta.1" authors = ["Nikolay Kim "] description = "Multipart support for actix web framework." readme = "README.md" @@ -18,8 +18,8 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = "1.0.0-alpha.6" -actix-service = "0.3.4" +actix-web = "1.0.0-beta.1" +actix-service = "0.3.6" bytes = "0.4" derive_more = "0.14" httparse = "1.3" @@ -31,4 +31,4 @@ twoway = "0.2" [dev-dependencies] actix-rt = "0.2.2" -actix-http = "0.1.0" \ No newline at end of file +actix-http = "0.1.1" \ No newline at end of file diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index c651fae56..59ed55994 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -168,7 +168,7 @@ impl InnerMultipart { match payload.readline() { None => { if payload.eof { - Err(MultipartError::Incomplete) + Ok(Some(true)) } else { Ok(None) } @@ -201,8 +201,7 @@ impl InnerMultipart { match payload.readline() { Some(chunk) => { if chunk.is_empty() { - //ValueError("Could not find starting boundary %r" - //% (self._boundary)) + return Err(MultipartError::Boundary); } if chunk.len() < boundary.len() { continue; @@ -505,47 +504,73 @@ impl InnerField { payload: &mut PayloadBuffer, boundary: &str, ) -> Poll, MultipartError> { - match payload.read_until(b"\r") { - None => { - if payload.eof { - Err(MultipartError::Incomplete) + let mut pos = 0; + + let len = payload.buf.len(); + if len == 0 { + return Ok(Async::NotReady); + } + + // check boundary + if len > 4 && payload.buf[0] == b'\r' { + let b_len = if &payload.buf[..2] == b"\r\n" && &payload.buf[2..4] == b"--" { + Some(4) + } else if &payload.buf[1..3] == b"--" { + Some(3) + } else { + None + }; + + if let Some(b_len) = b_len { + let b_size = boundary.len() + b_len; + if len < b_size { + return Ok(Async::NotReady); } else { - Ok(Async::NotReady) + if &payload.buf[b_len..b_size] == boundary.as_bytes() { + // found boundary + payload.buf.split_to(b_size); + return Ok(Async::Ready(None)); + } else { + pos = b_size; + } } } - Some(mut chunk) => { - if chunk.len() == 1 { - payload.unprocessed(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() - { - payload.unprocessed(chunk); - Ok(Async::Ready(None)) - } else { - // \r might be part of data stream - let ch = chunk.split_to(1); - payload.unprocessed(chunk); - Ok(Async::Ready(Some(ch))) - } - } + } + + loop { + return if let Some(idx) = twoway::find_bytes(&payload.buf[pos..], b"\r") { + let cur = pos + idx; + + // check if we have enough data for boundary detection + if cur + 4 > len { + if cur > 0 { + Ok(Async::Ready(Some(payload.buf.split_to(cur).freeze()))) + } else { + Ok(Async::NotReady) } } else { - let to = chunk.len() - 1; - let ch = chunk.split_to(to); - payload.unprocessed(chunk); - Ok(Async::Ready(Some(ch))) + // check boundary + if (&payload.buf[cur..cur + 2] == b"\r\n" + && &payload.buf[cur + 2..cur + 4] == b"--") + || (&payload.buf[cur..cur + 1] == b"\r" + && &payload.buf[cur + 1..cur + 3] == b"--") + { + if cur != 0 { + // return buffer + Ok(Async::Ready(Some(payload.buf.split_to(cur).freeze()))) + } else { + pos = cur + 1; + continue; + } + } else { + // not boundary + pos = cur + 1; + continue; + } } - } + } else { + return Ok(Async::Ready(Some(payload.buf.take().freeze()))); + }; } } @@ -555,26 +580,27 @@ impl InnerField { } let result = if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) { - let res = if let Some(ref mut len) = self.length { - InnerField::read_len(payload, len)? - } else { - InnerField::read_stream(payload, &self.boundary)? - }; + if !self.eof { + let res = if let Some(ref mut len) = self.length { + InnerField::read_len(payload, len)? + } else { + InnerField::read_stream(payload, &self.boundary)? + }; - match res { - Async::NotReady => Async::NotReady, - Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)), - Async::Ready(None) => { - self.eof = true; - 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"); - } - Async::Ready(None) - } + match res { + Async::NotReady => return Ok(Async::NotReady), + Async::Ready(Some(bytes)) => return Ok(Async::Ready(Some(bytes))), + Async::Ready(None) => self.eof = true, + } + } + + 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"); } + Async::Ready(None) } } } else { @@ -704,7 +730,7 @@ impl PayloadBuffer { } /// Read exact number of bytes - #[inline] + #[cfg(test)] fn read_exact(&mut self, size: usize) -> Option { if size <= self.buf.len() { Some(self.buf.split_to(size).freeze()) From d00c9bb8446ee82a9fbad884c202cf9505c6ccc5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 21 Apr 2019 16:14:09 -0700 Subject: [PATCH 113/122] do not consume boundary --- actix-multipart/src/server.rs | 73 ++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 59ed55994..82b0b5ace 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -528,7 +528,6 @@ impl InnerField { } else { if &payload.buf[b_len..b_size] == boundary.as_bytes() { // found boundary - payload.buf.split_to(b_size); return Ok(Async::Ready(None)); } else { pos = b_size; @@ -901,6 +900,78 @@ mod tests { }); } + #[test] + fn test_stream() { + run_on(|| { + let (sender, payload) = create_stream(); + + let bytes = Bytes::from( + "testasdadsad\r\n\ + --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ + Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\ + Content-Type: text/plain; charset=utf-8\r\n\r\n\ + test\r\n\ + --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ + Content-Type: text/plain; charset=utf-8\r\n\r\n\ + data\r\n\ + --abbc761f78ff4d7cb7573b5a23f96ef0--\r\n", + ); + sender.unbounded_send(Ok(bytes)).unwrap(); + + let mut headers = HeaderMap::new(); + headers.insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static( + "multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"", + ), + ); + + let mut multipart = Multipart::new(&headers, payload); + match multipart.poll().unwrap() { + Async::Ready(Some(mut field)) => { + let cd = field.content_disposition().unwrap(); + assert_eq!(cd.disposition, DispositionType::FormData); + assert_eq!(cd.parameters[0], DispositionParam::Name("file".into())); + + assert_eq!(field.content_type().type_(), mime::TEXT); + assert_eq!(field.content_type().subtype(), mime::PLAIN); + + match field.poll().unwrap() { + Async::Ready(Some(chunk)) => assert_eq!(chunk, "test"), + _ => unreachable!(), + } + match field.poll().unwrap() { + Async::Ready(None) => (), + _ => unreachable!(), + } + } + _ => unreachable!(), + } + + match multipart.poll().unwrap() { + Async::Ready(Some(mut field)) => { + 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, "data"), + _ => unreachable!(), + } + match field.poll() { + Ok(Async::Ready(None)) => (), + _ => unreachable!(), + } + } + _ => unreachable!(), + } + + match multipart.poll().unwrap() { + Async::Ready(None) => (), + _ => unreachable!(), + } + }); + } + #[test] fn test_basic() { run_on(|| { From 48bee5508789ad92a78162b8e1691c1ecd3de616 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Apr 2019 14:22:08 -0700 Subject: [PATCH 114/122] .to_async() handler can return Responder type #792 --- CHANGES.md | 9 +++++++++ src/handler.rs | 50 ++++++++++++++++++++++++++++++++++++------------- src/resource.rs | 2 +- src/route.rs | 37 ++++++++++++++++++++++++++++-------- src/test.rs | 40 +++++++++++++++++++++++++++++++++++++++ src/web.rs | 4 ++-- 6 files changed, 118 insertions(+), 24 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index eed851a76..6f0a2d422 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,14 @@ # Changes +### Added + +* Add helper functions for reading response body `test::read_body()` + +### Changed + +* `.to_async()` handler can return `Responder` type #792 + + ## [1.0.0-beta.1] - 2019-04-20 ### Added diff --git a/src/handler.rs b/src/handler.rs index f328cd25d..850c0c92c 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -124,7 +124,7 @@ where pub trait AsyncFactory: Clone + 'static where R: IntoFuture, - R::Item: Into, + R::Item: Responder, R::Error: Into, { fn call(&self, param: T) -> R; @@ -134,7 +134,7 @@ impl AsyncFactory<(), R> for F where F: Fn() -> R + Clone + 'static, R: IntoFuture, - R::Item: Into, + R::Item: Responder, R::Error: Into, { fn call(&self, _: ()) -> R { @@ -147,7 +147,7 @@ pub struct AsyncHandler where F: AsyncFactory, R: IntoFuture, - R::Item: Into, + R::Item: Responder, R::Error: Into, { hnd: F, @@ -158,7 +158,7 @@ impl AsyncHandler where F: AsyncFactory, R: IntoFuture, - R::Item: Into, + R::Item: Responder, R::Error: Into, { pub fn new(hnd: F) -> Self { @@ -173,7 +173,7 @@ impl Clone for AsyncHandler where F: AsyncFactory, R: IntoFuture, - R::Item: Into, + R::Item: Responder, R::Error: Into, { fn clone(&self) -> Self { @@ -188,7 +188,7 @@ impl Service for AsyncHandler where F: AsyncFactory, R: IntoFuture, - R::Item: Into, + R::Item: Responder, R::Error: Into, { type Request = (T, HttpRequest); @@ -203,32 +203,56 @@ where fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future { AsyncHandlerServiceResponse { fut: self.hnd.call(param).into_future(), + fut2: None, req: Some(req), } } } #[doc(hidden)] -pub struct AsyncHandlerServiceResponse { +pub struct AsyncHandlerServiceResponse +where + T: Future, + T::Item: Responder, +{ fut: T, + fut2: Option<<::Future as IntoFuture>::Future>, req: Option, } impl Future for AsyncHandlerServiceResponse where T: Future, - T::Item: Into, + T::Item: Responder, T::Error: Into, { type Item = ServiceResponse; type Error = Void; fn poll(&mut self) -> Poll { + if let Some(ref mut fut) = self.fut2 { + return match fut.poll() { + Ok(Async::Ready(res)) => Ok(Async::Ready(ServiceResponse::new( + self.req.take().unwrap(), + res, + ))), + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(e) => { + let res: Response = e.into().into(); + Ok(Async::Ready(ServiceResponse::new( + self.req.take().unwrap(), + res, + ))) + } + }; + } + match self.fut.poll() { - Ok(Async::Ready(res)) => Ok(Async::Ready(ServiceResponse::new( - self.req.take().unwrap(), - res.into(), - ))), + Ok(Async::Ready(res)) => { + self.fut2 = + Some(res.respond_to(self.req.as_ref().unwrap()).into_future()); + return self.poll(); + } Ok(Async::NotReady) => Ok(Async::NotReady), Err(e) => { let res: Response = e.into().into(); @@ -357,7 +381,7 @@ macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => { impl AsyncFactory<($($T,)+), Res> for Func where Func: Fn($($T,)+) -> Res + Clone + 'static, Res: IntoFuture, - Res::Item: Into, + Res::Item: Responder, Res::Error: Into, { fn call(&self, param: ($($T,)+)) -> Res { diff --git a/src/resource.rs b/src/resource.rs index 1f1e6e157..03c614a9d 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -217,7 +217,7 @@ where F: AsyncFactory, I: FromRequest + 'static, R: IntoFuture + 'static, - R::Item: Into, + R::Item: Responder, R::Error: Into, { self.routes.push(Route::new().to_async(handler)); diff --git a/src/route.rs b/src/route.rs index 7b9f36a64..8c97d7720 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,7 +1,7 @@ use std::cell::RefCell; use std::rc::Rc; -use actix_http::{http::Method, Error, Extensions, Response}; +use actix_http::{http::Method, Error, Extensions}; use actix_service::{NewService, Service}; use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; @@ -278,7 +278,7 @@ impl Route { F: AsyncFactory, T: FromRequest + 'static, R: IntoFuture + 'static, - R::Item: Into, + R::Item: Responder, R::Error: Into, { self.service = Box::new(RouteNewService::new(Extract::new( @@ -418,18 +418,25 @@ where mod tests { use std::time::Duration; + use bytes::Bytes; use futures::Future; + use serde_derive::Serialize; use tokio_timer::sleep; use crate::http::{Method, StatusCode}; - use crate::test::{call_service, init_service, TestRequest}; + use crate::test::{call_service, init_service, read_body, TestRequest}; use crate::{error, web, App, HttpResponse}; + #[derive(Serialize, PartialEq, Debug)] + struct MyObject { + name: String, + } + #[test] fn test_route() { - let mut srv = - init_service( - App::new().service( + let mut srv = init_service( + App::new() + .service( web::resource("/test") .route(web::get().to(|| HttpResponse::Ok())) .route(web::put().to(|| { @@ -444,8 +451,15 @@ mod tests { Err::(error::ErrorBadRequest("err")) }) })), - ), - ); + ) + .service(web::resource("/json").route(web::get().to_async(|| { + sleep(Duration::from_millis(25)).then(|_| { + Ok::<_, crate::Error>(web::Json(MyObject { + name: "test".to_string(), + })) + }) + }))), + ); let req = TestRequest::with_uri("/test") .method(Method::GET) @@ -476,5 +490,12 @@ mod tests { .to_request(); let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + + let req = TestRequest::with_uri("/json").to_request(); + let resp = call_service(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + + let body = read_body(resp); + assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}")); } } diff --git a/src/test.rs b/src/test.rs index d932adfd3..1f3a24271 100644 --- a/src/test.rs +++ b/src/test.rs @@ -193,6 +193,46 @@ where .unwrap_or_else(|_| panic!("read_response failed at block_on unwrap")) } +/// Helper function that returns a response body of a ServiceResponse. +/// This function blocks the current thread until futures complete. +/// +/// ```rust +/// use actix_web::{test, web, App, HttpResponse, http::header}; +/// use bytes::Bytes; +/// +/// #[test] +/// fn test_index() { +/// let mut app = test::init_service( +/// App::new().service( +/// web::resource("/index.html") +/// .route(web::post().to( +/// || HttpResponse::Ok().body("welcome!"))))); +/// +/// let req = test::TestRequest::post() +/// .uri("/index.html") +/// .header(header::CONTENT_TYPE, "application/json") +/// .to_request(); +/// +/// let resp = call_service(&mut srv, req); +/// let result = test::read_body(resp); +/// assert_eq!(result, Bytes::from_static(b"welcome!")); +/// } +/// ``` +pub fn read_body(mut res: ServiceResponse) -> Bytes +where + B: MessageBody, +{ + block_on(run_on(move || { + res.take_body() + .fold(BytesMut::new(), move |mut body, chunk| { + body.extend_from_slice(&chunk); + Ok::<_, Error>(body) + }) + .map(|body: BytesMut| body.freeze()) + })) + .unwrap_or_else(|_| panic!("read_response failed at block_on unwrap")) +} + /// Helper function that returns a deserialized response body of a TestRequest /// This function blocks the current thread until futures complete. /// diff --git a/src/web.rs b/src/web.rs index 079dec516..73314449c 100644 --- a/src/web.rs +++ b/src/web.rs @@ -1,5 +1,5 @@ //! Essentials helper functions and types for application registration. -use actix_http::{http::Method, Response}; +use actix_http::http::Method; use futures::{Future, IntoFuture}; pub use actix_http::Response as HttpResponse; @@ -268,7 +268,7 @@ where F: AsyncFactory, I: FromRequest + 'static, R: IntoFuture + 'static, - R::Item: Into, + R::Item: Responder, R::Error: Into, { Route::new().to_async(handler) From 3532602299f855ba1b9de291db9a8128fb72cb0f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Apr 2019 21:22:17 -0700 Subject: [PATCH 115/122] Added support for remainder match (i.e /path/{tail}*) --- CHANGES.md | 3 +++ Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 6f0a2d422..f4fdd6af7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,9 @@ * Add helper functions for reading response body `test::read_body()` +* Added support for `remainder match` (i.e "/path/{tail}*") + + ### Changed * `.to_async()` handler can return `Responder` type #792 diff --git a/Cargo.toml b/Cargo.toml index c4e01e9fb..74fe78b4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,7 @@ rust-tls = ["rustls", "actix-server/rust-tls"] actix-codec = "0.1.2" actix-service = "0.3.6" actix-utils = "0.3.4" -actix-router = "0.1.2" +actix-router = "0.1.3" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-beta.1" actix-http = { version = "0.1.1", features=["fail"] } From 5d531989e70d6279ea97e7dead7a1feae3240abc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 23 Apr 2019 09:42:19 -0700 Subject: [PATCH 116/122] Fix BorrowMutError panic in client connector #793 --- actix-http/CHANGES.md | 7 +++++++ actix-http/src/client/pool.rs | 28 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index fc33ff411..2edcceeb0 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.1.2] - 2019-04-23 + +### Fixed + +* Fix BorrowMutError panic in client connector #793 + + ## [0.1.1] - 2019-04-19 ### Changes diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 7b138dbaa..1164205ea 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -113,31 +113,31 @@ where match self.1.as_ref().borrow_mut().acquire(&key) { Acquire::Acquired(io, created) => { // use existing connection - Either::A(ok(IoConnection::new( + return Either::A(ok(IoConnection::new( io, created, Some(Acquired(key, Some(self.1.clone()))), - ))) - } - Acquire::NotAvailable => { - // connection is not available, wait - let (rx, token) = self.1.as_ref().borrow_mut().wait_for(req); - Either::B(Either::A(WaitForConnection { - rx, - key, - token, - inner: Some(self.1.clone()), - })) + ))); } Acquire::Available => { // open new connection - Either::B(Either::B(OpenConnection::new( + return Either::B(Either::B(OpenConnection::new( key, self.1.clone(), self.0.call(req), - ))) + ))); } + _ => (), } + + // connection is not available, wait + let (rx, token) = self.1.as_ref().borrow_mut().wait_for(req); + Either::B(Either::A(WaitForConnection { + rx, + key, + token, + inner: Some(self.1.clone()), + })) } } From 5f6a1a82492d6fbc1915a4b0f98c7bbfb828e008 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 23 Apr 2019 09:45:39 -0700 Subject: [PATCH 117/122] update version --- actix-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index ac15667f7..17e99bca7 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.1.1" +version = "0.1.2" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" From d2b0afd859e61f48f1057f37d09661afa1be1c36 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 23 Apr 2019 14:57:03 -0700 Subject: [PATCH 118/122] Fix http client pool and wait queue management --- Cargo.toml | 4 +- actix-http/CHANGES.md | 9 ++ actix-http/src/client/connector.rs | 44 +++++-- actix-http/src/client/h1proto.rs | 2 +- actix-http/src/client/pool.rs | 205 +++++++++++++++++++++++++---- awc/Cargo.toml | 6 +- awc/tests/test_client.rs | 201 ++++++++++++++++++++++++++-- test-server/src/lib.rs | 11 ++ 8 files changed, 430 insertions(+), 52 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 74fe78b4f..a886b5fcc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,7 @@ actix-utils = "0.3.4" actix-router = "0.1.3" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-beta.1" -actix-http = { version = "0.1.1", features=["fail"] } +actix-http = { version = "0.1.2", features=["fail"] } actix-server = "0.4.3" actix-server-config = "0.1.1" actix-threadpool = "0.1.0" @@ -98,7 +98,7 @@ openssl = { version="0.10", optional = true } rustls = { version = "^0.15", optional = true } [dev-dependencies] -actix-http = { version = "0.1.1", features=["ssl", "brotli", "flate2-zlib"] } +actix-http = { version = "0.1.2", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.1.0", features=["ssl"] } actix-files = { version = "0.1.0-beta.1" } rand = "0.6" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 2edcceeb0..37d0eec65 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,14 @@ # Changes +## [0.1.3] - 2019-04-23 + +### Fixed + +* Fix http client pool management + +* Fix http client wait queue management #794 + + ## [0.1.2] - 2019-04-23 ### Fixed diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 14df2eee8..0241e8472 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -114,7 +114,8 @@ where Request = TcpConnect, Response = TcpConnection, Error = actix_connect::ConnectError, - > + Clone, + > + Clone + + 'static, { /// Connection timeout, i.e. max time to connect to remote host including dns name resolution. /// Set to 1 second by default. @@ -284,7 +285,9 @@ mod connect_impl { pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service + + Clone + + 'static, { pub(crate) tcp_pool: ConnectionPool, } @@ -293,7 +296,8 @@ mod connect_impl { where Io: AsyncRead + AsyncWrite + 'static, T: Service - + Clone, + + Clone + + 'static, { fn clone(&self) -> Self { InnerConnector { @@ -305,7 +309,9 @@ mod connect_impl { impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service + + Clone + + 'static, { type Request = Connect; type Response = IoConnection; @@ -356,9 +362,11 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service - + Clone, + + Clone + + 'static, T2: Service - + Clone, + + Clone + + 'static, { fn clone(&self) -> Self { InnerConnector { @@ -372,8 +380,12 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service, - T2: Service, + T1: Service + + Clone + + 'static, + T2: Service + + Clone + + 'static, { type Request = Connect; type Response = EitherConnection; @@ -409,7 +421,9 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseA where Io1: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service + + Clone + + 'static, { fut: as Service>::Future, _t: PhantomData, @@ -417,7 +431,9 @@ mod connect_impl { impl Future for InnerConnectorResponseA where - T: Service, + T: Service + + Clone + + 'static, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { @@ -435,7 +451,9 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseB where Io2: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service + + Clone + + 'static, { fut: as Service>::Future, _t: PhantomData, @@ -443,7 +461,9 @@ mod connect_impl { impl Future for InnerConnectorResponseB where - T: Service, + T: Service + + Clone + + 'static, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index becc07528..97ed3bbc7 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -274,7 +274,7 @@ impl Stream for PlStream { Ok(Async::Ready(Some(chunk))) } else { let framed = self.framed.take().unwrap(); - let force_close = framed.get_codec().keepalive(); + let force_close = !framed.get_codec().keepalive(); release_connection(framed, force_close); Ok(Async::Ready(None)) } diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 1164205ea..8dedf72f5 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -49,7 +49,9 @@ pub(crate) struct ConnectionPool( impl ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service + + Clone + + 'static, { pub(crate) fn new( connector: T, @@ -69,7 +71,7 @@ where waiters: Slab::new(), waiters_queue: IndexSet::new(), available: HashMap::new(), - task: AtomicTask::new(), + task: None, })), ) } @@ -88,7 +90,9 @@ where impl Service for ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service + + Clone + + 'static, { type Request = Connect; type Response = IoConnection; @@ -131,7 +135,17 @@ where } // connection is not available, wait - let (rx, token) = self.1.as_ref().borrow_mut().wait_for(req); + let (rx, token, support) = self.1.as_ref().borrow_mut().wait_for(req); + + // start support future + if !support { + self.1.as_ref().borrow_mut().task = Some(AtomicTask::new()); + tokio_current_thread::spawn(ConnectorPoolSupport { + connector: self.0.clone(), + inner: self.1.clone(), + }) + } + Either::B(Either::A(WaitForConnection { rx, key, @@ -245,7 +259,7 @@ where Ok(Async::Ready(IoConnection::new( ConnectionType::H2(snd), Instant::now(), - Some(Acquired(self.key.clone(), self.inner.clone())), + Some(Acquired(self.key.clone(), self.inner.take())), ))) } Ok(Async::NotReady) => Ok(Async::NotReady), @@ -256,12 +270,11 @@ where match self.fut.poll() { Err(err) => Err(err), Ok(Async::Ready((io, proto))) => { - let _ = self.inner.take(); if proto == Protocol::Http1 { Ok(Async::Ready(IoConnection::new( ConnectionType::H1(io), Instant::now(), - Some(Acquired(self.key.clone(), self.inner.clone())), + Some(Acquired(self.key.clone(), self.inner.take())), ))) } else { self.h2 = Some(handshake(io)); @@ -279,7 +292,6 @@ enum Acquire { NotAvailable, } -// #[derive(Debug)] struct AvailableConnection { io: ConnectionType, used: Instant, @@ -298,7 +310,7 @@ pub(crate) struct Inner { oneshot::Sender, ConnectError>>, )>, waiters_queue: IndexSet<(Key, usize)>, - task: AtomicTask, + task: Option, } impl Inner { @@ -314,18 +326,6 @@ impl Inner { self.waiters.remove(token); self.waiters_queue.remove(&(key.clone(), token)); } - - fn release_conn(&mut self, key: &Key, io: ConnectionType, created: Instant) { - self.acquired -= 1; - self.available - .entry(key.clone()) - .or_insert_with(VecDeque::new) - .push_back(AvailableConnection { - io, - created, - used: Instant::now(), - }); - } } impl Inner @@ -339,6 +339,7 @@ where ) -> ( oneshot::Receiver, ConnectError>>, usize, + bool, ) { let (tx, rx) = oneshot::channel(); @@ -346,8 +347,9 @@ where let entry = self.waiters.vacant_entry(); let token = entry.key(); entry.insert((connect, tx)); - assert!(!self.waiters_queue.insert((key, token))); - (rx, token) + assert!(self.waiters_queue.insert((key, token))); + + (rx, token, self.task.is_some()) } fn acquire(&mut self, key: &Key) -> Acquire { @@ -400,6 +402,19 @@ where Acquire::Available } + fn release_conn(&mut self, key: &Key, io: ConnectionType, created: Instant) { + self.acquired -= 1; + self.available + .entry(key.clone()) + .or_insert_with(VecDeque::new) + .push_back(AvailableConnection { + io, + created, + used: Instant::now(), + }); + self.check_availibility(); + } + fn release_close(&mut self, io: ConnectionType) { self.acquired -= 1; if let Some(timeout) = self.disconnect_timeout { @@ -407,11 +422,12 @@ where tokio_current_thread::spawn(CloseConnection::new(io, timeout)) } } + self.check_availibility(); } fn check_availibility(&self) { if !self.waiters_queue.is_empty() && self.acquired < self.limit { - self.task.notify() + self.task.as_ref().map(|t| t.notify()); } } } @@ -451,6 +467,147 @@ where } } +struct ConnectorPoolSupport +where + Io: AsyncRead + AsyncWrite + 'static, +{ + connector: T, + inner: Rc>>, +} + +impl Future for ConnectorPoolSupport +where + Io: AsyncRead + AsyncWrite + 'static, + T: Service, + T::Future: 'static, +{ + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + let mut inner = self.inner.as_ref().borrow_mut(); + inner.task.as_ref().unwrap().register(); + + // check waiters + loop { + let (key, token) = { + if let Some((key, token)) = inner.waiters_queue.get_index(0) { + (key.clone(), *token) + } else { + break; + } + }; + match inner.acquire(&key) { + Acquire::NotAvailable => break, + Acquire::Acquired(io, created) => { + let (_, tx) = inner.waiters.remove(token); + if let Err(conn) = tx.send(Ok(IoConnection::new( + io, + created, + Some(Acquired(key.clone(), Some(self.inner.clone()))), + ))) { + let (io, created) = conn.unwrap().into_inner(); + inner.release_conn(&key, io, created); + } + } + Acquire::Available => { + let (connect, tx) = inner.waiters.remove(token); + OpenWaitingConnection::spawn( + key.clone(), + tx, + self.inner.clone(), + self.connector.call(connect), + ); + } + } + let _ = inner.waiters_queue.swap_remove_index(0); + } + + Ok(Async::NotReady) + } +} + +struct OpenWaitingConnection +where + Io: AsyncRead + AsyncWrite + 'static, +{ + fut: F, + key: Key, + h2: Option>, + rx: Option, ConnectError>>>, + inner: Option>>>, +} + +impl OpenWaitingConnection +where + F: Future + 'static, + Io: AsyncRead + AsyncWrite + 'static, +{ + fn spawn( + key: Key, + rx: oneshot::Sender, ConnectError>>, + inner: Rc>>, + fut: F, + ) { + tokio_current_thread::spawn(OpenWaitingConnection { + key, + fut, + h2: None, + rx: Some(rx), + inner: Some(inner), + }) + } +} + +impl Drop for OpenWaitingConnection +where + Io: AsyncRead + AsyncWrite + 'static, +{ + fn drop(&mut self) { + if let Some(inner) = self.inner.take() { + let mut inner = inner.as_ref().borrow_mut(); + inner.release(); + inner.check_availibility(); + } + } +} + +impl Future for OpenWaitingConnection +where + F: Future, + Io: AsyncRead + AsyncWrite, +{ + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + match self.fut.poll() { + Err(err) => { + let _ = self.inner.take(); + if let Some(rx) = self.rx.take() { + let _ = rx.send(Err(err)); + } + Err(()) + } + Ok(Async::Ready((io, proto))) => { + if proto == Protocol::Http1 { + let rx = self.rx.take().unwrap(); + let _ = rx.send(Ok(IoConnection::new( + ConnectionType::H1(io), + Instant::now(), + Some(Acquired(self.key.clone(), self.inner.take())), + ))); + Ok(Async::Ready(())) + } else { + self.h2 = Some(handshake(io)); + self.poll() + } + } + Ok(Async::NotReady) => Ok(Async::NotReady), + } + } +} + pub(crate) struct Acquired(Key, Option>>>); impl Acquired diff --git a/awc/Cargo.toml b/awc/Cargo.toml index a254d69c6..e6018f44f 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -38,7 +38,7 @@ flate2-rust = ["actix-http/flate2-rust"] [dependencies] actix-codec = "0.1.2" actix-service = "0.3.6" -actix-http = "0.1.1" +actix-http = "0.1.2" base64 = "0.10.1" bytes = "0.4" derive_more = "0.14" @@ -55,8 +55,8 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.2" -actix-web = { version = "1.0.0-alpha.6", features=["ssl"] } -actix-http = { version = "0.1.1", features=["ssl"] } +actix-web = { version = "1.0.0-beta.1", features=["ssl"] } +actix-http = { version = "0.1.2", features=["ssl"] } actix-http-test = { version = "0.1.0", features=["ssl"] } actix-utils = "0.3.4" actix-server = { version = "0.4.3", features=["ssl"] } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index afccdff86..d1139fdc5 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -1,5 +1,7 @@ use std::collections::HashMap; use std::io::{Read, Write}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; use std::time::Duration; use brotli2::write::BrotliEncoder; @@ -7,11 +9,12 @@ use bytes::Bytes; use flate2::read::GzDecoder; use flate2::write::GzEncoder; use flate2::Compression; -use futures::future::Future; +use futures::Future; use rand::Rng; use actix_http::HttpService; use actix_http_test::TestServer; +use actix_service::{fn_service, NewService}; use actix_web::http::Cookie; use actix_web::middleware::{BodyEncoding, Compress}; use actix_web::{http::header, web, App, Error, HttpMessage, HttpRequest, HttpResponse}; @@ -144,17 +147,195 @@ fn test_timeout_override() { } #[test] -fn test_connection_close() { - let mut srv = TestServer::new(|| { - HttpService::new( - App::new().service(web::resource("/").to(|| HttpResponse::Ok())), - ) +fn test_connection_reuse() { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); + + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + fn_service(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(HttpService::new( + App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok()))), + )) }); - let res = srv - .block_on(awc::Client::new().get(srv.url("/")).force_close().send()) - .unwrap(); - assert!(res.status().is_success()); + let client = awc::Client::default(); + + // req 1 + let request = client.get(srv.url("/")).send(); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + + // req 2 + let req = client.post(srv.url("/")); + let response = srv.block_on_fn(move || req.send()).unwrap(); + assert!(response.status().is_success()); + + // one connection + assert_eq!(num.load(Ordering::Relaxed), 1); +} + +#[test] +fn test_connection_force_close() { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); + + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + fn_service(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(HttpService::new( + App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok()))), + )) + }); + + let client = awc::Client::default(); + + // req 1 + let request = client.get(srv.url("/")).force_close().send(); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + + // req 2 + let req = client.post(srv.url("/")).force_close(); + let response = srv.block_on_fn(move || req.send()).unwrap(); + assert!(response.status().is_success()); + + // two connection + assert_eq!(num.load(Ordering::Relaxed), 2); +} + +#[test] +fn test_connection_server_close() { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); + + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + fn_service(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(HttpService::new( + App::new().service( + web::resource("/") + .route(web::to(|| HttpResponse::Ok().force_close().finish())), + ), + )) + }); + + let client = awc::Client::default(); + + // req 1 + let request = client.get(srv.url("/")).send(); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + + // req 2 + let req = client.post(srv.url("/")); + let response = srv.block_on_fn(move || req.send()).unwrap(); + assert!(response.status().is_success()); + + // two connection + assert_eq!(num.load(Ordering::Relaxed), 2); +} + +#[test] +fn test_connection_wait_queue() { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); + + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + fn_service(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(HttpService::new(App::new().service( + web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))), + ))) + }); + + let client = awc::Client::build() + .connector(awc::Connector::new().limit(1).finish()) + .finish(); + + // req 1 + let request = client.get(srv.url("/")).send(); + let mut response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + + // req 2 + let req2 = client.post(srv.url("/")); + let req2_fut = srv.execute(move || { + let mut fut = req2.send(); + assert!(fut.poll().unwrap().is_not_ready()); + fut + }); + + // read response 1 + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + + // req 2 + let response = srv.block_on(req2_fut).unwrap(); + assert!(response.status().is_success()); + + // two connection + assert_eq!(num.load(Ordering::Relaxed), 1); +} + +#[test] +fn test_connection_wait_queue_force_close() { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); + + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + fn_service(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(HttpService::new( + App::new().service( + web::resource("/") + .route(web::to(|| HttpResponse::Ok().force_close().body(STR))), + ), + )) + }); + + let client = awc::Client::build() + .connector(awc::Connector::new().limit(1).finish()) + .finish(); + + // req 1 + let request = client.get(srv.url("/")).send(); + let mut response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + + // req 2 + let req2 = client.post(srv.url("/")); + let req2_fut = srv.execute(move || { + let mut fut = req2.send(); + assert!(fut.poll().unwrap().is_not_ready()); + fut + }); + + // read response 1 + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + + // req 2 + let response = srv.block_on(req2_fut).unwrap(); + assert!(response.status().is_success()); + + // two connection + assert_eq!(num.load(Ordering::Relaxed), 2); } #[test] diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 37abe1292..42d07549d 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -124,6 +124,7 @@ impl TestServer { |e| log::error!("Can not set alpn protocol: {:?}", e), ); Connector::new() + .conn_lifetime(time::Duration::from_secs(0)) .timeout(time::Duration::from_millis(500)) .ssl(builder.build()) .finish() @@ -131,6 +132,7 @@ impl TestServer { #[cfg(not(feature = "ssl"))] { Connector::new() + .conn_lifetime(time::Duration::from_secs(0)) .timeout(time::Duration::from_millis(500)) .finish() } @@ -163,6 +165,15 @@ impl TestServerRuntime { self.rt.block_on(fut) } + /// Execute future on current core + pub fn block_on_fn(&mut self, f: F) -> Result + where + F: FnOnce() -> R, + R: Future, + { + self.rt.block_on(lazy(|| f())) + } + /// Execute function on current core pub fn execute(&mut self, fut: F) -> R where From 9702b2d88edec888ec88bdfa4736bc0c99971860 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 23 Apr 2019 15:06:30 -0700 Subject: [PATCH 119/122] add client h2 reuse test --- actix-http/Cargo.toml | 2 +- awc/tests/test_client.rs | 81 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 17e99bca7..438754b3f 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.1.2" +version = "0.1.3" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index d1139fdc5..7e2dc6ba4 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -12,10 +12,11 @@ use flate2::Compression; use futures::Future; use rand::Rng; +use actix_codec::{AsyncRead, AsyncWrite}; use actix_http::HttpService; use actix_http_test::TestServer; use actix_service::{fn_service, NewService}; -use actix_web::http::Cookie; +use actix_web::http::{Cookie, Version}; use actix_web::middleware::{BodyEncoding, Compress}; use actix_web::{http::header, web, App, Error, HttpMessage, HttpRequest, HttpResponse}; use awc::error::SendRequestError; @@ -42,6 +43,30 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; +#[cfg(feature = "ssl")] +fn ssl_acceptor( +) -> std::io::Result> { + use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; + // load ssl keys + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); + builder + .set_private_key_file("../tests/key.pem", SslFiletype::PEM) + .unwrap(); + builder + .set_certificate_chain_file("../tests/cert.pem") + .unwrap(); + builder.set_alpn_select_callback(|_, protos| { + const H2: &[u8] = b"\x02h2"; + if protos.windows(3).any(|window| window == H2) { + Ok(b"h2") + } else { + Err(openssl::ssl::AlpnError::NOACK) + } + }); + builder.set_alpn_protos(b"\x02h2")?; + Ok(actix_server::ssl::OpensslAcceptor::new(builder.build())) +} + #[test] fn test_simple() { let mut srv = @@ -178,6 +203,60 @@ fn test_connection_reuse() { assert_eq!(num.load(Ordering::Relaxed), 1); } +#[cfg(feature = "ssl")] +#[test] +fn test_connection_reuse_h2() { + let openssl = ssl_acceptor().unwrap(); + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); + + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + fn_service(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(App::new() + .service(web::resource("/").route(web::to(|| HttpResponse::Ok())))) + .map_err(|_| ()), + ) + }); + + // disable ssl verification + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let _ = builder + .set_alpn_protos(b"\x02h2\x08http/1.1") + .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); + + let client = awc::Client::build() + .connector(awc::Connector::new().ssl(builder.build()).finish()) + .finish(); + + // req 1 + let request = client.get(srv.surl("/")).send(); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + + // req 2 + let req = client.post(srv.surl("/")); + let response = srv.block_on_fn(move || req.send()).unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), Version::HTTP_2); + + // one connection + assert_eq!(num.load(Ordering::Relaxed), 1); +} + #[test] fn test_connection_force_close() { let num = Arc::new(AtomicUsize::new(0)); From 898ef570803c983aaf92e2d3a0ecf7e5d278cbb6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 23 Apr 2019 21:21:49 -0700 Subject: [PATCH 120/122] Fix async web::Data factory handling --- CHANGES.md | 5 +++++ src/app.rs | 27 +++++++++++++++++++++++++-- src/app_service.rs | 2 +- src/config.rs | 41 ++++++++++++++++++++++++++++++++++++++--- 4 files changed, 69 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f4fdd6af7..565362263 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,11 @@ * `.to_async()` handler can return `Responder` type #792 +### Fixed + +* Fix async web::Data factory handling + + ## [1.0.0-beta.1] - 2019-04-20 ### Added diff --git a/src/app.rs b/src/app.rs index c0bbc8b2b..bb6d2aef8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -431,13 +431,14 @@ where #[cfg(test)] mod tests { use actix_service::Service; + use bytes::Bytes; use futures::{Future, IntoFuture}; use super::*; use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::service::{ServiceRequest, ServiceResponse}; - use crate::test::{block_on, call_service, init_service, TestRequest}; - use crate::{web, Error, HttpResponse}; + use crate::test::{block_on, call_service, init_service, read_body, TestRequest}; + use crate::{web, Error, HttpRequest, HttpResponse}; #[test] fn test_default_resource() { @@ -598,4 +599,26 @@ mod tests { HeaderValue::from_static("0001") ); } + + #[test] + fn test_external_resource() { + let mut srv = init_service( + App::new() + .external_resource("youtube", "https://youtube.com/watch/{video_id}") + .route( + "/test", + web::get().to(|req: HttpRequest| { + HttpResponse::Ok().body(format!( + "{}", + req.url_for("youtube", &["12345"]).unwrap() + )) + }), + ), + ); + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + let body = read_body(resp); + assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); + } } diff --git a/src/app_service.rs b/src/app_service.rs index 63bf84e76..7229a2301 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -162,7 +162,7 @@ where } } - if self.endpoint.is_some() { + if self.endpoint.is_some() && self.data.is_empty() { Ok(Async::Ready(AppInitService { service: self.endpoint.take().unwrap(), rmap: self.rmap.clone(), diff --git a/src/config.rs b/src/config.rs index a8caba4d2..4c4bfa220 100644 --- a/src/config.rs +++ b/src/config.rs @@ -253,11 +253,14 @@ impl ServiceConfig { #[cfg(test)] mod tests { use actix_service::Service; + use bytes::Bytes; + use futures::Future; + use tokio_timer::sleep; use super::*; use crate::http::{Method, StatusCode}; - use crate::test::{block_on, call_service, init_service, TestRequest}; - use crate::{web, App, HttpResponse}; + use crate::test::{block_on, call_service, init_service, read_body, TestRequest}; + use crate::{web, App, HttpRequest, HttpResponse}; #[test] fn test_data() { @@ -277,7 +280,12 @@ mod tests { #[test] fn test_data_factory() { let cfg = |cfg: &mut ServiceConfig| { - cfg.data_factory(|| Ok::<_, ()>(10usize)); + cfg.data_factory(|| { + sleep(std::time::Duration::from_millis(50)).then(|_| { + println!("READY"); + Ok::<_, ()>(10usize) + }) + }); }; let mut srv = @@ -301,6 +309,33 @@ mod tests { assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } + #[test] + fn test_external_resource() { + let mut srv = init_service( + App::new() + .configure(|cfg| { + cfg.external_resource( + "youtube", + "https://youtube.com/watch/{video_id}", + ); + }) + .route( + "/test", + web::get().to(|req: HttpRequest| { + HttpResponse::Ok().body(format!( + "{}", + req.url_for("youtube", &["12345"]).unwrap() + )) + }), + ), + ); + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + let body = read_body(resp); + assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); + } + #[test] fn test_service() { let mut srv = init_service(App::new().configure(|cfg| { From 42644dac3f2827ac290d9f1e64591c35dbce395f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 24 Apr 2019 07:31:33 -0700 Subject: [PATCH 121/122] prepare actix-http-test release --- Cargo.toml | 2 +- test-server/CHANGES.md | 5 +++++ test-server/Cargo.toml | 8 ++++---- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a886b5fcc..f4720a1c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,7 +99,7 @@ rustls = { version = "^0.15", optional = true } [dev-dependencies] actix-http = { version = "0.1.2", features=["ssl", "brotli", "flate2-zlib"] } -actix-http-test = { version = "0.1.0", features=["ssl"] } +actix-http-test = { version = "0.1.1", features=["ssl"] } actix-files = { version = "0.1.0-beta.1" } rand = "0.6" env_logger = "0.6" diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md index cec01fde6..700b3aa1f 100644 --- a/test-server/CHANGES.md +++ b/test-server/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## [0.1.1] - 2019-04-24 + +* Always make new connection for http client + + ## [0.1.0] - 2019-04-16 * No changes diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 657dd2615..906c9d389 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "0.1.0" +version = "0.1.1" authors = ["Nikolay Kim "] description = "Actix http test server" readme = "README.md" @@ -35,7 +35,7 @@ actix-rt = "0.2.2" actix-service = "0.3.6" actix-server = "0.4.3" actix-utils = "0.3.5" -awc = "0.1.0" +awc = "0.1.1" base64 = "0.10" bytes = "0.4" @@ -55,5 +55,5 @@ tokio-timer = "0.2" openssl = { version="0.10", optional = true } [dev-dependencies] -actix-web = "1.0.0-alpha.5" -actix-http = "0.1.0" +actix-web = "1.0.0-beta.1" +actix-http = "0.1.2" From 679d1cd51360f62fe5f0084893591b6003671091 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 24 Apr 2019 10:25:46 -0700 Subject: [PATCH 122/122] allow to override responder's status code and headers --- CHANGES.md | 2 + src/responder.rs | 193 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 193 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 565362263..81be2a344 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,8 @@ ### Added +* Extend `Responder` trait, allow to override status code and headers. + * Add helper functions for reading response body `test::read_body()` * Added support for `remainder match` (i.e "/path/{tail}*") diff --git a/src/responder.rs b/src/responder.rs index 103009e75..f7f2a8b3a 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -1,8 +1,12 @@ use actix_http::error::InternalError; -use actix_http::{http::StatusCode, Error, Response, ResponseBuilder}; +use actix_http::http::{ + header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, HttpTryFrom, + StatusCode, +}; +use actix_http::{Error, Response, ResponseBuilder}; use bytes::{Bytes, BytesMut}; use futures::future::{err, ok, Either as EitherFuture, FutureResult}; -use futures::{Future, IntoFuture, Poll}; +use futures::{try_ready, Async, Future, IntoFuture, Poll}; use crate::request::HttpRequest; @@ -18,6 +22,51 @@ pub trait Responder { /// Convert itself to `AsyncResult` or `Error`. fn respond_to(self, req: &HttpRequest) -> Self::Future; + + /// Override a status code for a responder. + /// + /// ```rust + /// use actix_web::{HttpRequest, Responder, http::StatusCode}; + /// + /// fn index(req: HttpRequest) -> impl Responder { + /// "Welcome!".with_status(StatusCode::OK) + /// } + /// # fn main() {} + /// ``` + fn with_status(self, status: StatusCode) -> CustomResponder + where + Self: Sized, + { + CustomResponder::new(self).with_status(status) + } + + /// Add extra header to the responder's response. + /// + /// ```rust + /// use actix_web::{web, HttpRequest, Responder}; + /// use serde::Serialize; + /// + /// #[derive(Serialize)] + /// struct MyObj { + /// name: String, + /// } + /// + /// fn index(req: HttpRequest) -> impl Responder { + /// web::Json( + /// MyObj{name: "Name".to_string()} + /// ) + /// .with_header("x-version", "1.2.3") + /// } + /// # fn main() {} + /// ``` + fn with_header(self, key: K, value: V) -> CustomResponder + where + Self: Sized, + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + CustomResponder::new(self).with_header(key, value) + } } impl Responder for Response { @@ -154,6 +203,117 @@ impl Responder for BytesMut { } } +/// Allows to override status code and headers for a responder. +pub struct CustomResponder { + responder: T, + status: Option, + headers: Option, + error: Option, +} + +impl CustomResponder { + fn new(responder: T) -> Self { + CustomResponder { + responder, + status: None, + headers: None, + error: None, + } + } + + /// Override a status code for the responder's response. + /// + /// ```rust + /// use actix_web::{HttpRequest, Responder, http::StatusCode}; + /// + /// fn index(req: HttpRequest) -> impl Responder { + /// "Welcome!".with_status(StatusCode::OK) + /// } + /// # fn main() {} + /// ``` + pub fn with_status(mut self, status: StatusCode) -> Self { + self.status = Some(status); + self + } + + /// Add extra header to the responder's response. + /// + /// ```rust + /// use actix_web::{web, HttpRequest, Responder}; + /// use serde::Serialize; + /// + /// #[derive(Serialize)] + /// struct MyObj { + /// name: String, + /// } + /// + /// fn index(req: HttpRequest) -> impl Responder { + /// web::Json( + /// MyObj{name: "Name".to_string()} + /// ) + /// .with_header("x-version", "1.2.3") + /// } + /// # fn main() {} + /// ``` + pub fn with_header(mut self, key: K, value: V) -> Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + if self.headers.is_none() { + self.headers = Some(HeaderMap::new()); + } + + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + self.headers.as_mut().unwrap().append(key, value); + } + Err(e) => self.error = Some(e.into()), + }, + Err(e) => self.error = Some(e.into()), + }; + self + } +} + +impl Responder for CustomResponder { + type Error = T::Error; + type Future = CustomResponderFut; + + fn respond_to(self, req: &HttpRequest) -> Self::Future { + CustomResponderFut { + fut: self.responder.respond_to(req).into_future(), + status: self.status, + headers: self.headers, + } + } +} + +pub struct CustomResponderFut { + fut: ::Future, + status: Option, + headers: Option, +} + +impl Future for CustomResponderFut { + type Item = Response; + type Error = T::Error; + + fn poll(&mut self) -> Poll { + let mut res = try_ready!(self.fut.poll()); + if let Some(status) = self.status { + *res.status_mut() = status; + } + if let Some(ref headers) = self.headers { + for (k, v) in headers { + res.headers_mut().insert(k.clone(), v.clone()); + } + } + Ok(Async::Ready(res)) + } +} + /// Combines two different responder types into a single type /// /// ```rust @@ -435,4 +595,33 @@ pub(crate) mod tests { ); assert!(res.is_err()); } + + #[test] + fn test_custom_responder() { + let req = TestRequest::default().to_http_request(); + let res = block_on( + "test" + .to_string() + .with_status(StatusCode::BAD_REQUEST) + .respond_to(&req), + ) + .unwrap(); + assert_eq!(res.status(), StatusCode::BAD_REQUEST); + assert_eq!(res.body().bin_ref(), b"test"); + + let res = block_on( + "test" + .to_string() + .with_header("content-type", "json") + .respond_to(&req), + ) + .unwrap(); + + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(res.body().bin_ref(), b"test"); + assert_eq!( + res.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("json") + ); + } }