From d8f27e95a6a3f11a8dd6879779c537511e12bc40 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Dec 2017 10:17:15 -0800 Subject: [PATCH 001/279] added FromParam trait for path segment conversions, FramParam impl for PathBuf --- guide/src/qs_5.md | 66 ++++++++++- src/dev.rs | 2 +- src/error.rs | 59 +++++++--- src/h1.rs | 2 +- src/middlewares/session.rs | 4 +- src/recognizer.rs | 225 ++++++++++++++++++++++++++++--------- 6 files changed, 279 insertions(+), 79 deletions(-) diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index 157b55c57..adf96c903 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -60,17 +60,16 @@ Resource may have *variable path*also. For instance, a resource with the path '/a/{name}/c' would match all incoming requests with paths such as '/a/b/c', '/a/1/c', and '/a/etc/c'. -A *variable part*is specified in the form {identifier}, where the identifier can be +A *variable part* is specified in the form {identifier}, where the identifier can be used later in a request handler to access the matched value for that part. This is done by looking up the identifier in the `HttpRequest.match_info` object: - ```rust extern crate actix; use actix_web::*; fn index(req: Httprequest) -> String { - format!("Hello, {}", req.match_info.get('name').unwrap()) + format!("Hello, {}", req.match_info["name"]) } fn main() { @@ -92,8 +91,31 @@ fn main() { } ``` -To match path tail, `{tail:*}` pattern could be used. Tail pattern has to be last -segment in path otherwise it panics. +Any matched parameter can be deserialized into specific type if this type +implements `FromParam` trait. For example most of standard integer types +implements `FromParam` trait. i.e.: + +```rust +extern crate actix; +use actix_web::*; + +fn index(req: Httprequest) -> String { + let v1: u8 = req.match_info().query("v1")?; + let v2: u8 = req.match_info().query("v2")?; + format!("Values {} {}", v1, v2) +} + +fn main() { + Application::default("/") + .resource(r"/a/{v1}/{v2}/", |r| r.get(index)) + .finish(); +} +``` + +For this example for path '/a/1/2/', values v1 and v2 will resolve to "1" and "2". + +To match path tail, `{tail:*}` pattern could be used. Tail pattern must to be last +component of a path, any text after tail pattern will result in panic. ```rust,ignore fn main() { @@ -105,3 +127,37 @@ fn main() { Above example would match all incoming requests with path such as '/test/b/c', '/test/index.html', and '/test/etc/test'. + +It is possible to create a `PathBuf` from a tail path parameter. The returned `PathBuf` is +percent-decoded. If a segment is equal to "..", the previous segment (if +any) is skipped. + +For security purposes, if a segment meets any of the following conditions, +an `Err` is returned indicating the condition met: + + * Decoded segment starts with any of: `.` (except `..`), `*` + * Decoded segment ends with any of: `:`, `>`, `<` + * Decoded segment contains any of: `/` + * On Windows, decoded segment contains any of: '\' + * Percent-encoding results in invalid UTF8. + +As a result of these conditions, a `PathBuf` parsed from request path parameter is +safe to interpolate within, or use as a suffix of, a path without additional +checks. + +```rust +extern crate actix; +use actix_web::*; +use std::path::PathBuf; + +fn index(req: Httprequest) -> String { + let path: PathBuf = req.match_info().query("tail")?; + format!("Path {:?}", path) +} + +fn main() { + Application::default("/") + .resource(r"/a/{tail:**}", |r| r.get(index)) + .finish(); +} +``` diff --git a/src/dev.rs b/src/dev.rs index cb409ccc0..11484107f 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -11,8 +11,8 @@ // dev specific pub use pipeline::Pipeline; pub use route::Handler; -pub use recognizer::RouteRecognizer; pub use channel::{HttpChannel, HttpHandler}; +pub use recognizer::{FromParam, RouteRecognizer}; pub use application::ApplicationBuilder; pub use httpresponse::HttpResponseBuilder; diff --git a/src/error.rs b/src/error.rs index c6c4a7eb9..f9b1fca07 100644 --- a/src/error.rs +++ b/src/error.rs @@ -33,20 +33,20 @@ pub type Result = result::Result; /// General purpose actix web error #[derive(Debug)] pub struct Error { - cause: Box, + cause: Box, } impl Error { /// Returns a reference to the underlying cause of this Error. // this should return &Fail but needs this https://github.com/rust-lang/rust/issues/5665 - pub fn cause(&self) -> &ErrorResponse { + pub fn cause(&self) -> &ResponseError { self.cause.as_ref() } } /// Error that can be converted to `HttpResponse` -pub trait ErrorResponse: Fail { +pub trait ResponseError: Fail { /// Create response for error /// @@ -69,8 +69,8 @@ impl From for HttpResponse { } } -/// `Error` for any error that implements `ErrorResponse` -impl From for Error { +/// `Error` for any error that implements `ResponseError` +impl From for Error { fn from(err: T) -> Error { Error { cause: Box::new(err) } } @@ -78,31 +78,31 @@ impl From for Error { /// Default error is `InternalServerError` #[cfg(actix_nightly)] -default impl ErrorResponse for T { +default impl ResponseError for T { fn error_response(&self) -> HttpResponse { HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR, Body::Empty) } } /// `InternalServerError` for `JsonError` -impl ErrorResponse for JsonError {} +impl ResponseError for JsonError {} /// Return `InternalServerError` for `HttpError`, /// Response generation can return `HttpError`, so it is internal error -impl ErrorResponse for HttpError {} +impl ResponseError for HttpError {} /// Return `InternalServerError` for `io::Error` -impl ErrorResponse for IoError {} +impl ResponseError for IoError {} /// `InternalServerError` for `InvalidHeaderValue` -impl ErrorResponse for header::InvalidHeaderValue {} +impl ResponseError for header::InvalidHeaderValue {} /// Internal error #[derive(Fail, Debug)] #[fail(display="Unexpected task frame")] pub struct UnexpectedTaskFrame; -impl ErrorResponse for UnexpectedTaskFrame {} +impl ResponseError for UnexpectedTaskFrame {} /// A set of errors that can occur during parsing HTTP streams #[derive(Fail, Debug)] @@ -141,7 +141,7 @@ pub enum ParseError { } /// Return `BadRequest` for `ParseError` -impl ErrorResponse for ParseError { +impl ResponseError for ParseError { fn error_response(&self) -> HttpResponse { HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) } @@ -203,7 +203,7 @@ impl From for PayloadError { } /// Return `BadRequest` for `cookie::ParseError` -impl ErrorResponse for cookie::ParseError { +impl ResponseError for cookie::ParseError { fn error_response(&self) -> HttpResponse { HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) } @@ -223,7 +223,7 @@ pub enum HttpRangeError { } /// Return `BadRequest` for `HttpRangeError` -impl ErrorResponse for HttpRangeError { +impl ResponseError for HttpRangeError { fn error_response(&self) -> HttpResponse { HttpResponse::new( StatusCode::BAD_REQUEST, Body::from("Invalid Range header provided")) @@ -272,7 +272,7 @@ impl From for MultipartError { } /// Return `BadRequest` for `MultipartError` -impl ErrorResponse for MultipartError { +impl ResponseError for MultipartError { fn error_response(&self) -> HttpResponse { HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) @@ -290,7 +290,7 @@ pub enum ExpectError { UnknownExpect, } -impl ErrorResponse for ExpectError { +impl ResponseError for ExpectError { fn error_response(&self) -> HttpResponse { HTTPExpectationFailed.with_body("Unknown Expect") @@ -320,7 +320,7 @@ pub enum WsHandshakeError { BadWebsocketKey, } -impl ErrorResponse for WsHandshakeError { +impl ResponseError for WsHandshakeError { fn error_response(&self) -> HttpResponse { match *self { @@ -363,7 +363,30 @@ pub enum UrlencodedError { } /// Return `BadRequest` for `UrlencodedError` -impl ErrorResponse for UrlencodedError { +impl ResponseError for UrlencodedError { + + fn error_response(&self) -> HttpResponse { + HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) + } +} + +/// Errors which can occur when attempting to interpret a segment string as a +/// valid path segment. +#[derive(Fail, Debug, PartialEq)] +pub enum UriSegmentError { + /// The segment started with the wrapped invalid character. + #[fail(display="The segment started with the wrapped invalid character")] + BadStart(char), + /// The segment contained the wrapped invalid character. + #[fail(display="The segment contained the wrapped invalid character")] + BadChar(char), + /// The segment ended with the wrapped invalid character. + #[fail(display="The segment ended with the wrapped invalid character")] + BadEnd(char), +} + +/// Return `BadRequest` for `UriSegmentError` +impl ResponseError for UriSegmentError { fn error_response(&self) -> HttpResponse { HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) diff --git a/src/h1.rs b/src/h1.rs index ff358f154..1222eb618 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -19,7 +19,7 @@ use channel::HttpHandler; use h1writer::H1Writer; use httpcodes::HTTPNotFound; use httprequest::HttpRequest; -use error::{ParseError, PayloadError, ErrorResponse}; +use error::{ParseError, PayloadError, ResponseError}; use payload::{Payload, PayloadWriter, DEFAULT_BUFFER_SIZE}; const KEEPALIVE_PERIOD: u64 = 15; // seconds diff --git a/src/middlewares/session.rs b/src/middlewares/session.rs index 24ad9fefb..1ed02ee0c 100644 --- a/src/middlewares/session.rs +++ b/src/middlewares/session.rs @@ -13,7 +13,7 @@ use cookie::{CookieJar, Cookie, Key}; use futures::Future; use futures::future::{FutureResult, ok as FutOk, err as FutErr}; -use error::{Result, Error, ErrorResponse}; +use error::{Result, Error, ResponseError}; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middlewares::{Middleware, Started, Response}; @@ -177,7 +177,7 @@ pub enum CookieSessionError { Serialize(JsonError), } -impl ErrorResponse for CookieSessionError {} +impl ResponseError for CookieSessionError {} impl SessionImpl for CookieSession { diff --git a/src/recognizer.rs b/src/recognizer.rs index 17366bdb8..b5c8ea9ec 100644 --- a/src/recognizer.rs +++ b/src/recognizer.rs @@ -1,10 +1,180 @@ +use std; use std::rc::Rc; +use std::path::PathBuf; +use std::ops::Index; +use std::str::FromStr; use std::collections::HashMap; - use regex::{Regex, RegexSet, Captures}; +use error::{ResponseError, UriSegmentError}; + +/// A trait to abstract the idea of creating a new instance of a type from a path parameter. +pub trait FromParam: Sized { + /// The associated error which can be returned from parsing. + type Err: ResponseError; + + /// Parses a string `s` to return a value of this type. + fn from_param(s: &str) -> Result; +} + +/// Route match information +/// +/// If resource path contains variable patterns, `Params` stores this variables. +#[derive(Debug)] +pub struct Params { + text: String, + matches: Vec>, + names: Rc>, +} + +impl Params { + pub(crate) fn new(names: Rc>, + text: &str, + captures: &Captures) -> Self + { + Params { + names, + text: text.into(), + matches: captures + .iter() + .map(|capture| capture.map(|m| (m.start(), m.end()))) + .collect(), + } + } + + pub(crate) fn empty() -> Self + { + Params { + text: String::new(), + names: Rc::new(HashMap::new()), + matches: Vec::new(), + } + } + + /// Check if there are any matched patterns + pub fn is_empty(&self) -> bool { + self.names.is_empty() + } + + fn by_idx(&self, index: usize) -> Option<&str> { + self.matches + .get(index + 1) + .and_then(|m| m.map(|(start, end)| &self.text[start..end])) + } + + /// Get matched parameter by name without type conversion + pub fn get(&self, key: &str) -> Option<&str> { + self.names.get(key).and_then(|&i| self.by_idx(i - 1)) + } + + /// Get matched `FromParam` compatible parameter by name. + /// + /// If keyed parameter is not available empty string is used as default value. + /// + /// ```rust,ignore + /// fn index(req: HttpRequest) -> String { + /// let ivalue: isize = req.match_info().query()?; + /// format!("isuze value: {:?}", ivalue) + /// } + /// ``` + pub fn query(&self, key: &str) -> Result::Err> + { + if let Some(s) = self.get(key) { + T::from_param(s) + } else { + T::from_param("") + } + } +} + +impl<'a> Index<&'a str> for Params { + type Output = str; + + fn index(&self, name: &'a str) -> &str { + self.get(name).expect("Value for parameter is not available") + } +} + +/// Creates a `PathBuf` from a path parameter. The returned `PathBuf` is +/// percent-decoded. If a segment is equal to "..", the previous segment (if +/// any) is skipped. +/// +/// For security purposes, if a segment meets any of the following conditions, +/// an `Err` is returned indicating the condition met: +/// +/// * Decoded segment starts with any of: `.` (except `..`), `*` +/// * Decoded segment ends with any of: `:`, `>`, `<` +/// * Decoded segment contains any of: `/` +/// * On Windows, decoded segment contains any of: '\' +/// * Percent-encoding results in invalid UTF8. +/// +/// As a result of these conditions, a `PathBuf` parsed from request path parameter is +/// safe to interpolate within, or use as a suffix of, a path without additional +/// checks. +impl FromParam for PathBuf { + type Err = UriSegmentError; + + fn from_param(val: &str) -> Result { + let mut buf = PathBuf::new(); + for segment in val.split('/') { + if segment == ".." { + buf.pop(); + } else if segment.starts_with('.') { + return Err(UriSegmentError::BadStart('.')) + } else if segment.starts_with('*') { + return Err(UriSegmentError::BadStart('*')) + } else if segment.ends_with(':') { + return Err(UriSegmentError::BadEnd(':')) + } else if segment.ends_with('>') { + return Err(UriSegmentError::BadEnd('>')) + } else if segment.ends_with('<') { + return Err(UriSegmentError::BadEnd('<')) + } else if segment.contains('/') { + return Err(UriSegmentError::BadChar('/')) + } else if cfg!(windows) && segment.contains('\\') { + return Err(UriSegmentError::BadChar('\\')) + } else { + buf.push(segment) + } + } + + Ok(buf) + } +} + +macro_rules! FROM_STR { + ($type:ty) => { + impl FromParam for $type { + type Err = <$type as FromStr>::Err; + + fn from_param(val: &str) -> Result { + <$type as FromStr>::from_str(val) + } + } + } +} + +FROM_STR!(u8); +FROM_STR!(u16); +FROM_STR!(u32); +FROM_STR!(u64); +FROM_STR!(usize); +FROM_STR!(i8); +FROM_STR!(i16); +FROM_STR!(i32); +FROM_STR!(i64); +FROM_STR!(isize); +FROM_STR!(f32); +FROM_STR!(f64); +FROM_STR!(String); +FROM_STR!(std::net::IpAddr); +FROM_STR!(std::net::Ipv4Addr); +FROM_STR!(std::net::Ipv6Addr); +FROM_STR!(std::net::SocketAddr); +FROM_STR!(std::net::SocketAddrV4); +FROM_STR!(std::net::SocketAddrV6); + -#[doc(hidden)] pub struct RouteRecognizer { prefix: usize, patterns: RegexSet, @@ -173,56 +343,6 @@ fn parse(pattern: &str) -> String { re } -/// Route match information -/// -/// If resource path contains variable patterns, `Params` stores this variables. -#[derive(Debug)] -pub struct Params { - text: String, - matches: Vec>, - names: Rc>, -} - -impl Params { - pub(crate) fn new(names: Rc>, - text: &str, - captures: &Captures) -> Self - { - Params { - names, - text: text.into(), - matches: captures - .iter() - .map(|capture| capture.map(|m| (m.start(), m.end()))) - .collect(), - } - } - - pub(crate) fn empty() -> Self - { - Params { - text: String::new(), - names: Rc::new(HashMap::new()), - matches: Vec::new(), - } - } - - pub fn is_empty(&self) -> bool { - self.names.is_empty() - } - - fn by_idx(&self, index: usize) -> Option<&str> { - self.matches - .get(index + 1) - .and_then(|m| m.map(|(start, end)| &self.text[start..end])) - } - - /// Get matched parameter by name - pub fn get(&self, key: &str) -> Option<&str> { - self.names.get(key).and_then(|&i| self.by_idx(i - 1)) - } -} - #[cfg(test)] mod tests { use regex::Regex; @@ -249,6 +369,7 @@ mod tests { assert_eq!(*val, 2); assert!(!params.as_ref().unwrap().is_empty()); assert_eq!(params.as_ref().unwrap().get("val").unwrap(), "value"); + assert_eq!(¶ms.as_ref().unwrap()["val"], "value"); let (params, val) = rec.recognize("/name/value2/index.html").unwrap(); assert_eq!(*val, 3); From ebfd3ac2755e0607c73d95e87dcc3de7efada87a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Dec 2017 10:43:14 -0800 Subject: [PATCH 002/279] tests for PathBuf::from_param --- src/recognizer.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/recognizer.rs b/src/recognizer.rs index b5c8ea9ec..7aac944c7 100644 --- a/src/recognizer.rs +++ b/src/recognizer.rs @@ -129,8 +129,8 @@ impl FromParam for PathBuf { return Err(UriSegmentError::BadEnd('>')) } else if segment.ends_with('<') { return Err(UriSegmentError::BadEnd('<')) - } else if segment.contains('/') { - return Err(UriSegmentError::BadChar('/')) + } else if segment.is_empty() { + continue } else if cfg!(windows) && segment.contains('\\') { return Err(UriSegmentError::BadChar('\\')) } else { @@ -347,6 +347,20 @@ fn parse(pattern: &str) -> String { mod tests { use regex::Regex; use super::*; + use std::iter::FromIterator; + + #[test] + fn test_path_buf() { + assert_eq!(PathBuf::from_param("/test/.tt"), Err(UriSegmentError::BadStart('.'))); + assert_eq!(PathBuf::from_param("/test/*tt"), Err(UriSegmentError::BadStart('*'))); + assert_eq!(PathBuf::from_param("/test/tt:"), Err(UriSegmentError::BadEnd(':'))); + assert_eq!(PathBuf::from_param("/test/tt<"), Err(UriSegmentError::BadEnd('<'))); + assert_eq!(PathBuf::from_param("/test/tt>"), Err(UriSegmentError::BadEnd('>'))); + assert_eq!(PathBuf::from_param("/seg1/seg2/"), + Ok(PathBuf::from_iter(vec!["seg1", "seg2"]))); + assert_eq!(PathBuf::from_param("/seg1/../seg2/"), + Ok(PathBuf::from_iter(vec!["seg2"]))); + } #[test] fn test_recognizer() { From 0fc01c48d1dd4f4cd219630e278c41cb2237b789 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Dec 2017 11:03:41 -0800 Subject: [PATCH 003/279] return bad request for param parse error --- src/recognizer.rs | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/recognizer.rs b/src/recognizer.rs index 7aac944c7..9dec57354 100644 --- a/src/recognizer.rs +++ b/src/recognizer.rs @@ -4,8 +4,13 @@ use std::path::PathBuf; use std::ops::Index; use std::str::FromStr; use std::collections::HashMap; + +use failure::Fail; +use http::{StatusCode}; use regex::{Regex, RegexSet, Captures}; +use body::Body; +use httpresponse::HttpResponse; use error::{ResponseError, UriSegmentError}; /// A trait to abstract the idea of creating a new instance of a type from a path parameter. @@ -73,7 +78,7 @@ impl Params { /// /// ```rust,ignore /// fn index(req: HttpRequest) -> String { - /// let ivalue: isize = req.match_info().query()?; + /// let ivalue: isize = req.match_info().query("val")?; /// format!("isuze value: {:?}", ivalue) /// } /// ``` @@ -142,13 +147,32 @@ impl FromParam for PathBuf { } } +#[derive(Fail, Debug)] +#[fail(display="Error")] +pub struct BadRequest(T); + +impl BadRequest { + pub fn cause(&self) -> &T { + &self.0 + } +} + +impl ResponseError for BadRequest + where T: Send + Sync + std::fmt::Debug +std::fmt::Display + 'static, + BadRequest: Fail +{ + fn error_response(&self) -> HttpResponse { + HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) + } +} + macro_rules! FROM_STR { ($type:ty) => { impl FromParam for $type { - type Err = <$type as FromStr>::Err; + type Err = BadRequest<<$type as FromStr>::Err>; fn from_param(val: &str) -> Result { - <$type as FromStr>::from_str(val) + <$type as FromStr>::from_str(val).map_err(|e| BadRequest(e)) } } } From d0b9d9c1d6e87d360b821ebef9315ca2107aa23c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Dec 2017 11:41:20 -0800 Subject: [PATCH 004/279] content-encoding; try cargo tarpaulin --- .travis.yml | 29 ++++++++++++++++------------- guide/src/SUMMARY.md | 3 +-- guide/src/qs_7.md | 16 +++++++++++++++- guide/src/qs_8.md | 1 - 4 files changed, 32 insertions(+), 17 deletions(-) delete mode 100644 guide/src/qs_8.md diff --git a/.travis.yml b/.travis.yml index a1b3c431e..68d29e255 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,18 +49,21 @@ after_success: - | if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then - wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz && - tar xzf master.tar.gz && - cd kcov-master && - mkdir build && - cd build && - cmake .. && - make && - make install DESTDIR=../../kcov-build && - cd ../.. && - rm -rf kcov-master && - for file in target/debug/actix_web-*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done && - for file in target/debug/test_*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done && - bash <(curl -s https://codecov.io/bash) && + bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) + cargo tarpaulin --out Xml + bash <(curl -s https://codecov.io/bash) + #wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz && + #tar xzf master.tar.gz && + #cd kcov-master && + #mkdir build && + #cd build && + #cmake .. && + #make && + #make install DESTDIR=../../kcov-build && + #cd ../.. && + #rm -rf kcov-master && + #for file in target/debug/actix_web-*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done && + #for file in target/debug/test_*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done && + #bash <(curl -s https://codecov.io/bash) && echo "Uploaded code coverage" fi diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 93a265a42..4a820e160 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -6,8 +6,7 @@ - [Handler](./qs_4.md) - [Resources and Routes](./qs_5.md) - [Application state](./qs_6.md) -- [Request](./qs_7.md) -- [Response](./qs_8.md) +- [Request & Response](./qs_7.md) - [WebSockets](./qs_9.md) - [User sessions](./qs_10.md) - [Logging](./qs_11.md) diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 0d92e9fdd..b5b91d599 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -1 +1,15 @@ -# Request +# HttpRequest & HttpResponse + +## Content encoding + +Actix automatically *compress*/*decompress* payload. +Following encodings are supported: + + * Brotli + * Gzip + * Deflate + * Identity + + If request headers contains `Content-Encoding` header, request payload get decompressed + according to header value. Multiple codecs are not supported, i.e: `Content-Encoding: br, gzip`. + diff --git a/guide/src/qs_8.md b/guide/src/qs_8.md deleted file mode 100644 index 465fa3626..000000000 --- a/guide/src/qs_8.md +++ /dev/null @@ -1 +0,0 @@ -# Response From 29a26b3236306cae73e8f229f49f80289a78c4c7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Dec 2017 12:14:16 -0800 Subject: [PATCH 005/279] code cleanup --- src/application.rs | 5 ++--- src/error.rs | 6 ++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/application.rs b/src/application.rs index 4fe5f1dd8..e890787cb 100644 --- a/src/application.rs +++ b/src/application.rs @@ -244,9 +244,8 @@ impl ApplicationBuilder where S: 'static { routes.push((path, handler)) } - for (path, mut handler) in parts.handlers { - let path = prefix.clone() + path.trim_left_matches('/'); - handlers.insert(path, handler); + for (path, handler) in parts.handlers { + handlers.insert(prefix.clone() + path.trim_left_matches('/'), handler); } Application { state: Rc::new(parts.state), diff --git a/src/error.rs b/src/error.rs index f9b1fca07..0f03bcdd4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -168,10 +168,8 @@ impl From for ParseError { impl From for ParseError { fn from(err: httparse::Error) -> ParseError { match err { - httparse::Error::HeaderName | - httparse::Error::HeaderValue | - httparse::Error::NewLine | - httparse::Error::Token => ParseError::Header, + httparse::Error::HeaderName | httparse::Error::HeaderValue | + httparse::Error::NewLine | httparse::Error::Token => ParseError::Header, httparse::Error::Status => ParseError::Status, httparse::Error::TooManyHeaders => ParseError::TooLarge, httparse::Error::Version => ParseError::Version, From 187948ddd167d6337cb92638aa40f8ee1fb52373 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Dec 2017 14:58:22 -0800 Subject: [PATCH 006/279] error response for io::Error --- src/error.rs | 16 ++++++++++++++-- src/recognizer.rs | 2 +- src/staticfiles.rs | 22 +++------------------- 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/error.rs b/src/error.rs index 0f03bcdd4..606a92580 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,5 @@ //! Error and Result module -use std::{fmt, result}; +use std::{io, fmt, result}; use std::str::Utf8Error; use std::string::FromUtf8Error; use std::io::Error as IoError; @@ -92,7 +92,19 @@ impl ResponseError for JsonError {} impl ResponseError for HttpError {} /// Return `InternalServerError` for `io::Error` -impl ResponseError for IoError {} +impl ResponseError for io::Error { + + fn error_response(&self) -> HttpResponse { + match self.kind() { + io::ErrorKind::NotFound => + HttpResponse::new(StatusCode::NOT_FOUND, Body::Empty), + io::ErrorKind::PermissionDenied => + HttpResponse::new(StatusCode::FORBIDDEN, Body::Empty), + _ => + HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR, Body::Empty) + } + } +} /// `InternalServerError` for `InvalidHeaderValue` impl ResponseError for header::InvalidHeaderValue {} diff --git a/src/recognizer.rs b/src/recognizer.rs index 9dec57354..7531b43aa 100644 --- a/src/recognizer.rs +++ b/src/recognizer.rs @@ -172,7 +172,7 @@ macro_rules! FROM_STR { type Err = BadRequest<<$type as FromStr>::Err>; fn from_param(val: &str) -> Result { - <$type as FromStr>::from_str(val).map_err(|e| BadRequest(e)) + <$type as FromStr>::from_str(val).map_err(BadRequest) } } } diff --git a/src/staticfiles.rs b/src/staticfiles.rs index 858ee5f6b..0618fa40c 100644 --- a/src/staticfiles.rs +++ b/src/staticfiles.rs @@ -11,7 +11,7 @@ use mime_guess::get_mime_type; use route::Handler; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use httpcodes::{HTTPOk, HTTPNotFound, HTTPForbidden}; +use httpcodes::{HTTPOk, HTTPNotFound}; /// Static files handling /// @@ -149,26 +149,10 @@ impl Handler for StaticFiles { // full filepath let idx = if filepath.starts_with('/') { 1 } else { 0 }; - let filename = match self.directory.join(&filepath[idx..]).canonicalize() { - Ok(fname) => fname, - Err(err) => return match err.kind() { - io::ErrorKind::NotFound => Ok(HTTPNotFound.into()), - io::ErrorKind::PermissionDenied => Ok(HTTPForbidden.into()), - _ => Err(err), - } - }; + let filename = self.directory.join(&filepath[idx..]).canonicalize()?; if filename.is_dir() { - match self.index( - &req.path()[..req.prefix_len()], &filepath[idx..], &filename) - { - Ok(resp) => Ok(resp), - Err(err) => match err.kind() { - io::ErrorKind::NotFound => Ok(HTTPNotFound.into()), - io::ErrorKind::PermissionDenied => Ok(HTTPForbidden.into()), - _ => Err(err), - } - } + self.index(&req.path()[..req.prefix_len()], &filepath[idx..], &filename) } else { let mut resp = HTTPOk.build(); if let Some(ext) = filename.extension() { From 61744b68a194702ead7878085153f086cdfcf151 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Dec 2017 16:37:21 -0800 Subject: [PATCH 007/279] introduce custom FromRequest traint for conversion into Reply --- src/application.rs | 4 +-- src/dev.rs | 2 +- src/httpcodes.rs | 9 +++-- src/httprequest.rs | 5 +++ src/resource.rs | 14 ++++---- src/route.rs | 87 ++++++++++++++++++++++++++++++++-------------- 6 files changed, 82 insertions(+), 39 deletions(-) diff --git a/src/application.rs b/src/application.rs index e890787cb..91514d151 100644 --- a/src/application.rs +++ b/src/application.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use futures::Future; use error::Error; -use route::{RouteHandler, Reply, Handler, WrapHandler, AsyncHandler}; +use route::{RouteHandler, Reply, Handler, FromRequest, WrapHandler, AsyncHandler}; use resource::Resource; use recognizer::{RouteRecognizer, check_pattern}; use httprequest::HttpRequest; @@ -190,7 +190,7 @@ impl ApplicationBuilder where S: 'static { pub fn handler(&mut self, path: P, handler: F) -> &mut Self where P: Into, F: Fn(HttpRequest) -> R + 'static, - R: Into + 'static + R: FromRequest + 'static { self.parts.as_mut().expect("Use after finish") .handlers.insert(path.into(), Box::new(WrapHandler::new(handler))); diff --git a/src/dev.rs b/src/dev.rs index 11484107f..537590651 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -10,7 +10,7 @@ // dev specific pub use pipeline::Pipeline; -pub use route::Handler; +pub use route::{Handler, FromRequest}; pub use channel::{HttpChannel, HttpHandler}; pub use recognizer::{FromParam, RouteRecognizer}; diff --git a/src/httpcodes.rs b/src/httpcodes.rs index 44415f5f3..013e16d39 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -3,8 +3,7 @@ use http::StatusCode; use body::Body; -use route::Reply; -use route::RouteHandler; +use route::{Reply, RouteHandler, FromRequest}; use httprequest::HttpRequest; use httpresponse::{HttpResponse, HttpResponseBuilder}; @@ -74,6 +73,12 @@ impl RouteHandler for StaticResponse { } } +impl FromRequest for StaticResponse { + fn from_request(self, _: HttpRequest) -> Reply { + Reply::response(HttpResponse::new(self.0, Body::Empty)) + } +} + impl From for HttpResponse { fn from(st: StaticResponse) -> Self { st.response() diff --git a/src/httprequest.rs b/src/httprequest.rs index 393330153..debfefe3b 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -83,6 +83,11 @@ impl HttpRequest<()> { impl HttpRequest { + /// Construct new http request without state. + pub fn clone_without_state(&self) -> HttpRequest { + HttpRequest(Rc::clone(&self.0), Rc::new(())) + } + /// get mutable reference for inner message fn as_mut(&mut self) -> &mut HttpMessage { let r: &HttpMessage = self.0.as_ref(); diff --git a/src/resource.rs b/src/resource.rs index 6e82b60ae..b13fda249 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -5,7 +5,7 @@ use http::Method; use futures::Future; use error::Error; -use route::{Reply, Handler, RouteHandler, AsyncHandler, WrapHandler}; +use route::{Reply, Handler, FromRequest, RouteHandler, AsyncHandler, WrapHandler}; use httprequest::HttpRequest; use httpresponse::HttpResponse; use httpcodes::{HTTPNotFound, HTTPMethodNotAllowed}; @@ -53,14 +53,14 @@ impl Resource where S: 'static { } /// Set resource name - pub fn set_name>(&mut self, name: T) { + pub fn name>(&mut self, name: T) { self.name = name.into(); } /// Register handler for specified method. pub fn handler(&mut self, method: Method, handler: F) where F: Fn(HttpRequest) -> R + 'static, - R: Into + 'static, + R: FromRequest + 'static, { self.routes.insert(method, Box::new(WrapHandler::new(handler))); } @@ -83,28 +83,28 @@ impl Resource where S: 'static { /// Register handler for `GET` method. pub fn get(&mut self, handler: F) where F: Fn(HttpRequest) -> R + 'static, - R: Into + 'static, { + R: FromRequest + 'static, { self.routes.insert(Method::GET, Box::new(WrapHandler::new(handler))); } /// Register handler for `POST` method. pub fn post(&mut self, handler: F) where F: Fn(HttpRequest) -> R + 'static, - R: Into + 'static, { + R: FromRequest + 'static, { self.routes.insert(Method::POST, Box::new(WrapHandler::new(handler))); } /// Register handler for `PUT` method. pub fn put(&mut self, handler: F) where F: Fn(HttpRequest) -> R + 'static, - R: Into + 'static, { + R: FromRequest + 'static, { self.routes.insert(Method::PUT, Box::new(WrapHandler::new(handler))); } /// Register handler for `DELETE` method. pub fn delete(&mut self, handler: F) where F: Fn(HttpRequest) -> R + 'static, - R: Into + 'static, { + R: FromRequest + 'static, { self.routes.insert(Method::DELETE, Box::new(WrapHandler::new(handler))); } } diff --git a/src/route.rs b/src/route.rs index 1550751c2..1ab32a56f 100644 --- a/src/route.rs +++ b/src/route.rs @@ -14,16 +14,20 @@ use httpresponse::HttpResponse; pub trait Handler: 'static { /// The type of value that handler will return. - type Result: Into; + type Result: FromRequest; /// Handle request fn handle(&self, req: HttpRequest) -> Self::Result; } +pub trait FromRequest { + fn from_request(self, req: HttpRequest) -> Reply; +} + /// Handler for Fn() impl Handler for F where F: Fn(HttpRequest) -> R + 'static, - R: Into + 'static + R: FromRequest + 'static { type Result = R; @@ -67,28 +71,41 @@ impl Reply { } } -#[cfg(not(actix_nightly))] -impl> From for Reply -{ - fn from(item: T) -> Self { - Reply(ReplyItem::Message(item.into())) +impl FromRequest for Reply { + fn from_request(self, _: HttpRequest) -> Reply { + self + } +} + +impl FromRequest for HttpResponse { + fn from_request(self, _: HttpRequest) -> Reply { + Reply(ReplyItem::Message(self)) } } #[cfg(actix_nightly)] -default impl> From for Reply +default impl FromRequest for T { - fn from(item: T) -> Self { - Reply(ReplyItem::Message(item.into())) + fn from_request(self, req: HttpRequest) -> Reply { + self.from_request(req) } } #[cfg(actix_nightly)] -default impl, E: Into> From> for Reply { - fn from(res: StdResult) -> Self { - match res { - Ok(val) => val.into().into(), - Err(err) => err.into().into(), +default impl> FromRequest for StdResult { + fn from_request(self, req: HttpRequest) -> Reply { + match self { + Ok(val) => val.from_request(req), + Err(err) => Reply(ReplyItem::Message(err.into().into())), + } + } +} + +impl> FromRequest for StdResult { + fn from_request(self, _: HttpRequest) -> Reply { + match self { + Ok(val) => val, + Err(err) => Reply(ReplyItem::Message(err.into().into())), } } } @@ -97,22 +114,37 @@ impl> From> for Reply { fn from(res: StdResult) -> Self { match res { Ok(val) => val, - Err(err) => err.into().into(), + Err(err) => Reply(ReplyItem::Message(err.into().into())), } } } -impl>, S: 'static> From> for Reply -{ - fn from(item: HttpContext) -> Self { - Reply(ReplyItem::Actor(Box::new(item))) +impl> FromRequest for StdResult { + fn from_request(self, _: HttpRequest) -> Reply { + match self { + Ok(val) => Reply(ReplyItem::Message(val)), + Err(err) => Reply(ReplyItem::Message(err.into().into())), + } } } -impl From>> for Reply +impl>, S: 'static> FromRequest for HttpContext { - fn from(item: Box>) -> Self { - Reply(ReplyItem::Future(item)) + fn from_request(self, _: HttpRequest) -> Reply { + Reply(ReplyItem::Actor(Box::new(self))) + } +} + +impl>, S: 'static> From> for Reply { + fn from(ctx: HttpContext) -> Reply { + Reply(ReplyItem::Actor(Box::new(ctx))) + } +} + +impl FromRequest for Box> +{ + fn from_request(self, _: HttpRequest) -> Reply { + Reply(ReplyItem::Future(self)) } } @@ -125,7 +157,7 @@ pub(crate) trait RouteHandler: 'static { pub(crate) struct WrapHandler where H: Handler, - R: Into, + R: FromRequest, S: 'static, { h: H, @@ -134,7 +166,7 @@ struct WrapHandler impl WrapHandler where H: Handler, - R: Into, + R: FromRequest, S: 'static, { pub fn new(h: H) -> Self { @@ -144,11 +176,12 @@ impl WrapHandler impl RouteHandler for WrapHandler where H: Handler, - R: Into + 'static, + R: FromRequest + 'static, S: 'static, { fn handle(&self, req: HttpRequest) -> Reply { - self.h.handle(req).into() + let req2 = req.clone_without_state(); + self.h.handle(req).from_request(req2) } } From fb3185de945aec2fab8663d6f3e5cc36c4d0e9db Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Dec 2017 16:47:02 -0800 Subject: [PATCH 008/279] rename module --- src/{staticfiles.rs => fs.rs} | 0 src/lib.rs | 5 ++--- 2 files changed, 2 insertions(+), 3 deletions(-) rename src/{staticfiles.rs => fs.rs} (100%) diff --git a/src/staticfiles.rs b/src/fs.rs similarity index 100% rename from src/staticfiles.rs rename to src/fs.rs diff --git a/src/lib.rs b/src/lib.rs index fb2b1a3aa..ddcf67e85 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,9 +57,8 @@ mod payload; mod resource; mod recognizer; mod route; -//mod task; mod pipeline; -mod staticfiles; +mod fs; mod server; mod channel; mod wsframe; @@ -87,7 +86,7 @@ pub use resource::Resource; pub use recognizer::Params; pub use server::HttpServer; pub use context::HttpContext; -pub use staticfiles::StaticFiles; +pub use fs::StaticFiles; // re-exports pub use http::{Method, StatusCode, Version}; From 6bc7d60f5226e9d7df34d8db8479f80d0bd5b303 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Dec 2017 17:14:55 -0800 Subject: [PATCH 009/279] more default impls for FromRequest --- src/httpresponse.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++ src/route.rs | 4 +-- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 75f45c844..35499ef55 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -12,7 +12,9 @@ use serde::Serialize; use Cookie; use body::Body; use error::Error; +use route::{Reply, FromRequest}; use encoding::ContentEncoding; +use httprequest::HttpRequest; /// Represents various types of connection #[derive(Copy, Clone, PartialEq, Debug)] @@ -458,6 +460,12 @@ impl From for HttpResponse { } } +impl FromRequest for HttpResponseBuilder { + fn from_request(self, _: HttpRequest) -> Reply { + Reply::response(self) + } +} + impl From<&'static str> for HttpResponse { fn from(val: &'static str) -> Self { HttpResponse::build(StatusCode::OK) @@ -467,6 +475,15 @@ impl From<&'static str> for HttpResponse { } } +impl FromRequest for &'static str { + fn from_request(self, req: HttpRequest) -> Reply { + HttpResponse::build(StatusCode::OK) + .content_type("text/plain; charset=utf-8") + .body(self) + .from_request(req) + } +} + impl From<&'static [u8]> for HttpResponse { fn from(val: &'static [u8]) -> Self { HttpResponse::build(StatusCode::OK) @@ -476,6 +493,15 @@ impl From<&'static [u8]> for HttpResponse { } } +impl FromRequest for &'static [u8] { + fn from_request(self, req: HttpRequest) -> Reply { + HttpResponse::build(StatusCode::OK) + .content_type("application/octet-stream") + .body(self) + .from_request(req) + } +} + impl From for HttpResponse { fn from(val: String) -> Self { HttpResponse::build(StatusCode::OK) @@ -485,6 +511,15 @@ impl From for HttpResponse { } } +impl FromRequest for String { + fn from_request(self, req: HttpRequest) -> Reply { + HttpResponse::build(StatusCode::OK) + .content_type("text/plain; charset=utf-8") + .body(self) + .from_request(req) + } +} + impl<'a> From<&'a String> for HttpResponse { fn from(val: &'a String) -> Self { HttpResponse::build(StatusCode::OK) @@ -494,6 +529,15 @@ impl<'a> From<&'a String> for HttpResponse { } } +impl<'a> FromRequest for &'a String { + fn from_request(self, req: HttpRequest) -> Reply { + HttpResponse::build(StatusCode::OK) + .content_type("text/plain; charset=utf-8") + .body(self) + .from_request(req) + } +} + impl From for HttpResponse { fn from(val: Bytes) -> Self { HttpResponse::build(StatusCode::OK) @@ -503,6 +547,15 @@ impl From for HttpResponse { } } +impl FromRequest for Bytes { + fn from_request(self, req: HttpRequest) -> Reply { + HttpResponse::build(StatusCode::OK) + .content_type("application/octet-stream") + .body(self) + .from_request(req) + } +} + impl From for HttpResponse { fn from(val: BytesMut) -> Self { HttpResponse::build(StatusCode::OK) @@ -512,6 +565,15 @@ impl From for HttpResponse { } } +impl FromRequest for BytesMut { + fn from_request(self, req: HttpRequest) -> Reply { + HttpResponse::build(StatusCode::OK) + .content_type("application/octet-stream") + .body(self) + .from_request(req) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/route.rs b/src/route.rs index 1ab32a56f..e949fd853 100644 --- a/src/route.rs +++ b/src/route.rs @@ -92,10 +92,10 @@ default impl FromRequest for T } #[cfg(actix_nightly)] -default impl> FromRequest for StdResult { +default impl, E: Into> FromRequest for StdResult { fn from_request(self, req: HttpRequest) -> Reply { match self { - Ok(val) => val.from_request(req), + Ok(val) => Reply(ReplyItem::Message(val.into())), //val.from_request(req), Err(err) => Reply(ReplyItem::Message(err.into().into())), } } From 7c6faaa8e09e6684e4f022005a43d74a6ce7fc45 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Dec 2017 14:22:04 -0800 Subject: [PATCH 010/279] add Item and Error to FromRequest trait --- README.md | 2 +- build.rs | 10 ++++ examples/basic.rs | 2 +- examples/websocket-chat/src/main.rs | 2 +- examples/websocket.rs | 2 +- guide/src/qs_4.md | 39 ++++-------- guide/src/qs_5.md | 20 +++---- src/dev.rs | 2 +- src/error.rs | 2 +- src/fs.rs | 10 ++-- src/httpcodes.rs | 9 ++- src/httpresponse.rs | 92 ++++++++++++++++++++++++----- src/lib.rs | 5 +- src/route.rs | 89 ++++++++++++++++------------ 14 files changed, 178 insertions(+), 108 deletions(-) diff --git a/README.md b/README.md index 5bdb88dd1..d128ff999 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ fn main() { Application::default("/") .middleware(middlewares::Logger::default()) // <- register logger middleware .resource("/ws/", |r| r.get(|req| ws::start(req, MyWebSocket))) // <- websocket route - .route("/", StaticFiles::new("examples/static/", true))) // <- server static files + .route("/", fs::StaticFiles::new("examples/static/", true))) // <- serve static files .serve::<_, ()>("127.0.0.1:8080").unwrap(); Arbiter::system().send(msgs::SystemExit(0)); diff --git a/build.rs b/build.rs index dae9e0626..f6ff9cb29 100644 --- a/build.rs +++ b/build.rs @@ -12,8 +12,18 @@ fn main() { // generates doc tests for `README.md`. skeptic::generate_doc_tests( &["README.md", + "guide/src/qs_1.md", "guide/src/qs_2.md", "guide/src/qs_3.md", + "guide/src/qs_4.md", + "guide/src/qs_5.md", + "guide/src/qs_6.md", + "guide/src/qs_7.md", + "guide/src/qs_9.md", + "guide/src/qs_10.md", + "guide/src/qs_11.md", + "guide/src/qs_12.md", + "guide/src/qs_13.md", ]); } else { let _ = fs::File::create(f); diff --git a/examples/basic.rs b/examples/basic.rs index fc2a55355..f42642f0e 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -89,7 +89,7 @@ fn main() { } }) // static files - .route("/static", StaticFiles::new("examples/static/", true))) + .route("/static", fs::StaticFiles::new("examples/static/", true))) .serve::<_, ()>("127.0.0.1:8080").unwrap(); println!("Started http server: 127.0.0.1:8080"); diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs index ae123d5ee..8a83e35dc 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -208,7 +208,7 @@ fn main() { // websocket .resource("/ws/", |r| r.get(chat_route)) // static resources - .route("/static", StaticFiles::new("static/", true))) + .route("/static", fs::StaticFiles::new("static/", true))) .serve::<_, ()>("127.0.0.1:8080").unwrap(); let _ = sys.run(); diff --git a/examples/websocket.rs b/examples/websocket.rs index 24a88d03b..655fca05e 100644 --- a/examples/websocket.rs +++ b/examples/websocket.rs @@ -67,7 +67,7 @@ fn main() { // websocket route .resource("/ws/", |r| r.get(ws_index)) // static files - .route("/", StaticFiles::new("examples/static/", true))) + .route("/", fs::StaticFiles::new("examples/static/", true))) // start http server on 127.0.0.1:8080 .serve::<_, ()>("127.0.0.1:8080").unwrap(); diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index 969d01b8f..1675b3a19 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -6,14 +6,14 @@ A request handler can by any object that implements By default actix provdes several `Handler` implementations: * Simple function that accepts `HttpRequest` and returns any object that - can be converted to `HttpResponse` + implements `FromRequest` trait * Function that accepts `HttpRequest` and returns `Result>` object. * Function that accepts `HttpRequest` and return actor that has `HttpContext`as a context. -Actix provides response conversion into `HttpResponse` for some standard types, +Actix provides response `FromRequest` implementation for some standard types, like `&'static str`, `String`, etc. For complete list of implementations check -[HttpResponse documentation](../actix_web/struct.HttpResponse.html#implementations). +[FromRequest documentation](../actix_web/trait.FromRequest.html#foreign-impls). Examples: @@ -58,19 +58,18 @@ struct MyObj { name: String, } -/// we have to convert Error into HttpResponse as well, but with -/// specialization this could be handled genericly. -impl Into for MyObj { - fn into(self) -> HttpResponse { - let body = match serde_json::to_string(&self) { - Err(err) => return Error::from(err).into(), - Ok(body) => body, - }; +/// we have to convert Error into HttpResponse as well +impl FromRequest for MyObj { + type Item = HttpResponse; + type Error = Error; + + fn from_request(self, req: HttpRequest) -> Result { + let body = serde_json::to_string(&self)?; // Create response and set content type - HttpResponse::Ok() + Ok(HttpResponse::Ok() .content_type("application/json") - .body(body).unwrap() + .body(body)?) } } @@ -90,20 +89,6 @@ fn main() { } ``` -If `specialization` is enabled, conversion could be simplier: - -```rust,ignore -impl Into> for MyObj { - fn into(self) -> Result { - let body = serde_json::to_string(&self)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)?) - } -} -``` - ## Async handlers There are two different types of async handlers. diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index adf96c903..849056dc1 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -65,11 +65,11 @@ used later in a request handler to access the matched value for that part. This done by looking up the identifier in the `HttpRequest.match_info` object: ```rust -extern crate actix; +extern crate actix_web; use actix_web::*; -fn index(req: Httprequest) -> String { - format!("Hello, {}", req.match_info["name"]) +fn index(req: HttpRequest) -> String { + format!("Hello, {}", &req.match_info()["name"]) } fn main() { @@ -96,13 +96,13 @@ implements `FromParam` trait. For example most of standard integer types implements `FromParam` trait. i.e.: ```rust -extern crate actix; +extern crate actix_web; use actix_web::*; -fn index(req: Httprequest) -> String { +fn index(req: HttpRequest) -> Result { let v1: u8 = req.match_info().query("v1")?; let v2: u8 = req.match_info().query("v2")?; - format!("Values {} {}", v1, v2) + Ok(format!("Values {} {}", v1, v2)) } fn main() { @@ -146,18 +146,18 @@ safe to interpolate within, or use as a suffix of, a path without additional checks. ```rust -extern crate actix; +extern crate actix_web; use actix_web::*; use std::path::PathBuf; -fn index(req: Httprequest) -> String { +fn index(req: HttpRequest) -> Result { let path: PathBuf = req.match_info().query("tail")?; - format!("Path {:?}", path) + Ok(format!("Path {:?}", path)) } fn main() { Application::default("/") - .resource(r"/a/{tail:**}", |r| r.get(index)) + .resource(r"/a/{tail:*}", |r| r.get(index)) .finish(); } ``` diff --git a/src/dev.rs b/src/dev.rs index 537590651..7e597c9d6 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -9,8 +9,8 @@ //! ``` // dev specific +pub use route::Handler; pub use pipeline::Pipeline; -pub use route::{Handler, FromRequest}; pub use channel::{HttpChannel, HttpHandler}; pub use recognizer::{FromParam, RouteRecognizer}; diff --git a/src/error.rs b/src/error.rs index 606a92580..8ed5ccf97 100644 --- a/src/error.rs +++ b/src/error.rs @@ -31,7 +31,7 @@ use httpcodes::{HTTPBadRequest, HTTPMethodNotAllowed, HTTPExpectationFailed}; pub type Result = result::Result; /// General purpose actix web error -#[derive(Debug)] +#[derive(Fail, Debug)] pub struct Error { cause: Box, } diff --git a/src/fs.rs b/src/fs.rs index 0618fa40c..75650a05e 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1,6 +1,6 @@ //! Static files support. -//! -//! TODO: needs to re-implement actual files handling, current impl blocks + +// //! TODO: needs to re-implement actual files handling, current impl blocks use std::io; use std::io::Read; use std::fmt::Write; @@ -19,11 +19,10 @@ use httpcodes::{HTTPOk, HTTPNotFound}; /// /// ```rust /// extern crate actix_web; -/// use actix_web::*; /// /// fn main() { -/// let app = Application::default("/") -/// .route("/static", StaticFiles::new(".", true)) +/// let app = actix_web::Application::default("/") +/// .route("/static", actix_web::fs::StaticFiles::new(".", true)) /// .finish(); /// } /// ``` @@ -39,6 +38,7 @@ impl StaticFiles { /// Create new `StaticFiles` instance /// /// `dir` - base directory + /// /// `index` - show index for directory pub fn new>(dir: D, index: bool) -> StaticFiles { let dir = dir.into(); diff --git a/src/httpcodes.rs b/src/httpcodes.rs index 013e16d39..64d308d5f 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -1,6 +1,6 @@ //! Basic http responses #![allow(non_upper_case_globals)] -use http::StatusCode; +use http::{StatusCode, Error as HttpError}; use body::Body; use route::{Reply, RouteHandler, FromRequest}; @@ -74,8 +74,11 @@ impl RouteHandler for StaticResponse { } impl FromRequest for StaticResponse { - fn from_request(self, _: HttpRequest) -> Reply { - Reply::response(HttpResponse::new(self.0, Body::Empty)) + type Item = HttpResponse; + type Error = HttpError; + + fn from_request(self, _: HttpRequest) -> Result { + self.build().body(Body::Empty) } } diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 35499ef55..21663853c 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -8,11 +8,10 @@ use http::{StatusCode, Version, HeaderMap, HttpTryFrom, Error as HttpError}; use http::header::{self, HeaderName, HeaderValue}; use serde_json; use serde::Serialize; - use Cookie; use body::Body; use error::Error; -use route::{Reply, FromRequest}; +use route::FromRequest; use encoding::ContentEncoding; use httprequest::HttpRequest; @@ -461,8 +460,11 @@ impl From for HttpResponse { } impl FromRequest for HttpResponseBuilder { - fn from_request(self, _: HttpRequest) -> Reply { - Reply::response(self) + type Item = HttpResponse; + type Error = HttpError; + + fn from_request(mut self, _: HttpRequest) -> Result { + self.finish() } } @@ -476,11 +478,13 @@ impl From<&'static str> for HttpResponse { } impl FromRequest for &'static str { - fn from_request(self, req: HttpRequest) -> Reply { + type Item = HttpResponse; + type Error = HttpError; + + fn from_request(self, _: HttpRequest) -> Result { HttpResponse::build(StatusCode::OK) .content_type("text/plain; charset=utf-8") .body(self) - .from_request(req) } } @@ -494,11 +498,13 @@ impl From<&'static [u8]> for HttpResponse { } impl FromRequest for &'static [u8] { - fn from_request(self, req: HttpRequest) -> Reply { + type Item = HttpResponse; + type Error = HttpError; + + fn from_request(self, _: HttpRequest) -> Result { HttpResponse::build(StatusCode::OK) .content_type("application/octet-stream") .body(self) - .from_request(req) } } @@ -512,11 +518,13 @@ impl From for HttpResponse { } impl FromRequest for String { - fn from_request(self, req: HttpRequest) -> Reply { + type Item = HttpResponse; + type Error = HttpError; + + fn from_request(self, _: HttpRequest) -> Result { HttpResponse::build(StatusCode::OK) .content_type("text/plain; charset=utf-8") .body(self) - .from_request(req) } } @@ -530,11 +538,13 @@ impl<'a> From<&'a String> for HttpResponse { } impl<'a> FromRequest for &'a String { - fn from_request(self, req: HttpRequest) -> Reply { + type Item = HttpResponse; + type Error = HttpError; + + fn from_request(self, _: HttpRequest) -> Result { HttpResponse::build(StatusCode::OK) .content_type("text/plain; charset=utf-8") .body(self) - .from_request(req) } } @@ -548,11 +558,13 @@ impl From for HttpResponse { } impl FromRequest for Bytes { - fn from_request(self, req: HttpRequest) -> Reply { + type Item = HttpResponse; + type Error = HttpError; + + fn from_request(self, _: HttpRequest) -> Result { HttpResponse::build(StatusCode::OK) .content_type("application/octet-stream") .body(self) - .from_request(req) } } @@ -566,11 +578,13 @@ impl From for HttpResponse { } impl FromRequest for BytesMut { - fn from_request(self, req: HttpRequest) -> Reply { + type Item = HttpResponse; + type Error = HttpError; + + fn from_request(self, _: HttpRequest) -> Result { HttpResponse::build(StatusCode::OK) .content_type("application/octet-stream") .body(self) - .from_request(req) } } @@ -650,6 +664,8 @@ mod tests { #[test] fn test_into_response() { + let req = HttpRequest::default(); + let resp: HttpResponse = "test".into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -657,6 +673,13 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().binary().unwrap(), &Binary::from("test")); + let resp: HttpResponse = "test".from_request(req.clone()).ok().unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), + header::HeaderValue::from_static("text/plain; charset=utf-8")); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().binary().unwrap(), &Binary::from("test")); + let resp: HttpResponse = b"test".as_ref().into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -664,6 +687,13 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().binary().unwrap(), &Binary::from(b"test".as_ref())); + let resp: HttpResponse = b"test".as_ref().from_request(req.clone()).ok().unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), + header::HeaderValue::from_static("application/octet-stream")); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().binary().unwrap(), &Binary::from(b"test".as_ref())); + let resp: HttpResponse = "test".to_owned().into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -671,6 +701,13 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().binary().unwrap(), &Binary::from("test".to_owned())); + let resp: HttpResponse = "test".to_owned().from_request(req.clone()).ok().unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), + header::HeaderValue::from_static("text/plain; charset=utf-8")); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().binary().unwrap(), &Binary::from("test".to_owned())); + let resp: HttpResponse = (&"test".to_owned()).into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -678,6 +715,13 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().binary().unwrap(), &Binary::from((&"test".to_owned()))); + let resp: HttpResponse = (&"test".to_owned()).from_request(req.clone()).ok().unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), + header::HeaderValue::from_static("text/plain; charset=utf-8")); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().binary().unwrap(), &Binary::from((&"test".to_owned()))); + let b = Bytes::from_static(b"test"); let resp: HttpResponse = b.into(); assert_eq!(resp.status(), StatusCode::OK); @@ -686,6 +730,14 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().binary().unwrap(), &Binary::from(Bytes::from_static(b"test"))); + let b = Bytes::from_static(b"test"); + let resp: HttpResponse = b.from_request(req.clone()).ok().unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), + header::HeaderValue::from_static("application/octet-stream")); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().binary().unwrap(), &Binary::from(Bytes::from_static(b"test"))); + let b = BytesMut::from("test"); let resp: HttpResponse = b.into(); assert_eq!(resp.status(), StatusCode::OK); @@ -693,5 +745,13 @@ mod tests { header::HeaderValue::from_static("application/octet-stream")); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().binary().unwrap(), &Binary::from(BytesMut::from("test"))); + + let b = BytesMut::from("test"); + let resp: HttpResponse = b.from_request(req.clone()).ok().unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), + header::HeaderValue::from_static("application/octet-stream")); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().binary().unwrap(), &Binary::from(BytesMut::from("test"))); } } diff --git a/src/lib.rs b/src/lib.rs index ddcf67e85..61c6a0a4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,7 +58,6 @@ mod resource; mod recognizer; mod route; mod pipeline; -mod fs; mod server; mod channel; mod wsframe; @@ -68,6 +67,7 @@ mod h2; mod h1writer; mod h2writer; +pub mod fs; pub mod ws; pub mod dev; pub mod error; @@ -81,12 +81,11 @@ pub use application::Application; pub use httprequest::{HttpRequest, UrlEncoded}; pub use httpresponse::HttpResponse; pub use payload::{Payload, PayloadItem}; -pub use route::Reply; +pub use route::{Reply, FromRequest}; pub use resource::Resource; pub use recognizer::Params; pub use server::HttpServer; pub use context::HttpContext; -pub use fs::StaticFiles; // re-exports pub use http::{Method, StatusCode, Version}; diff --git a/src/route.rs b/src/route.rs index e949fd853..7a1fbcbc5 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,5 +1,4 @@ use std::marker::PhantomData; -use std::result::Result as StdResult; use actix::Actor; use futures::Future; @@ -21,7 +20,13 @@ pub trait Handler: 'static { } pub trait FromRequest { - fn from_request(self, req: HttpRequest) -> Reply; + /// The associated item which can be returned. + type Item: Into; + + /// The associated error which can be returned. + type Error: Into; + + fn from_request(self, req: HttpRequest) -> Result; } /// Handler for Fn() @@ -72,46 +77,53 @@ impl Reply { } impl FromRequest for Reply { - fn from_request(self, _: HttpRequest) -> Reply { - self + type Item = Reply; + type Error = Error; + + fn from_request(self, _: HttpRequest) -> Result { + Ok(self) } } impl FromRequest for HttpResponse { - fn from_request(self, _: HttpRequest) -> Reply { - Reply(ReplyItem::Message(self)) + type Item = Reply; + type Error = Error; + + fn from_request(self, _: HttpRequest) -> Result { + Ok(Reply(ReplyItem::Message(self))) } } -#[cfg(actix_nightly)] -default impl FromRequest for T -{ - fn from_request(self, req: HttpRequest) -> Reply { - self.from_request(req) +impl From for Reply { + + fn from(resp: HttpResponse) -> Reply { + Reply(ReplyItem::Message(resp)) } } -#[cfg(actix_nightly)] -default impl, E: Into> FromRequest for StdResult { - fn from_request(self, req: HttpRequest) -> Reply { +impl, E: Into> FromRequest for Result { + type Item = Reply; + type Error = E; + + fn from_request(self, _: HttpRequest) -> Result { match self { - Ok(val) => Reply(ReplyItem::Message(val.into())), //val.from_request(req), - Err(err) => Reply(ReplyItem::Message(err.into().into())), + Ok(val) => Ok(Reply(ReplyItem::Message(val.into()))), + Err(err) => Err(err), } } } -impl> FromRequest for StdResult { - fn from_request(self, _: HttpRequest) -> Reply { - match self { - Ok(val) => val, - Err(err) => Reply(ReplyItem::Message(err.into().into())), - } +impl> FromRequest for Result { + type Item = Reply; + type Error = E; + + fn from_request(self, _: HttpRequest) -> Result { + self } } -impl> From> for Reply { - fn from(res: StdResult) -> Self { +impl> From> for Reply { + fn from(res: Result) -> Self { match res { Ok(val) => val, Err(err) => Reply(ReplyItem::Message(err.into().into())), @@ -119,23 +131,18 @@ impl> From> for Reply { } } -impl> FromRequest for StdResult { - fn from_request(self, _: HttpRequest) -> Reply { - match self { - Ok(val) => Reply(ReplyItem::Message(val)), - Err(err) => Reply(ReplyItem::Message(err.into().into())), - } - } -} - impl>, S: 'static> FromRequest for HttpContext { - fn from_request(self, _: HttpRequest) -> Reply { - Reply(ReplyItem::Actor(Box::new(self))) + type Item = Reply; + type Error = Error; + + fn from_request(self, _: HttpRequest) -> Result { + Ok(Reply(ReplyItem::Actor(Box::new(self)))) } } impl>, S: 'static> From> for Reply { + fn from(ctx: HttpContext) -> Reply { Reply(ReplyItem::Actor(Box::new(ctx))) } @@ -143,8 +150,11 @@ impl>, S: 'static> From> fo impl FromRequest for Box> { - fn from_request(self, _: HttpRequest) -> Reply { - Reply(ReplyItem::Future(self)) + type Item = Reply; + type Error = Error; + + fn from_request(self, _: HttpRequest) -> Result { + Ok(Reply(ReplyItem::Future(self))) } } @@ -181,7 +191,10 @@ impl RouteHandler for WrapHandler { fn handle(&self, req: HttpRequest) -> Reply { let req2 = req.clone_without_state(); - self.h.handle(req).from_request(req2) + match self.h.handle(req).from_request(req2) { + Ok(reply) => reply.into(), + Err(err) => Reply::response(err.into()), + } } } From 5abc46034a31f3853d4bb548ff414aeba330643e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Dec 2017 16:57:25 -0800 Subject: [PATCH 011/279] refactor static files --- src/fs.rs | 279 ++++++++++++++++++++++++++++++++++----------------- src/route.rs | 25 ++--- 2 files changed, 197 insertions(+), 107 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index 75650a05e..86b9ed55b 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -5,13 +5,189 @@ use std::io; use std::io::Read; use std::fmt::Write; use std::fs::{File, DirEntry}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; +use std::ops::{Deref, DerefMut}; use mime_guess::get_mime_type; -use route::Handler; +use route::{Handler, FromRequest}; +use recognizer::FromParam; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use httpcodes::{HTTPOk, HTTPNotFound}; +use httpcodes::HTTPOk; + +/// A file with an associated name; responds with the Content-Type based on the +/// file extension. +#[derive(Debug)] +pub struct NamedFile(PathBuf, File); + +impl NamedFile { + /// Attempts to open a file in read-only mode. + /// + /// # Examples + /// + /// ```rust + /// use actix_web::fs::NamedFile; + /// + /// # #[allow(unused_variables)] + /// let file = NamedFile::open("foo.txt"); + /// ``` + pub fn open>(path: P) -> io::Result { + let file = File::open(path.as_ref())?; + Ok(NamedFile(path.as_ref().to_path_buf(), file)) + } + + /// Returns reference to the underlying `File` object. + #[inline] + pub fn file(&self) -> &File { + &self.1 + } + + /// Retrieve the path of this file. + /// + /// # Examples + /// + /// ```rust + /// # use std::io; + /// use actix_web::fs::NamedFile; + /// + /// # #[allow(dead_code)] + /// # fn path() -> io::Result<()> { + /// let file = NamedFile::open("test.txt")?; + /// assert_eq!(file.path().as_os_str(), "foo.txt"); + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn path(&self) -> &Path { + self.0.as_path() + } +} + +impl Deref for NamedFile { + type Target = File; + + fn deref(&self) -> &File { + &self.1 + } +} + +impl DerefMut for NamedFile { + fn deref_mut(&mut self) -> &mut File { + &mut self.1 + } +} + +impl FromRequest for NamedFile { + type Item = HttpResponse; + type Error = io::Error; + + fn from_request(mut self, _: HttpRequest) -> Result { + let mut resp = HTTPOk.build(); + if let Some(ext) = self.path().extension() { + let mime = get_mime_type(&ext.to_string_lossy()); + resp.content_type(format!("{}", mime).as_str()); + } + let mut data = Vec::new(); + let _ = self.1.read_to_end(&mut data); + Ok(resp.body(data).unwrap()) + } +} + +/// A directory; responds with the generated directory listing. +#[derive(Debug)] +pub struct Directory{ + base: PathBuf, + path: PathBuf +} + +impl Directory { + pub fn new(base: PathBuf, path: PathBuf) -> Directory { + Directory { + base: base, + path: path + } + } + + fn can_list(&self, entry: &io::Result) -> bool { + if let Ok(ref entry) = *entry { + if let Some(name) = entry.file_name().to_str() { + if name.starts_with('.') { + return false + } + } + if let Ok(ref md) = entry.metadata() { + let ft = md.file_type(); + return ft.is_dir() || ft.is_file() || ft.is_symlink() + } + } + false + } +} + +impl FromRequest for Directory { + type Item = HttpResponse; + type Error = io::Error; + + fn from_request(self, req: HttpRequest) -> Result { + let index_of = format!("Index of {}", req.path()); + let mut body = String::new(); + let base = Path::new(req.path()); + + for entry in self.path.read_dir()? { + if self.can_list(&entry) { + let entry = entry.unwrap(); + let p = match entry.path().strip_prefix(&self.base) { + Ok(p) => base.join(p), + Err(_) => continue + }; + // show file url as relative to static path + let file_url = format!("{}", p.to_string_lossy()); + + // if file is a directory, add '/' to the end of the name + if let Ok(metadata) = entry.metadata() { + if metadata.is_dir() { + let _ = write!(body, "", + file_url, entry.file_name().to_string_lossy()); + } else { + let _ = write!(body, "
  • {}
  • ", + file_url, entry.file_name().to_string_lossy()); + } + } else { + continue + } + } + } + + let html = format!("\ + {}\ +

    {}

    \ +
      \ + {}\ +
    \n", index_of, index_of, body); + Ok(HTTPOk.build() + .content_type("text/html; charset=utf-8") + .body(html).unwrap()) + } +} + +/// This enum represents all filesystem elements. +pub enum FilesystemElement { + File(NamedFile), + Directory(Directory), +} + +impl FromRequest for FilesystemElement { + type Item = HttpResponse; + type Error = io::Error; + + fn from_request(self, req: HttpRequest) -> Result { + match self { + FilesystemElement::File(file) => file.from_request(req), + FilesystemElement::Directory(dir) => dir.from_request(req), + } + } +} + /// Static files handling /// @@ -67,106 +243,25 @@ impl StaticFiles { } } - fn index(&self, prefix: &str, relpath: &str, filename: &PathBuf) -> Result { - let index_of = format!("Index of {}{}", prefix, relpath); - let mut body = String::new(); - - for entry in filename.read_dir()? { - if self.can_list(&entry) { - let entry = entry.unwrap(); - // show file url as relative to static path - let file_url = format!( - "{}{}", prefix, - entry.path().strip_prefix(&self.directory).unwrap().to_string_lossy()); - - // if file is a directory, add '/' to the end of the name - if let Ok(metadata) = entry.metadata() { - if metadata.is_dir() { - //format!("
  • {}
  • ", file_url, file_name)); - let _ = write!(body, "
  • {}/
  • ", - file_url, entry.file_name().to_string_lossy()); - } else { - // write!(body, "{}/", entry.file_name()) - let _ = write!(body, "
  • {}
  • ", - file_url, entry.file_name().to_string_lossy()); - } - } else { - continue - } - } - } - - let html = format!("\ - {}\ -

    {}

    \ -
      \ - {}\ -
    \n", index_of, index_of, body); - Ok( - HTTPOk.build() - .content_type("text/html; charset=utf-8") - .body(html).unwrap() - ) - } - - fn can_list(&self, entry: &io::Result) -> bool { - if let Ok(ref entry) = *entry { - if let Some(name) = entry.file_name().to_str() { - if name.starts_with('.') { - return false - } - } - if let Ok(ref md) = entry.metadata() { - let ft = md.file_type(); - return ft.is_dir() || ft.is_file() || ft.is_symlink() - } - } - false - } } impl Handler for StaticFiles { - type Result = Result; + type Result = Result; fn handle(&self, req: HttpRequest) -> Self::Result { if !self.accessible { - Ok(HTTPNotFound.into()) + Err(io::Error::new(io::ErrorKind::NotFound, "not found")) } else { - let mut hidden = false; - let filepath = req.path()[req.prefix_len()..] - .split('/').filter(|s| { - if s.starts_with('.') { - hidden = true; - } - !s.is_empty() - }) - .fold(String::new(), |s, i| {s + "/" + i}); - - // hidden file - if hidden { - return Ok(HTTPNotFound.into()) - } + let relpath = PathBuf::from_param(&req.path()[req.prefix_len()..]) + .map_err(|_| io::Error::new(io::ErrorKind::NotFound, "not found"))?; // full filepath - let idx = if filepath.starts_with('/') { 1 } else { 0 }; - let filename = self.directory.join(&filepath[idx..]).canonicalize()?; + let path = self.directory.join(&relpath).canonicalize()?; - if filename.is_dir() { - self.index(&req.path()[..req.prefix_len()], &filepath[idx..], &filename) + if path.is_dir() { + Ok(FilesystemElement::Directory(Directory::new(self.directory.clone(), path))) } else { - let mut resp = HTTPOk.build(); - if let Some(ext) = filename.extension() { - let mime = get_mime_type(&ext.to_string_lossy()); - resp.content_type(format!("{}", mime).as_str()); - } - match File::open(filename) { - Ok(mut file) => { - let mut data = Vec::new(); - let _ = file.read_to_end(&mut data); - Ok(resp.body(data).unwrap()) - }, - Err(err) => Err(err), - } + Ok(FilesystemElement::File(NamedFile::open(path)?)) } } } diff --git a/src/route.rs b/src/route.rs index 7a1fbcbc5..cb35da70f 100644 --- a/src/route.rs +++ b/src/route.rs @@ -101,27 +101,22 @@ impl From for Reply { } } -impl, E: Into> FromRequest for Result { - type Item = Reply; - type Error = E; +impl> FromRequest for Result +{ + type Item = ::Item; + type Error = Error; - fn from_request(self, _: HttpRequest) -> Result { + fn from_request(self, req: HttpRequest) -> Result { match self { - Ok(val) => Ok(Reply(ReplyItem::Message(val.into()))), - Err(err) => Err(err), + Ok(val) => match val.from_request(req) { + Ok(val) => Ok(val), + Err(err) => Err(err.into()), + }, + Err(err) => Err(err.into()), } } } -impl> FromRequest for Result { - type Item = Reply; - type Error = E; - - fn from_request(self, _: HttpRequest) -> Result { - self - } -} - impl> From> for Reply { fn from(res: Result) -> Self { match res { From 69f0c098e3b3e01e033dddf789539a5dc74a3cf1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Dec 2017 16:58:31 -0800 Subject: [PATCH 012/279] check show_index --- src/fs.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index 86b9ed55b..84da53c28 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -205,7 +205,7 @@ impl FromRequest for FilesystemElement { pub struct StaticFiles { directory: PathBuf, accessible: bool, - _show_index: bool, + show_index: bool, _chunk_size: usize, _follow_symlinks: bool, } @@ -237,7 +237,7 @@ impl StaticFiles { StaticFiles { directory: dir, accessible: access, - _show_index: index, + show_index: index, _chunk_size: 0, _follow_symlinks: false, } @@ -259,7 +259,11 @@ impl Handler for StaticFiles { let path = self.directory.join(&relpath).canonicalize()?; if path.is_dir() { - Ok(FilesystemElement::Directory(Directory::new(self.directory.clone(), path))) + if self.show_index { + Ok(FilesystemElement::Directory(Directory::new(self.directory.clone(), path))) + } else { + Err(io::Error::new(io::ErrorKind::NotFound, "not found")) + } } else { Ok(FilesystemElement::File(NamedFile::open(path)?)) } From 5decff9154c48998e667e18e3f21bb9f0775f62a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Dec 2017 18:15:09 -0800 Subject: [PATCH 013/279] added fs tests --- guide/src/qs_12.md | 40 ++++++++++++++++++++++++++++++++++++++++ src/error.rs | 2 +- src/fs.rs | 35 +++++++++++++++++++++++++++++++++++ src/httpresponse.rs | 4 +++- 4 files changed, 79 insertions(+), 2 deletions(-) diff --git a/guide/src/qs_12.md b/guide/src/qs_12.md index 8220368dd..dd7f6acff 100644 --- a/guide/src/qs_12.md +++ b/guide/src/qs_12.md @@ -1 +1,41 @@ # Static file handling + +## Individual file + +It is possible to serve static files with tail path pattern and `NamedFile`. + +```rust +extern crate actix_web; +use actix_web::*; +use std::path::PathBuf; + +fn index(req: HttpRequest) -> Result { + let path: PathBuf = req.match_info().query("tail")?; + Ok(fs::NamedFile::open(path)?) +} + +fn main() { + Application::default("/") + .resource(r"/a/{tail:*}", |r| r.get(index)) + .finish(); +} +``` + +## Directory + +To serve all files from specific directory `StaticFiles` type could be used. +`StaticFiles` could be registered with `Application::route` method. + +```rust +extern crate actix_web; + +fn main() { + actix_web::Application::default("/") + .route("/static", actix_web::fs::StaticFiles::new(".", true)) + .finish(); +} +``` + +First parameter is a base directory. Second parameter is `show_index`, if it set to *true* +directory listing would be returned for directories, if it is set to *false* +then *404 Not Found* would be returned instead of directory listing. diff --git a/src/error.rs b/src/error.rs index 8ed5ccf97..053df2d7e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -28,7 +28,7 @@ use httpcodes::{HTTPBadRequest, HTTPMethodNotAllowed, HTTPExpectationFailed}; /// /// This typedef is generally used to avoid writing out `actix_web::error::Error` directly and /// is otherwise a direct mapping to `Result`. -pub type Result = result::Result; +pub type Result = result::Result; /// General purpose actix web error #[derive(Fail, Debug)] diff --git a/src/fs.rs b/src/fs.rs index 84da53c28..df275ea83 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -270,3 +270,38 @@ impl Handler for StaticFiles { } } } + +#[cfg(test)] +mod tests { + use super::*; + use http::header; + + #[test] + fn test_named_file() { + assert!(NamedFile::open("test--").is_err()); + let mut file = NamedFile::open("Cargo.toml").unwrap(); + { file.file(); + let _f: &File = &file; } + { let _f: &mut File = &mut file; } + + let resp = file.from_request(HttpRequest::default()).unwrap(); + assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml") + } + + #[test] + fn test_static_files() { + let mut st = StaticFiles::new(".", true); + st.accessible = false; + assert!(st.handle(HttpRequest::default()).is_err()); + + st.accessible = true; + st.show_index = false; + assert!(st.handle(HttpRequest::default()).is_err()); + + st.show_index = true; + let resp = st.handle(HttpRequest::default()).from_request(HttpRequest::default()).unwrap(); + assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/html; charset=utf-8"); + assert!(resp.body().is_binary()); + assert!(format!("{:?}", resp.body()).contains("README.md")); + } +} diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 21663853c..da26c6789 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -600,7 +600,9 @@ mod tests { .version(Version::HTTP_10) .finish().unwrap(); assert_eq!(resp.version(), Some(Version::HTTP_10)); - assert_eq!(resp.status(), StatusCode::NO_CONTENT) + assert_eq!(resp.status(), StatusCode::NO_CONTENT); + + let _t = format!("{:?}", resp); } #[test] From 319e9bbd05cd9c9169f41513cbb3fce535d1d024 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Dec 2017 18:51:52 -0800 Subject: [PATCH 014/279] added Json response support --- guide/src/qs_12.md | 4 ++-- guide/src/qs_7.md | 26 ++++++++++++++++++++++++++ src/lib.rs | 5 +++-- src/route.rs | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 4 deletions(-) diff --git a/guide/src/qs_12.md b/guide/src/qs_12.md index dd7f6acff..102f4f94c 100644 --- a/guide/src/qs_12.md +++ b/guide/src/qs_12.md @@ -23,7 +23,7 @@ fn main() { ## Directory -To serve all files from specific directory `StaticFiles` type could be used. +To serve files from specific directory and sub-directories `StaticFiles` type could be used. `StaticFiles` could be registered with `Application::route` method. ```rust @@ -36,6 +36,6 @@ fn main() { } ``` -First parameter is a base directory. Second parameter is `show_index`, if it set to *true* +First parameter is a base directory. Second parameter is *show_index*, if it is set to *true* directory listing would be returned for directories, if it is set to *false* then *404 Not Found* would be returned instead of directory listing. diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index b5b91d599..bfbcb6577 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -13,3 +13,29 @@ Following encodings are supported: If request headers contains `Content-Encoding` header, request payload get decompressed according to header value. Multiple codecs are not supported, i.e: `Content-Encoding: br, gzip`. +## JSON Response + +The `Json` type allows you to respond with well-formed JSON data: simply return a value of +type Json where T is the type of a structure to serialize into *JSON*. The +type `T` must implement the `Serialize` trait from *serde*. + +```rust +extern crate actix_web; +#[macro_use] extern crate serde_derive; +use actix_web::*; + +#[derive(Serialize)] +struct MyObj { + name: String, +} + +fn index(req: HttpRequest) -> Result> { + Ok(Json(MyObj{name: req.match_info().query("name")?})) +} + +fn main() { + Application::default("/") + .resource(r"/a/{name}", |r| r.get(index)) + .finish(); +} +``` diff --git a/src/lib.rs b/src/lib.rs index 61c6a0a4f..6b90a3917 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,7 +34,8 @@ extern crate percent_encoding; extern crate actix; extern crate h2 as http2; -// extern crate redis_async; +#[cfg(test)] +#[macro_use] extern crate serde_derive; #[cfg(feature="tls")] extern crate native_tls; @@ -81,7 +82,7 @@ pub use application::Application; pub use httprequest::{HttpRequest, UrlEncoded}; pub use httpresponse::HttpResponse; pub use payload::{Payload, PayloadItem}; -pub use route::{Reply, FromRequest}; +pub use route::{Reply, Json, FromRequest}; pub use resource::Resource; pub use recognizer::Params; pub use server::HttpServer; diff --git a/src/route.rs b/src/route.rs index cb35da70f..25223924a 100644 --- a/src/route.rs +++ b/src/route.rs @@ -2,6 +2,8 @@ use std::marker::PhantomData; use actix::Actor; use futures::Future; +use serde_json; +use serde::Serialize; use error::Error; use context::{HttpContext, IoContext}; @@ -223,3 +225,37 @@ impl RouteHandler for AsyncHandler Reply::async((self.f)(req)) } } + + +pub struct Json (pub T); + +impl FromRequest for Json { + type Item = HttpResponse; + type Error = Error; + + fn from_request(self, _: HttpRequest) -> Result { + let body = serde_json::to_string(&self.0)?; + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(body)?) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use http::header; + + #[derive(Serialize)] + struct MyObj { + name: &'static str, + } + + #[test] + fn test_json() { + let json = Json(MyObj{name: "test"}); + let resp = json.from_request(HttpRequest::default()).unwrap(); + assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "application/json"); + } +} From 57c53bd2a0eb5599e8f3021df0c3d75ee1a64c80 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Dec 2017 18:58:15 -0800 Subject: [PATCH 015/279] add encoding section --- guide/src/qs_7.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index bfbcb6577..abad8dfe8 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -13,6 +13,19 @@ Following encodings are supported: If request headers contains `Content-Encoding` header, request payload get decompressed according to header value. Multiple codecs are not supported, i.e: `Content-Encoding: br, gzip`. +Response payload get compressed based on `content_encoding` settings. +If `ContentEncoding::Auto` is selected then compression depends on request's +`Accept-Encoding` header. `ContentEncoding::Identity` could be used to disable compression. +If other content encoding is selected the compression is enforced. + +```rust,ignore +fn index(req: HttpRequest) -> HttpResponse { + HttpResponse::Ok() + .content_encoding(ContentEncoding::Br) + .body(Body) +} +``` + ## JSON Response The `Json` type allows you to respond with well-formed JSON data: simply return a value of From d35be02587be911e20f0ec7c15c88c4d88ec4687 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Dec 2017 20:09:46 -0800 Subject: [PATCH 016/279] cleanup --- guide/src/qs_7.md | 20 +++++++++++++------- src/httpresponse.rs | 14 ++------------ 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index abad8dfe8..356c1d817 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -3,7 +3,7 @@ ## Content encoding Actix automatically *compress*/*decompress* payload. -Following encodings are supported: +Following codecs are supported: * Brotli * Gzip @@ -13,17 +13,23 @@ Following encodings are supported: If request headers contains `Content-Encoding` header, request payload get decompressed according to header value. Multiple codecs are not supported, i.e: `Content-Encoding: br, gzip`. -Response payload get compressed based on `content_encoding` settings. -If `ContentEncoding::Auto` is selected then compression depends on request's -`Accept-Encoding` header. `ContentEncoding::Identity` could be used to disable compression. -If other content encoding is selected the compression is enforced. +Response payload get compressed based on *content_encoding* parameter. +By default `ContentEncoding::Auto` is used. If `ContentEncoding::Auto` is selected +then compression depends on request's `Accept-Encoding` header. +`ContentEncoding::Identity` could be used to disable compression. +If other content encoding is selected the compression is enforced for this codec. For example, +to enable `brotli` response's body compression use `ContentEncoding::Br`: + +```rust +extern crate actix_web; +use actix_web::*; -```rust,ignore fn index(req: HttpRequest) -> HttpResponse { HttpResponse::Ok() .content_encoding(ContentEncoding::Br) - .body(Body) + .body("data").unwrap() } +# fn main() {} ``` ## JSON Response diff --git a/src/httpresponse.rs b/src/httpresponse.rs index da26c6789..ef757e1a8 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -1,5 +1,5 @@ //! Pieces pertaining to the HTTP response. -use std::{io, mem, str, fmt}; +use std::{mem, str, fmt}; use std::convert::Into; use cookie::CookieJar; @@ -157,17 +157,6 @@ impl HttpResponse { self.chunked } - /// Enables automatic chunked transfer encoding - pub fn enable_chunked_encoding(&mut self) -> Result<(), io::Error> { - if self.headers.contains_key(header::CONTENT_LENGTH) { - Err(io::Error::new(io::ErrorKind::Other, - "You can't enable chunked encoding when a content length is set")) - } else { - self.chunked = true; - Ok(()) - } - } - /// Content encoding pub fn content_encoding(&self) -> &ContentEncoding { &self.encoding @@ -597,6 +586,7 @@ mod tests { fn test_basic_builder() { let resp = HttpResponse::Ok() .status(StatusCode::NO_CONTENT) + .header("X-TEST", "value") .version(Version::HTTP_10) .finish().unwrap(); assert_eq!(resp.version(), Some(Version::HTTP_10)); From 57fd35ffc1e929e1d5ec5ee2299833f95cc3f8c7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Dec 2017 20:47:15 -0800 Subject: [PATCH 017/279] added default headers middleware --- build.rs | 1 - guide/src/SUMMARY.md | 3 +- guide/src/qs_10.md | 88 ++++++++++++++++++++++- guide/src/qs_11.md | 59 ---------------- src/httpresponse.rs | 6 ++ src/middlewares/defaultheaders.rs | 113 ++++++++++++++++++++++++++++++ src/middlewares/mod.rs | 2 + 7 files changed, 209 insertions(+), 63 deletions(-) delete mode 100644 guide/src/qs_11.md create mode 100644 src/middlewares/defaultheaders.rs diff --git a/build.rs b/build.rs index f6ff9cb29..6a8a3bd01 100644 --- a/build.rs +++ b/build.rs @@ -21,7 +21,6 @@ fn main() { "guide/src/qs_7.md", "guide/src/qs_9.md", "guide/src/qs_10.md", - "guide/src/qs_11.md", "guide/src/qs_12.md", "guide/src/qs_13.md", ]); diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 4a820e160..1828400d3 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -8,7 +8,6 @@ - [Application state](./qs_6.md) - [Request & Response](./qs_7.md) - [WebSockets](./qs_9.md) -- [User sessions](./qs_10.md) -- [Logging](./qs_11.md) +- [Middlewares](./qs_10.md) - [Static file handling](./qs_12.md) - [HTTP/2](./qs_13.md) diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index 0c28628eb..422e0812d 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -1 +1,87 @@ -# User sessions +# Middlewares + +## Logging + +Logging is implemented as middleware. Middlewares get executed in same order as registraton order. +It is common to register logging middleware as first middleware for application. +Logging middleware has to be registered for each application. + +### Usage + +Create `Logger` middlewares with the specified `format`. +Default `Logger` could be created with `default` method, it uses the default format: + +```ignore + %a %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i" %T +``` +```rust +extern crate actix_web; +use actix_web::Application; +use actix_web::middlewares::Logger; + +fn main() { + Application::default("/") + .middleware(Logger::default()) + .middleware(Logger::new("%a %{User-Agent}i")) + .finish(); +} +``` + +Here is example of default logging format: + +``` +INFO:actix_web::middlewares::logger: 127.0.0.1:59934 [02/Dec/2017:00:21:43 -0800] "GET / HTTP/1.1" 302 0 "-" "curl/7.54.0" 0.000397 +INFO:actix_web::middlewares::logger: 127.0.0.1:59947 [02/Dec/2017:00:22:40 -0800] "GET /index.html HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:57.0) Gecko/20100101 Firefox/57.0" 0.000646 +``` + +### Format + + `%%` The percent sign + + `%a` Remote IP-address (IP-address of proxy if using reverse proxy) + + `%t` Time when the request was started to process + + `%P` The process ID of the child that serviced the request + + `%r` First line of request + + `%s` Response status code + + `%b` Size of response in bytes, including HTTP headers + + `%T` Time taken to serve the request, in seconds with floating fraction in .06f format + + `%D` Time taken to serve the request, in milliseconds + + `%{FOO}i` request.headers['FOO'] + + `%{FOO}o` response.headers['FOO'] + + `%{FOO}e` os.environ['FOO'] + + +## Default headers + +It is possible to set default response headers with `DefaultHeaders` middleware. +*DefaultHeaders* middleware does not set header if response headers already contains it. + +```rust +extern crate actix_web; +use actix_web::*; + +fn main() { + let app = Application::default("/") + .middleware( + middlewares::DefaultHeaders::build() + .header("X-Version", "0.2") + .finish()) + .resource("/test", |r| { + r.get(|req| httpcodes::HTTPOk); + r.handler(Method::HEAD, |req| httpcodes::HTTPMethodNotAllowed); + }) + .finish(); +} +``` + +## User sessions diff --git a/guide/src/qs_11.md b/guide/src/qs_11.md deleted file mode 100644 index 5aed847ce..000000000 --- a/guide/src/qs_11.md +++ /dev/null @@ -1,59 +0,0 @@ -# Logging - -Logging is implemented as middleware. Middlewares get executed in same order as registraton order. -It is common to register logging middleware as first middleware for application. -Logging middleware has to be registered for each application. - -## Usage - -Create `Logger` middlewares with the specified `format`. -Default `Logger` could be created with `default` method, it uses the default format: - -```ignore - %a %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i" %T -``` -```rust -extern crate actix_web; -use actix_web::Application; -use actix_web::middlewares::Logger; - -fn main() { - Application::default("/") - .middleware(Logger::default()) - .middleware(Logger::new("%a %{User-Agent}i")) - .finish(); -} -``` - -Here is example of default logging format: - -``` -INFO:actix_web::middlewares::logger: 127.0.0.1:59934 [02/Dec/2017:00:21:43 -0800] "GET / HTTP/1.1" 302 0 "-" "curl/7.54.0" 0.000397 -INFO:actix_web::middlewares::logger: 127.0.0.1:59947 [02/Dec/2017:00:22:40 -0800] "GET /index.html HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:57.0) Gecko/20100101 Firefox/57.0" 0.000646 -``` - -## Format - - `%%` The percent sign - - `%a` Remote IP-address (IP-address of proxy if using reverse proxy) - - `%t` Time when the request was started to process - - `%P` The process ID of the child that serviced the request - - `%r` First line of request - - `%s` Response status code - - `%b` Size of response in bytes, including HTTP headers - - `%T` Time taken to serve the request, in seconds with floating fraction in .06f format - - `%D` Time taken to serve the request, in milliseconds - - `%{FOO}i` request.headers['FOO'] - - `%{FOO}o` response.headers['FOO'] - - `%{FOO}e` os.environ['FOO'] diff --git a/src/httpresponse.rs b/src/httpresponse.rs index ef757e1a8..cfb83fa0b 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -303,6 +303,7 @@ impl HttpResponseBuilder { /// By default `ContentEncoding::Auto` is used, which automatically /// negotiates content encoding based on request's `Accept-Encoding` headers. /// To enforce specific encoding, use specific ContentEncoding` value. + #[inline] pub fn content_encoding(&mut self, enc: ContentEncoding) -> &mut Self { if let Some(parts) = parts(&mut self.parts, &self.err) { parts.encoding = enc; @@ -311,6 +312,7 @@ impl HttpResponseBuilder { } /// Set connection type + #[inline] pub fn connection_type(&mut self, conn: ConnectionType) -> &mut Self { if let Some(parts) = parts(&mut self.parts, &self.err) { parts.connection_type = Some(conn); @@ -319,16 +321,19 @@ impl HttpResponseBuilder { } /// Set connection type to Upgrade + #[inline] pub fn upgrade(&mut self) -> &mut Self { self.connection_type(ConnectionType::Upgrade) } /// Force close connection, even if it is marked as keep-alive + #[inline] pub fn force_close(&mut self) -> &mut Self { self.connection_type(ConnectionType::Close) } /// Enables automatic chunked transfer encoding + #[inline] pub fn enable_chunked(&mut self) -> &mut Self { if let Some(parts) = parts(&mut self.parts, &self.err) { parts.chunked = true; @@ -337,6 +342,7 @@ impl HttpResponseBuilder { } /// Set response content type + #[inline] pub fn content_type(&mut self, value: V) -> &mut Self where HeaderValue: HttpTryFrom { diff --git a/src/middlewares/defaultheaders.rs b/src/middlewares/defaultheaders.rs new file mode 100644 index 000000000..ae52e684f --- /dev/null +++ b/src/middlewares/defaultheaders.rs @@ -0,0 +1,113 @@ +//! Default response headers +use http::{HeaderMap, HttpTryFrom}; +use http::header::{HeaderName, HeaderValue}; + +use httprequest::HttpRequest; +use httpresponse::HttpResponse; +use middlewares::{Response, Middleware}; + +/// `Middleware` for setting default response headers. +/// +/// This middleware does not set header if response headers already contains it. +/// +/// ```rust +/// extern crate actix_web; +/// use actix_web::*; +/// +/// fn main() { +/// let app = Application::default("/") +/// .middleware( +/// middlewares::DefaultHeaders::build() +/// .header("X-Version", "0.2") +/// .finish()) +/// .resource("/test", |r| { +/// r.get(|req| httpcodes::HTTPOk); +/// r.handler(Method::HEAD, |req| httpcodes::HTTPMethodNotAllowed); +/// }) +/// .finish(); +/// } +/// ``` +pub struct DefaultHeaders(HeaderMap); + +impl DefaultHeaders { + pub fn build() -> DefaultHeadersBuilder { + DefaultHeadersBuilder{headers: Some(HeaderMap::new())} + } +} + +impl Middleware for DefaultHeaders { + + fn response(&self, _: &mut HttpRequest, mut resp: HttpResponse) -> Response { + for (key, value) in self.0.iter() { + if !resp.headers().contains_key(key) { + resp.headers_mut().insert(key, value.clone()); + } + } + Response::Done(resp) + } +} + +/// Structure that follows the builder pattern for building `DefaultHeaders` middleware. +#[derive(Debug)] +pub struct DefaultHeadersBuilder { + headers: Option, +} + +impl DefaultHeadersBuilder { + + /// Set a header. + #[inline] + #[cfg_attr(feature = "cargo-clippy", allow(match_wild_err_arm))] + pub fn header(&mut self, key: K, value: V) -> &mut Self + where HeaderName: HttpTryFrom, + HeaderValue: HttpTryFrom + { + if let Some(ref mut headers) = self.headers { + match HeaderName::try_from(key) { + Ok(key) => { + match HeaderValue::try_from(value) { + Ok(value) => { headers.append(key, value); } + Err(_) => panic!("Can not create header value"), + } + }, + Err(_) => panic!("Can not create header name"), + }; + } + self + } + + /// Finishes building and returns the built `DefaultHeaders` middleware. + pub fn finish(&mut self) -> DefaultHeaders { + let headers = self.headers.take().expect("cannot reuse middleware builder"); + DefaultHeaders(headers) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use http::header::CONTENT_TYPE; + + #[test] + fn test_default_headers() { + let mw = DefaultHeaders::build() + .header(CONTENT_TYPE, "0001") + .finish(); + + let mut req = HttpRequest::default(); + + let resp = HttpResponse::Ok().finish().unwrap(); + let resp = match mw.response(&mut req, resp) { + Response::Done(resp) => resp, + _ => panic!(), + }; + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); + + let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish().unwrap(); + let resp = match mw.response(&mut req, resp) { + Response::Done(resp) => resp, + _ => panic!(), + }; + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002"); + } +} diff --git a/src/middlewares/mod.rs b/src/middlewares/mod.rs index abb25800a..ebe405319 100644 --- a/src/middlewares/mod.rs +++ b/src/middlewares/mod.rs @@ -7,7 +7,9 @@ use httpresponse::HttpResponse; mod logger; mod session; +mod defaultheaders; pub use self::logger::Logger; +pub use self::defaultheaders::{DefaultHeaders, DefaultHeadersBuilder}; pub use self::session::{RequestSession, Session, SessionImpl, SessionBackend, SessionStorage, CookieSessionError, CookieSessionBackend, CookieSessionBackendBuilder}; From 3bf3738e6548b1c17b4a83146c6c483f13351773 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Dec 2017 13:32:05 -0800 Subject: [PATCH 018/279] introduce route predicates --- README.md | 3 +- examples/basic.rs | 6 +- examples/state.rs | 4 +- examples/websocket.rs | 2 +- guide/src/qs_10.md | 8 +- guide/src/qs_12.md | 2 +- guide/src/qs_2.md | 29 +++-- guide/src/qs_3.md | 21 ++-- guide/src/qs_4.md | 4 +- guide/src/qs_5.md | 59 ++++++---- guide/src/qs_6.md | 2 +- guide/src/qs_7.md | 2 +- src/application.rs | 6 +- src/lib.rs | 3 +- src/middlewares/defaultheaders.rs | 4 +- src/pred.rs | 148 +++++++++++++++++++++++++ src/recognizer.rs | 9 +- src/resource.rs | 174 ++++++++++++++++++++---------- src/ws.rs | 2 +- tests/test_server.rs | 4 +- 20 files changed, 370 insertions(+), 122 deletions(-) create mode 100644 src/pred.rs diff --git a/README.md b/README.md index d128ff999..695e67675 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,8 @@ fn main() { HttpServer::new( Application::default("/") .middleware(middlewares::Logger::default()) // <- register logger middleware - .resource("/ws/", |r| r.get(|req| ws::start(req, MyWebSocket))) // <- websocket route + .resource("/ws/", |r| r.method(Method::GET) + .handler(|req| ws::start(req, MyWebSocket))) // <- websocket route .route("/", fs::StaticFiles::new("examples/static/", true))) // <- serve static files .serve::<_, ()>("127.0.0.1:8080").unwrap(); diff --git a/examples/basic.rs b/examples/basic.rs index f42642f0e..a7e375110 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -69,11 +69,11 @@ fn main() { // register simple handle r, handle all methods .handler("/index.html", index) // with path parameters - .resource("/user/{name}/", |r| r.handler(Method::GET, with_param)) + .resource("/user/{name}/", |r| r.route().method(Method::GET).handler(with_param)) // async handler - .resource("/async/{name}", |r| r.async(Method::GET, index_async)) + .resource("/async/{name}", |r| r.route().method(Method::GET).async(index_async)) // redirect - .resource("/", |r| r.handler(Method::GET, |req| { + .resource("/", |r| r.route().method(Method::GET).handler(|req| { println!("{:?}", req); httpcodes::HTTPFound diff --git a/examples/state.rs b/examples/state.rs index 52a2da799..afdc5e78f 100644 --- a/examples/state.rs +++ b/examples/state.rs @@ -64,7 +64,9 @@ fn main() { // enable logger .middleware(middlewares::Logger::default()) // websocket route - .resource("/ws/", |r| r.get(|r| ws::start(r, MyWebSocket{counter: 0}))) + .resource( + "/ws/", |r| r.route().method(Method::GET) + .handler(|req| ws::start(req, MyWebSocket{counter: 0}))) // register simple handler, handle all methods .handler("/", index)) .serve::<_, ()>("127.0.0.1:8080").unwrap(); diff --git a/examples/websocket.rs b/examples/websocket.rs index 655fca05e..0772b3c99 100644 --- a/examples/websocket.rs +++ b/examples/websocket.rs @@ -65,7 +65,7 @@ fn main() { // enable logger .middleware(middlewares::Logger::default()) // websocket route - .resource("/ws/", |r| r.get(ws_index)) + .resource("/ws/", |r| r.route().method(Method::GET).handler(ws_index)) // static files .route("/", fs::StaticFiles::new("examples/static/", true))) // start http server on 127.0.0.1:8080 diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index 422e0812d..66a049d22 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -50,7 +50,7 @@ INFO:actix_web::middlewares::logger: 127.0.0.1:59947 [02/Dec/2017:00:22:40 -0800 `%b` Size of response in bytes, including HTTP headers - `%T` Time taken to serve the request, in seconds with floating fraction in .06f format + `%T` Time taken to serve the request, in seconds with floating fraction in .06f format `%D` Time taken to serve the request, in milliseconds @@ -63,7 +63,7 @@ INFO:actix_web::middlewares::logger: 127.0.0.1:59947 [02/Dec/2017:00:22:40 -0800 ## Default headers -It is possible to set default response headers with `DefaultHeaders` middleware. +Tto set default response headers `DefaultHeaders` middleware could be used. *DefaultHeaders* middleware does not set header if response headers already contains it. ```rust @@ -77,8 +77,8 @@ fn main() { .header("X-Version", "0.2") .finish()) .resource("/test", |r| { - r.get(|req| httpcodes::HTTPOk); - r.handler(Method::HEAD, |req| httpcodes::HTTPMethodNotAllowed); + r.method(Method::GET).handler(|req| httpcodes::HTTPOk); + r.method(Method::HEAD).handler(|req| httpcodes::HTTPMethodNotAllowed); }) .finish(); } diff --git a/guide/src/qs_12.md b/guide/src/qs_12.md index 102f4f94c..a280e0759 100644 --- a/guide/src/qs_12.md +++ b/guide/src/qs_12.md @@ -16,7 +16,7 @@ fn index(req: HttpRequest) -> Result { fn main() { Application::default("/") - .resource(r"/a/{tail:*}", |r| r.get(index)) + .resource(r"/a/{tail:*}", |r| r.method(Method::GET).handler(index)) .finish(); } ``` diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md index 4fbcf67fa..480aacb86 100644 --- a/guide/src/qs_2.md +++ b/guide/src/qs_2.md @@ -29,22 +29,29 @@ In order to implement a web server, first we need to create a request handler. A request handler is a function that accepts a `HttpRequest` instance as its only parameter and returns a type that can be converted into `HttpResponse`: -```rust,ignore -extern crate actix_web; -use actix_web::*; - -fn index(req: HttpRequest) -> &'static str { - "Hello world!" -} +```rust +# extern crate actix_web; +# use actix_web::*; + fn index(req: HttpRequest) -> &'static str { + "Hello world!" + } +# fn main() {} ``` Next, create an `Application` instance and register the request handler with the application's `resource` on a particular *HTTP method* and *path*:: -```rust,ignore +```rust +# extern crate actix_web; +# use actix_web::*; +# fn index(req: HttpRequest) -> &'static str { +# "Hello world!" +# } +# fn main() { let app = Application::default("/") - .resource("/", |r| r.get(index)) - .finish() + .resource("/", |r| r.method(Method::GET).handler(index)) + .finish(); +# } ``` After that, application instance can be used with `HttpServer` to listen for incoming @@ -73,7 +80,7 @@ fn main() { HttpServer::new( Application::default("/") - .resource("/", |r| r.get(index))) + .resource("/", |r| r.route().handler(index))) .serve::<_, ()>("127.0.0.1:8088").unwrap(); println!("Started http server: 127.0.0.1:8088"); diff --git a/guide/src/qs_3.md b/guide/src/qs_3.md index 66606206e..28841f894 100644 --- a/guide/src/qs_3.md +++ b/guide/src/qs_3.md @@ -13,9 +13,18 @@ Application acts as namespace for all routes, i.e all routes for specific applic has same url path prefix: ```rust,ignore +# extern crate actix_web; +# extern crate tokio_core; +# use actix_web::*; +# fn index(req: HttpRequest) -> &'static str { +# "Hello world!" +# } + +# fn main() { let app = Application::default("/prefix") - .resource("/index.html", |r| r.handler(Method::GET, index) + .resource("/index.html", |r| r.method(Method::GET).handler(index)) .finish() +# } ``` In this example application with `/prefix` prefix and `index.html` resource @@ -24,8 +33,8 @@ get created. This resource is available as on `/prefix/index.html` url. Multiple applications could be served with one server: ```rust -extern crate actix_web; -extern crate tokio_core; +# extern crate actix_web; +# extern crate tokio_core; use std::net::SocketAddr; use actix_web::*; use tokio_core::net::TcpStream; @@ -33,13 +42,13 @@ use tokio_core::net::TcpStream; fn main() { HttpServer::::new(vec![ Application::default("/app1") - .resource("/", |r| r.get(|r| httpcodes::HTTPOk)) + .resource("/", |r| r.route().handler(|r| httpcodes::HTTPOk)) .finish(), Application::default("/app2") - .resource("/", |r| r.get(|r| httpcodes::HTTPOk)) + .resource("/", |r| r.route().handler(|r| httpcodes::HTTPOk)) .finish(), Application::default("/") - .resource("/", |r| r.get(|r| httpcodes::HTTPOk)) + .resource("/", |r| r.route().handler(|r| httpcodes::HTTPOk)) .finish(), ]); } diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index 1675b3a19..0e0ff981c 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -78,8 +78,8 @@ fn main() { HttpServer::new( Application::default("/") - .resource("/", |r| r.handler( - Method::GET, |req| {MyObj{name: "user".to_owned()}}))) + .resource("/", |r| r.method( + Method::GET).handler(|req| {MyObj{name: "user".to_owned()}}))) .serve::<_, ()>("127.0.0.1:8088").unwrap(); println!("Started http server: 127.0.0.1:8088"); diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index 849056dc1..4ccceceaf 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -8,14 +8,17 @@ which corresponds to requested URL. Prefix handler: -```rust,ignore -fn index(req: Httprequest) -> HttpResponse { - ... +```rust +# extern crate actix_web; +# use actix_web::*; + +fn index(req: HttpRequest) -> HttpResponse { + unimplemented!() } fn main() { Application::default("/") - .handler("/prefix", |req| index) + .handler("/prefix", index) .finish(); } ``` @@ -24,10 +27,17 @@ In this example `index` get called for any url which starts with `/prefix`. Application prefix combines with handler prefix i.e -```rust,ignore +```rust +# extern crate actix_web; +# use actix_web::*; + +fn index(req: HttpRequest) -> HttpResponse { + unimplemented!() +} + fn main() { Application::default("/app") - .handler("/prefix", |req| index) + .handler("/prefix", index) .finish(); } ``` @@ -38,12 +48,15 @@ Resource contains set of route for same endpoint. Route corresponds to handling *HTTP method* by calling *web handler*. Resource select route based on *http method*, if no route could be matched default response `HTTPMethodNotAllowed` get resturned. -```rust,ignore +```rust +# extern crate actix_web; +# use actix_web::*; + fn main() { Application::default("/") .resource("/prefix", |r| { - r.get(HTTPOk) - r.post(HTTPForbidden) + r.method(Method::GET).handler(|r| httpcodes::HTTPOk); + r.method(Method::POST).handler(|r| httpcodes::HTTPForbidden); }) .finish(); } @@ -65,7 +78,7 @@ used later in a request handler to access the matched value for that part. This done by looking up the identifier in the `HttpRequest.match_info` object: ```rust -extern crate actix_web; +# extern crate actix_web; use actix_web::*; fn index(req: HttpRequest) -> String { @@ -74,7 +87,7 @@ fn index(req: HttpRequest) -> String { fn main() { Application::default("/") - .resource("/{name}", |r| r.get(index)) + .resource("/{name}", |r| r.method(Method::GET).handler(index)) .finish(); } ``` @@ -83,10 +96,16 @@ By default, each part matches the regular expression `[^{}/]+`. You can also specify a custom regex in the form `{identifier:regex}`: -```rust,ignore +```rust +# extern crate actix_web; +# use actix_web::*; +# fn index(req: HttpRequest) -> String { +# format!("Hello, {}", &req.match_info()["name"]) +# } + fn main() { Application::default("/") - .resource(r"{name:\d+}", |r| r.get(index)) + .resource(r"{name:\d+}", |r| r.method(Method::GET).handler(index)) .finish(); } ``` @@ -107,20 +126,19 @@ fn index(req: HttpRequest) -> Result { fn main() { Application::default("/") - .resource(r"/a/{v1}/{v2}/", |r| r.get(index)) + .resource(r"/a/{v1}/{v2}/", |r| r.route().handler(index)) .finish(); } ``` For this example for path '/a/1/2/', values v1 and v2 will resolve to "1" and "2". -To match path tail, `{tail:*}` pattern could be used. Tail pattern must to be last -component of a path, any text after tail pattern will result in panic. +It is possible to match path tail with custom `.*` regex. ```rust,ignore fn main() { Application::default("/") - .resource(r"/test/{tail:*}", |r| r.get(index)) + .resource(r"/test/{tail:.*}", |r| r.method(Method::GET).handler(index)) .finish(); } ``` @@ -142,11 +160,10 @@ an `Err` is returned indicating the condition met: * Percent-encoding results in invalid UTF8. As a result of these conditions, a `PathBuf` parsed from request path parameter is -safe to interpolate within, or use as a suffix of, a path without additional -checks. +safe to interpolate within, or use as a suffix of, a path without additional checks. ```rust -extern crate actix_web; +# extern crate actix_web; use actix_web::*; use std::path::PathBuf; @@ -157,7 +174,7 @@ fn index(req: HttpRequest) -> Result { fn main() { Application::default("/") - .resource(r"/a/{tail:*}", |r| r.get(index)) + .resource(r"/a/{tail:.*}", |r| r.method(Method::GET).handler(index)) .finish(); } ``` diff --git a/guide/src/qs_6.md b/guide/src/qs_6.md index 8060e455a..096459b78 100644 --- a/guide/src/qs_6.md +++ b/guide/src/qs_6.md @@ -30,7 +30,7 @@ fn index(req: HttpRequest) -> String { fn main() { Application::build("/", AppState{counter: Cell::new(0)}) - .resource("/", |r| r.handler(Method::GET, index)) + .resource("/", |r| r.method(Method::GET).handler(index)) .finish(); } ``` diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 356c1d817..fcf2dec83 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -54,7 +54,7 @@ fn index(req: HttpRequest) -> Result> { fn main() { Application::default("/") - .resource(r"/a/{name}", |r| r.get(index)) + .resource(r"/a/{name}", |r| r.method(Method::GET).handler(index)) .finish(); } ``` diff --git a/src/application.rs b/src/application.rs index 91514d151..3e05487e0 100644 --- a/src/application.rs +++ b/src/application.rs @@ -118,7 +118,7 @@ impl ApplicationBuilder where S: 'static { /// A variable part is specified in the form `{identifier}`, where /// the identifier can be used later in a request handler to access the matched /// value for that part. This is done by looking up the identifier - /// in the `Params` object returned by `Request.match_info()` method. + /// in the `Params` object returned by `HttpRequest.match_info()` method. /// /// By default, each part matches the regular expression `[^{}/]+`. /// @@ -134,8 +134,8 @@ impl ApplicationBuilder where S: 'static { /// fn main() { /// let app = Application::default("/") /// .resource("/test", |r| { - /// r.get(|req| httpcodes::HTTPOk); - /// r.handler(Method::HEAD, |req| httpcodes::HTTPMethodNotAllowed); + /// r.method(Method::GET).handler(|_| httpcodes::HTTPOk); + /// r.method(Method::HEAD).handler(|_| httpcodes::HTTPMethodNotAllowed); /// }) /// .finish(); /// } diff --git a/src/lib.rs b/src/lib.rs index 6b90a3917..cc69995ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,6 +75,7 @@ pub mod error; pub mod httpcodes; pub mod multipart; pub mod middlewares; +pub mod pred; pub use error::{Error, Result}; pub use encoding::ContentEncoding; pub use body::{Body, Binary}; @@ -83,7 +84,7 @@ pub use httprequest::{HttpRequest, UrlEncoded}; pub use httpresponse::HttpResponse; pub use payload::{Payload, PayloadItem}; pub use route::{Reply, Json, FromRequest}; -pub use resource::Resource; +pub use resource::{Route, Resource}; pub use recognizer::Params; pub use server::HttpServer; pub use context::HttpContext; diff --git a/src/middlewares/defaultheaders.rs b/src/middlewares/defaultheaders.rs index ae52e684f..cf2503e30 100644 --- a/src/middlewares/defaultheaders.rs +++ b/src/middlewares/defaultheaders.rs @@ -21,8 +21,8 @@ use middlewares::{Response, Middleware}; /// .header("X-Version", "0.2") /// .finish()) /// .resource("/test", |r| { -/// r.get(|req| httpcodes::HTTPOk); -/// r.handler(Method::HEAD, |req| httpcodes::HTTPMethodNotAllowed); +/// r.method(Method::GET).handler(|_| httpcodes::HTTPOk); +/// r.method(Method::HEAD).handler(|_| httpcodes::HTTPMethodNotAllowed); /// }) /// .finish(); /// } diff --git a/src/pred.rs b/src/pred.rs new file mode 100644 index 000000000..2eebd040d --- /dev/null +++ b/src/pred.rs @@ -0,0 +1,148 @@ +//! Route match predicates +#![allow(non_snake_case)] +use std::marker::PhantomData; +use http; +use http::{header, HttpTryFrom}; +use httprequest::HttpRequest; + +/// Trait defines resource route predicate. +/// Predicate can modify request object. It is also possible to +/// to store extra attributes on request by using `.extensions()` method. +pub trait Predicate { + + /// Check if request matches predicate + fn check(&self, &mut HttpRequest) -> bool; + +} + +/// Return predicate that matches if any of supplied predicate matches. +pub fn Any(preds: T) -> Box> + where T: IntoIterator>> +{ + Box::new(AnyPredicate(preds.into_iter().collect())) +} + +struct AnyPredicate(Vec>>); + +impl Predicate for AnyPredicate { + fn check(&self, req: &mut HttpRequest) -> bool { + for p in &self.0 { + if p.check(req) { + return true + } + } + false + } +} + +/// Return predicate that matches if all of supplied predicate matches. +pub fn All(preds: T) -> Box> + where T: IntoIterator>> +{ + Box::new(AllPredicate(preds.into_iter().collect())) +} + +struct AllPredicate(Vec>>); + +impl Predicate for AllPredicate { + fn check(&self, req: &mut HttpRequest) -> bool { + for p in &self.0 { + if !p.check(req) { + return false + } + } + true + } +} + +/// Return predicate that matches if supplied predicate does not match. +pub fn Not(pred: Box>) -> Box> +{ + Box::new(NotPredicate(pred)) +} + +struct NotPredicate(Box>); + +impl Predicate for NotPredicate { + fn check(&self, req: &mut HttpRequest) -> bool { + !self.0.check(req) + } +} + +/// Http method predicate +struct MethodPredicate(http::Method, PhantomData); + +impl Predicate for MethodPredicate { + fn check(&self, req: &mut HttpRequest) -> bool { + *req.method() == self.0 + } +} + +/// Predicate to match *GET* http method +pub fn Get() -> Box> { + Box::new(MethodPredicate(http::Method::GET, PhantomData)) +} + +/// Predicate to match *POST* http method +pub fn Post() -> Box> { + Box::new(MethodPredicate(http::Method::POST, PhantomData)) +} + +/// Predicate to match *PUT* http method +pub fn Put() -> Box> { + Box::new(MethodPredicate(http::Method::PUT, PhantomData)) +} + +/// Predicate to match *DELETE* http method +pub fn Delete() -> Box> { + Box::new(MethodPredicate(http::Method::DELETE, PhantomData)) +} + +/// Predicate to match *HEAD* http method +pub fn Head() -> Box> { + Box::new(MethodPredicate(http::Method::HEAD, PhantomData)) +} + +/// Predicate to match *OPTIONS* http method +pub fn Options() -> Box> { + Box::new(MethodPredicate(http::Method::OPTIONS, PhantomData)) +} + +/// Predicate to match *CONNECT* http method +pub fn Connect() -> Box> { + Box::new(MethodPredicate(http::Method::CONNECT, PhantomData)) +} + +/// Predicate to match *PATCH* http method +pub fn Patch() -> Box> { + Box::new(MethodPredicate(http::Method::PATCH, PhantomData)) +} + +/// Predicate to match *TRACE* http method +pub fn Trace() -> Box> { + Box::new(MethodPredicate(http::Method::TRACE, PhantomData)) +} + +/// Predicate to match specified http method +pub fn Method(method: http::Method) -> Box> { + Box::new(MethodPredicate(method, PhantomData)) +} + +/// Return predicate that matches if request contains specified header and value. +pub fn Header(name: &'static str, value: &'static str) -> Box> +{ + Box::new(HeaderPredicate(header::HeaderName::try_from(name).unwrap(), + header::HeaderValue::from_static(value), + PhantomData)) +} + +struct HeaderPredicate(header::HeaderName, header::HeaderValue, PhantomData); + +impl Predicate for HeaderPredicate { + fn check(&self, req: &mut HttpRequest) -> bool { + if let Some(val) = req.headers().get(&self.0) { + return val == self.1 + } + false + } +} diff --git a/src/recognizer.rs b/src/recognizer.rs index 7531b43aa..aa17c9d7a 100644 --- a/src/recognizer.rs +++ b/src/recognizer.rs @@ -76,11 +76,14 @@ impl Params { /// /// If keyed parameter is not available empty string is used as default value. /// - /// ```rust,ignore - /// fn index(req: HttpRequest) -> String { + /// ```rust + /// # extern crate actix_web; + /// # use actix_web::*; + /// fn index(req: HttpRequest) -> Result { /// let ivalue: isize = req.match_info().query("val")?; - /// format!("isuze value: {:?}", ivalue) + /// Ok(format!("isuze value: {:?}", ivalue)) /// } + /// # fn main() {} /// ``` pub fn query(&self, key: &str) -> Result::Err> { diff --git a/src/resource.rs b/src/resource.rs index b13fda249..c175b683f 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,34 +1,111 @@ use std::marker::PhantomData; -use std::collections::HashMap; use http::Method; use futures::Future; use error::Error; +use pred::{self, Predicate}; use route::{Reply, Handler, FromRequest, RouteHandler, AsyncHandler, WrapHandler}; +use httpcodes::HTTPNotFound; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use httpcodes::{HTTPNotFound, HTTPMethodNotAllowed}; + +/// Resource route definition. Route uses builder-like pattern for configuration. +pub struct Route { + preds: Vec>>, + handler: Box>, +} + +impl Default for Route { + + fn default() -> Route { + Route { + preds: Vec::new(), + handler: Box::new(WrapHandler::new(|_| HTTPNotFound)), + } + } +} + +impl Route { + + fn check(&self, req: &mut HttpRequest) -> bool { + for pred in &self.preds { + if !pred.check(req) { + return false + } + } + true + } + + fn handle(&self, req: HttpRequest) -> Reply { + self.handler.handle(req) + } + + /// Add match predicate to route. + pub fn p(&mut self, p: Box>) -> &mut Self { + self.preds.push(p); + self + } + + /// Add predicates to route. + pub fn predicates

    (&mut self, preds: P) -> &mut Self + where P: IntoIterator>> + { + self.preds.extend(preds.into_iter()); + self + } + + + /// Add method check to route. This method could be called multiple times. + pub fn method(&mut self, method: Method) -> &mut Self { + self.preds.push(pred::Method(method)); + self + } + + /// Set handler function. Usually call to this method is last call + /// during route configuration, because it does not return reference to self. + pub fn handler(&mut self, handler: F) + where F: Fn(HttpRequest) -> R + 'static, + R: FromRequest + 'static, + { + self.handler = Box::new(WrapHandler::new(handler)); + } + + /// Set handler function. + pub fn async(&mut self, handler: F) + where F: Fn(HttpRequest) -> R + 'static, + R: Future + 'static, + { + self.handler = Box::new(AsyncHandler::new(handler)); + } +} /// Http resource /// /// `Resource` is an entry in route table which corresponds to requested URL. /// /// Resource in turn has at least one route. -/// Route corresponds to handling HTTP method by calling route handler. +/// Route consists of an object that implements `Handler` trait (handler) +/// and list of predicates (objects that implement `Predicate` trait). +/// Route uses builder-like pattern for configuration. +/// During request handling, resource object iterate through all routes +/// and check all predicates for specific route, if request matches all predicates route +/// route considired matched and route handler get called. /// /// ```rust /// extern crate actix_web; +/// use actix_web::*; /// /// fn main() { -/// let app = actix_web::Application::default("/") -/// .resource("/", |r| r.get(|_| actix_web::HttpResponse::Ok())) +/// let app = Application::default("/") +/// .resource( +/// "/", |r| r.route().method(Method::GET).handler(|r| HttpResponse::Ok())) /// .finish(); /// } pub struct Resource { name: String, state: PhantomData, - routes: HashMap>>, + routes: Vec>, default: Box>, } @@ -37,8 +114,8 @@ impl Default for Resource { Resource { name: String::new(), state: PhantomData, - routes: HashMap::new(), - default: Box::new(HTTPMethodNotAllowed)} + routes: Vec::new(), + default: Box::new(HTTPNotFound)} } } @@ -48,7 +125,7 @@ impl Resource where S: 'static { Resource { name: String::new(), state: PhantomData, - routes: HashMap::new(), + routes: Vec::new(), default: Box::new(HTTPNotFound)} } @@ -57,65 +134,48 @@ impl Resource where S: 'static { self.name = name.into(); } - /// Register handler for specified method. - pub fn handler(&mut self, method: Method, handler: F) - where F: Fn(HttpRequest) -> R + 'static, - R: FromRequest + 'static, - { - self.routes.insert(method, Box::new(WrapHandler::new(handler))); + /// Register a new route and return mutable reference to *Route* object. + /// *Route* is used for route configuration, i.e. adding predicates, setting up handler. + /// + /// ```rust + /// extern crate actix_web; + /// use actix_web::*; + /// + /// fn main() { + /// let app = Application::default("/") + /// .resource( + /// "/", |r| r.route() + /// .p(pred::Any(vec![pred::Get(), pred::Put()])) + /// .p(pred::Header("Content-Type", "text/plain")) + /// .handler(|r| HttpResponse::Ok())) + /// .finish(); + /// } + pub fn route(&mut self) -> &mut Route { + self.routes.push(Route::default()); + self.routes.last_mut().unwrap() } - /// Register async handler for specified method. - pub fn async(&mut self, method: Method, handler: F) - where F: Fn(HttpRequest) -> R + 'static, - R: Future + 'static, - { - self.routes.insert(method, Box::new(AsyncHandler::new(handler))); + /// Register a new route and add method check to route. + pub fn method(&mut self, method: Method) -> &mut Route { + self.routes.push(Route::default()); + self.routes.last_mut().unwrap().method(method) } /// Default handler is used if no matched route found. - /// By default `HTTPMethodNotAllowed` is used. - pub fn default_handler(&mut self, handler: H) where H: Handler - { + /// By default `HTTPNotFound` is used. + pub fn default_handler(&mut self, handler: H) where H: Handler { self.default = Box::new(WrapHandler::new(handler)); } - - /// Register handler for `GET` method. - pub fn get(&mut self, handler: F) - where F: Fn(HttpRequest) -> R + 'static, - R: FromRequest + 'static, { - self.routes.insert(Method::GET, Box::new(WrapHandler::new(handler))); - } - - /// Register handler for `POST` method. - pub fn post(&mut self, handler: F) - where F: Fn(HttpRequest) -> R + 'static, - R: FromRequest + 'static, { - self.routes.insert(Method::POST, Box::new(WrapHandler::new(handler))); - } - - /// Register handler for `PUT` method. - pub fn put(&mut self, handler: F) - where F: Fn(HttpRequest) -> R + 'static, - R: FromRequest + 'static, { - self.routes.insert(Method::PUT, Box::new(WrapHandler::new(handler))); - } - - /// Register handler for `DELETE` method. - pub fn delete(&mut self, handler: F) - where F: Fn(HttpRequest) -> R + 'static, - R: FromRequest + 'static, { - self.routes.insert(Method::DELETE, Box::new(WrapHandler::new(handler))); - } } impl RouteHandler for Resource { - fn handle(&self, req: HttpRequest) -> Reply { - if let Some(handler) = self.routes.get(req.method()) { - handler.handle(req) - } else { - self.default.handle(req) + fn handle(&self, mut req: HttpRequest) -> Reply { + for route in &self.routes { + if route.check(&mut req) { + return route.handle(req) + } } + self.default.handle(req) } } diff --git a/src/ws.rs b/src/ws.rs index cd9cf0059..e5e467e07 100644 --- a/src/ws.rs +++ b/src/ws.rs @@ -43,7 +43,7 @@ //! //! fn main() { //! Application::default("/") -//! .resource("/ws/", |r| r.get(ws_index)) // <- register websocket route +//! .resource("/ws/", |r| r.method(Method::GET).handler(ws_index)) // <- register websocket route //! .finish(); //! } //! ``` diff --git a/tests/test_server.rs b/tests/test_server.rs index 445536dd4..5cd556c3b 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -16,7 +16,7 @@ fn create_server() -> HttpServer> { HttpServer::new( vec![Application::default("/") .resource("/", |r| - r.handler(Method::GET, |_| httpcodes::HTTPOk)) + r.route().method(Method::GET).handler(|_| httpcodes::HTTPOk)) .finish()]) } @@ -94,7 +94,7 @@ fn test_middlewares() { response: act_num2, finish: act_num3}) .resource("/", |r| - r.handler(Method::GET, |_| httpcodes::HTTPOk)) + r.route().method(Method::GET).handler(|_| httpcodes::HTTPOk)) .finish()]) .serve::<_, ()>("127.0.0.1:58904").unwrap(); sys.run(); From a163e753189b20c2a7d6a78ce924c8fe89c8e023 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Dec 2017 13:34:55 -0800 Subject: [PATCH 019/279] drop tail path pattern --- src/recognizer.rs | 36 ++---------------------------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/src/recognizer.rs b/src/recognizer.rs index aa17c9d7a..333904906 100644 --- a/src/recognizer.rs +++ b/src/recognizer.rs @@ -314,7 +314,6 @@ pub(crate) fn check_pattern(path: &str) { fn parse(pattern: &str) -> String { const DEFAULT_PATTERN: &str = "[^/]+"; - let mut hard_stop = false; let mut re = String::from("^/"); let mut in_param = false; let mut in_param_pattern = false; @@ -327,20 +326,10 @@ fn parse(pattern: &str) -> String { continue; } - if hard_stop { - panic!("Tail '*' section has to be last lection of pattern"); - } - if in_param { // In parameter segment: `{....}` if ch == '}' { - if param_pattern == "*" { - hard_stop = true; - re.push_str( - &format!(r"(?P<{}>[%/[:word:][:punct:][:space:]]+)", ¶m_name)); - } else { - re.push_str(&format!(r"(?P<{}>{})", ¶m_name, ¶m_pattern)); - } + re.push_str(&format!(r"(?P<{}>{})", ¶m_name, ¶m_pattern)); param_name.clear(); param_pattern = String::from(DEFAULT_PATTERN); @@ -398,7 +387,7 @@ mod tests { ("/name/{val}", 2), ("/name/{val}/index.html", 3), ("/v{val}/{val2}/index.html", 4), - ("/v/{tail:*}", 5), + ("/v/{tail:.*}", 5), ]; rec.set_routes(routes); @@ -489,25 +478,4 @@ mod tests { assert_eq!(captures.name("version").unwrap().as_str(), "151"); assert_eq!(captures.name("id").unwrap().as_str(), "adahg32"); } - - #[test] - fn test_tail_param() { - let re = assert_parse("/user/{tail:*}", - r"^/user/(?P[%/[:word:][:punct:][:space:]]+)$"); - assert!(re.is_match("/user/profile")); - assert!(re.is_match("/user/2345")); - assert!(re.is_match("/user/2345/")); - assert!(re.is_match("/user/2345/sdg")); - assert!(re.is_match("/user/2345/sd-_g/")); - assert!(re.is_match("/user/2345/sdg/asddsasd/index.html")); - - let re = assert_parse("/user/v{tail:*}", - r"^/user/v(?P[%/[:word:][:punct:][:space:]]+)$"); - assert!(!re.is_match("/user/2345/")); - assert!(re.is_match("/user/vprofile")); - assert!(re.is_match("/user/v_2345")); - assert!(re.is_match("/user/v2345/sdg")); - assert!(re.is_match("/user/v2345/sd-_g/test.html")); - assert!(re.is_match("/user/v/sdg/asddsasd/index.html")); - } } From 03f7d95d88ee0d82acd1d7a8b9ec7d80a2226c5d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Dec 2017 13:36:28 -0800 Subject: [PATCH 020/279] fix fmratting --- guide/src/qs_3.md | 1 - 1 file changed, 1 deletion(-) diff --git a/guide/src/qs_3.md b/guide/src/qs_3.md index 28841f894..84367a993 100644 --- a/guide/src/qs_3.md +++ b/guide/src/qs_3.md @@ -19,7 +19,6 @@ has same url path prefix: # fn index(req: HttpRequest) -> &'static str { # "Hello world!" # } - # fn main() { let app = Application::default("/prefix") .resource("/index.html", |r| r.method(Method::GET).handler(index)) From f5d6179a349f4837371bd69fbf4de808b7319e7b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Dec 2017 14:07:53 -0800 Subject: [PATCH 021/279] renamed Route::handler to Route::f, added Route::h to register Handler --- README.md | 4 ++-- examples/basic.rs | 4 ++-- examples/state.rs | 5 +++-- examples/websocket.rs | 2 +- guide/src/qs_10.md | 4 ++-- guide/src/qs_12.md | 5 +++-- guide/src/qs_2.md | 4 ++-- guide/src/qs_3.md | 8 ++++---- guide/src/qs_4.md | 2 +- guide/src/qs_5.md | 14 +++++++------- guide/src/qs_6.md | 2 +- guide/src/qs_7.md | 2 +- src/application.rs | 4 ++-- src/httpcodes.rs | 10 +++++++++- src/middlewares/defaultheaders.rs | 4 ++-- src/resource.rs | 18 +++++++++++++----- src/route.rs | 21 +++++++++++++++++++++ src/ws.rs | 2 +- tests/test_server.rs | 4 ++-- 19 files changed, 79 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 695e67675..53defdd26 100644 --- a/README.md +++ b/README.md @@ -108,8 +108,8 @@ fn main() { HttpServer::new( Application::default("/") .middleware(middlewares::Logger::default()) // <- register logger middleware - .resource("/ws/", |r| r.method(Method::GET) - .handler(|req| ws::start(req, MyWebSocket))) // <- websocket route + .resource("/ws/", |r| + r.method(Method::GET).f(|req| ws::start(req, MyWebSocket))) // <- websocket route .route("/", fs::StaticFiles::new("examples/static/", true))) // <- serve static files .serve::<_, ()>("127.0.0.1:8080").unwrap(); diff --git a/examples/basic.rs b/examples/basic.rs index a7e375110..3db4a1f38 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -69,11 +69,11 @@ fn main() { // register simple handle r, handle all methods .handler("/index.html", index) // with path parameters - .resource("/user/{name}/", |r| r.route().method(Method::GET).handler(with_param)) + .resource("/user/{name}/", |r| r.route().method(Method::GET).f(with_param)) // async handler .resource("/async/{name}", |r| r.route().method(Method::GET).async(index_async)) // redirect - .resource("/", |r| r.route().method(Method::GET).handler(|req| { + .resource("/", |r| r.route().method(Method::GET).f(|req| { println!("{:?}", req); httpcodes::HTTPFound diff --git a/examples/state.rs b/examples/state.rs index afdc5e78f..db0347945 100644 --- a/examples/state.rs +++ b/examples/state.rs @@ -65,8 +65,9 @@ fn main() { .middleware(middlewares::Logger::default()) // websocket route .resource( - "/ws/", |r| r.route().method(Method::GET) - .handler(|req| ws::start(req, MyWebSocket{counter: 0}))) + "/ws/", |r| r.route() + .method(Method::GET) + .f(|req| ws::start(req, MyWebSocket{counter: 0}))) // register simple handler, handle all methods .handler("/", index)) .serve::<_, ()>("127.0.0.1:8080").unwrap(); diff --git a/examples/websocket.rs b/examples/websocket.rs index 0772b3c99..f6ead4220 100644 --- a/examples/websocket.rs +++ b/examples/websocket.rs @@ -65,7 +65,7 @@ fn main() { // enable logger .middleware(middlewares::Logger::default()) // websocket route - .resource("/ws/", |r| r.route().method(Method::GET).handler(ws_index)) + .resource("/ws/", |r| r.route().method(Method::GET).f(ws_index)) // static files .route("/", fs::StaticFiles::new("examples/static/", true))) // start http server on 127.0.0.1:8080 diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index 66a049d22..23275cb4f 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -77,8 +77,8 @@ fn main() { .header("X-Version", "0.2") .finish()) .resource("/test", |r| { - r.method(Method::GET).handler(|req| httpcodes::HTTPOk); - r.method(Method::HEAD).handler(|req| httpcodes::HTTPMethodNotAllowed); + r.method(Method::GET).f(|req| httpcodes::HTTPOk); + r.method(Method::HEAD).f(|req| httpcodes::HTTPMethodNotAllowed); }) .finish(); } diff --git a/guide/src/qs_12.md b/guide/src/qs_12.md index a280e0759..b85caacfb 100644 --- a/guide/src/qs_12.md +++ b/guide/src/qs_12.md @@ -2,7 +2,8 @@ ## Individual file -It is possible to serve static files with tail path pattern and `NamedFile`. +It is possible to serve static files with custom path pattern and `NamedFile`. To +match path tail we can use `.*` regex. ```rust extern crate actix_web; @@ -16,7 +17,7 @@ fn index(req: HttpRequest) -> Result { fn main() { Application::default("/") - .resource(r"/a/{tail:*}", |r| r.method(Method::GET).handler(index)) + .resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index)) .finish(); } ``` diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md index 480aacb86..b8179db35 100644 --- a/guide/src/qs_2.md +++ b/guide/src/qs_2.md @@ -49,7 +49,7 @@ request handler with the application's `resource` on a particular *HTTP method* # } # fn main() { let app = Application::default("/") - .resource("/", |r| r.method(Method::GET).handler(index)) + .resource("/", |r| r.method(Method::GET).f(index)) .finish(); # } ``` @@ -80,7 +80,7 @@ fn main() { HttpServer::new( Application::default("/") - .resource("/", |r| r.route().handler(index))) + .resource("/", |r| r.route().f(index))) .serve::<_, ()>("127.0.0.1:8088").unwrap(); println!("Started http server: 127.0.0.1:8088"); diff --git a/guide/src/qs_3.md b/guide/src/qs_3.md index 84367a993..f94e27266 100644 --- a/guide/src/qs_3.md +++ b/guide/src/qs_3.md @@ -21,7 +21,7 @@ has same url path prefix: # } # fn main() { let app = Application::default("/prefix") - .resource("/index.html", |r| r.method(Method::GET).handler(index)) + .resource("/index.html", |r| r.method(Method::GET).f(index)) .finish() # } ``` @@ -41,13 +41,13 @@ use tokio_core::net::TcpStream; fn main() { HttpServer::::new(vec![ Application::default("/app1") - .resource("/", |r| r.route().handler(|r| httpcodes::HTTPOk)) + .resource("/", |r| r.route().f(|r| httpcodes::HTTPOk)) .finish(), Application::default("/app2") - .resource("/", |r| r.route().handler(|r| httpcodes::HTTPOk)) + .resource("/", |r| r.route().f(|r| httpcodes::HTTPOk)) .finish(), Application::default("/") - .resource("/", |r| r.route().handler(|r| httpcodes::HTTPOk)) + .resource("/", |r| r.route().f(|r| httpcodes::HTTPOk)) .finish(), ]); } diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index 0e0ff981c..cc62a0118 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -79,7 +79,7 @@ fn main() { HttpServer::new( Application::default("/") .resource("/", |r| r.method( - Method::GET).handler(|req| {MyObj{name: "user".to_owned()}}))) + Method::GET).f(|req| {MyObj{name: "user".to_owned()}}))) .serve::<_, ()>("127.0.0.1:8088").unwrap(); println!("Started http server: 127.0.0.1:8088"); diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index 4ccceceaf..9e024926f 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -55,8 +55,8 @@ if no route could be matched default response `HTTPMethodNotAllowed` get resturn fn main() { Application::default("/") .resource("/prefix", |r| { - r.method(Method::GET).handler(|r| httpcodes::HTTPOk); - r.method(Method::POST).handler(|r| httpcodes::HTTPForbidden); + r.method(Method::GET).h(httpcodes::HTTPOk); + r.method(Method::POST).h(httpcodes::HTTPForbidden); }) .finish(); } @@ -87,7 +87,7 @@ fn index(req: HttpRequest) -> String { fn main() { Application::default("/") - .resource("/{name}", |r| r.method(Method::GET).handler(index)) + .resource("/{name}", |r| r.method(Method::GET).f(index)) .finish(); } ``` @@ -105,7 +105,7 @@ You can also specify a custom regex in the form `{identifier:regex}`: fn main() { Application::default("/") - .resource(r"{name:\d+}", |r| r.method(Method::GET).handler(index)) + .resource(r"{name:\d+}", |r| r.method(Method::GET).f(index)) .finish(); } ``` @@ -126,7 +126,7 @@ fn index(req: HttpRequest) -> Result { fn main() { Application::default("/") - .resource(r"/a/{v1}/{v2}/", |r| r.route().handler(index)) + .resource(r"/a/{v1}/{v2}/", |r| r.route().f(index)) .finish(); } ``` @@ -138,7 +138,7 @@ It is possible to match path tail with custom `.*` regex. ```rust,ignore fn main() { Application::default("/") - .resource(r"/test/{tail:.*}", |r| r.method(Method::GET).handler(index)) + .resource(r"/test/{tail:.*}", |r| r.method(Method::GET).f(index)) .finish(); } ``` @@ -174,7 +174,7 @@ fn index(req: HttpRequest) -> Result { fn main() { Application::default("/") - .resource(r"/a/{tail:.*}", |r| r.method(Method::GET).handler(index)) + .resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index)) .finish(); } ``` diff --git a/guide/src/qs_6.md b/guide/src/qs_6.md index 096459b78..5e4e220b8 100644 --- a/guide/src/qs_6.md +++ b/guide/src/qs_6.md @@ -30,7 +30,7 @@ fn index(req: HttpRequest) -> String { fn main() { Application::build("/", AppState{counter: Cell::new(0)}) - .resource("/", |r| r.method(Method::GET).handler(index)) + .resource("/", |r| r.method(Method::GET).f(index)) .finish(); } ``` diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index fcf2dec83..8cadb61e7 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -54,7 +54,7 @@ fn index(req: HttpRequest) -> Result> { fn main() { Application::default("/") - .resource(r"/a/{name}", |r| r.method(Method::GET).handler(index)) + .resource(r"/a/{name}", |r| r.method(Method::GET).f(index)) .finish(); } ``` diff --git a/src/application.rs b/src/application.rs index 3e05487e0..8d8bf3d02 100644 --- a/src/application.rs +++ b/src/application.rs @@ -134,8 +134,8 @@ impl ApplicationBuilder where S: 'static { /// fn main() { /// let app = Application::default("/") /// .resource("/test", |r| { - /// r.method(Method::GET).handler(|_| httpcodes::HTTPOk); - /// r.method(Method::HEAD).handler(|_| httpcodes::HTTPMethodNotAllowed); + /// r.method(Method::GET).f(|_| httpcodes::HTTPOk); + /// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed); /// }) /// .finish(); /// } diff --git a/src/httpcodes.rs b/src/httpcodes.rs index 64d308d5f..5aa43b059 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -3,7 +3,7 @@ use http::{StatusCode, Error as HttpError}; use body::Body; -use route::{Reply, RouteHandler, FromRequest}; +use route::{Reply, Handler, RouteHandler, FromRequest}; use httprequest::HttpRequest; use httpresponse::{HttpResponse, HttpResponseBuilder}; @@ -67,6 +67,14 @@ impl StaticResponse { } } +impl Handler for StaticResponse { + type Result = HttpResponse; + + fn handle(&self, _: HttpRequest) -> HttpResponse { + HttpResponse::new(self.0, Body::Empty) + } +} + impl RouteHandler for StaticResponse { fn handle(&self, _: HttpRequest) -> Reply { Reply::response(HttpResponse::new(self.0, Body::Empty)) diff --git a/src/middlewares/defaultheaders.rs b/src/middlewares/defaultheaders.rs index cf2503e30..08d6a923f 100644 --- a/src/middlewares/defaultheaders.rs +++ b/src/middlewares/defaultheaders.rs @@ -21,8 +21,8 @@ use middlewares::{Response, Middleware}; /// .header("X-Version", "0.2") /// .finish()) /// .resource("/test", |r| { -/// r.method(Method::GET).handler(|_| httpcodes::HTTPOk); -/// r.method(Method::HEAD).handler(|_| httpcodes::HTTPMethodNotAllowed); +/// r.method(Method::GET).f(|_| httpcodes::HTTPOk); +/// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed); /// }) /// .finish(); /// } diff --git a/src/resource.rs b/src/resource.rs index c175b683f..fd669210a 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -10,7 +10,10 @@ use httpcodes::HTTPNotFound; use httprequest::HttpRequest; use httpresponse::HttpResponse; -/// Resource route definition. Route uses builder-like pattern for configuration. +/// 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 Route { preds: Vec>>, handler: Box>, @@ -55,16 +58,21 @@ impl Route { self } - /// Add method check to route. This method could be called multiple times. pub fn method(&mut self, method: Method) -> &mut Self { self.preds.push(pred::Method(method)); self } + /// Set handler object. Usually call to this method is last call + /// during route configuration, because it does not return reference to self. + pub fn h>(&mut self, handler: H) { + self.handler = Box::new(WrapHandler::new(handler)); + } + /// Set handler function. Usually call to this method is last call /// during route configuration, because it does not return reference to self. - pub fn handler(&mut self, handler: F) + pub fn f(&mut self, handler: F) where F: Fn(HttpRequest) -> R + 'static, R: FromRequest + 'static, { @@ -99,7 +107,7 @@ impl Route { /// fn main() { /// let app = Application::default("/") /// .resource( -/// "/", |r| r.route().method(Method::GET).handler(|r| HttpResponse::Ok())) +/// "/", |r| r.route().method(Method::GET).f(|r| HttpResponse::Ok())) /// .finish(); /// } pub struct Resource { @@ -147,7 +155,7 @@ impl Resource where S: 'static { /// "/", |r| r.route() /// .p(pred::Any(vec![pred::Get(), pred::Put()])) /// .p(pred::Header("Content-Type", "text/plain")) - /// .handler(|r| HttpResponse::Ok())) + /// .f(|r| HttpResponse::Ok())) /// .finish(); /// } pub fn route(&mut self) -> &mut Route { diff --git a/src/route.rs b/src/route.rs index 25223924a..91d70bced 100644 --- a/src/route.rs +++ b/src/route.rs @@ -227,6 +227,27 @@ impl RouteHandler for AsyncHandler } +/// Json response helper +/// +/// The `Json` type allows you to respond with well-formed JSON data: simply return a value of +/// type Json where T is the type of a structure to serialize into *JSON*. The +/// type `T` must implement the `Serialize` trait from *serde*. +/// +/// ```rust +/// # extern crate actix_web; +/// # #[macro_use] extern crate serde_derive; +/// # use actix_web::*; +/// # +/// #[derive(Serialize)] +/// struct MyObj { +/// name: String, +/// } +/// +/// fn index(req: HttpRequest) -> Result> { +/// Ok(Json(MyObj{name: req.match_info().query("name")?})) +/// } +/// # fn main() {} +/// ``` pub struct Json (pub T); impl FromRequest for Json { diff --git a/src/ws.rs b/src/ws.rs index e5e467e07..6c6f58221 100644 --- a/src/ws.rs +++ b/src/ws.rs @@ -43,7 +43,7 @@ //! //! fn main() { //! Application::default("/") -//! .resource("/ws/", |r| r.method(Method::GET).handler(ws_index)) // <- register websocket route +//! .resource("/ws/", |r| r.method(Method::GET).f(ws_index)) // <- register websocket route //! .finish(); //! } //! ``` diff --git a/tests/test_server.rs b/tests/test_server.rs index 5cd556c3b..45d0f46e4 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -16,7 +16,7 @@ fn create_server() -> HttpServer> { HttpServer::new( vec![Application::default("/") .resource("/", |r| - r.route().method(Method::GET).handler(|_| httpcodes::HTTPOk)) + r.route().method(Method::GET).h(httpcodes::HTTPOk)) .finish()]) } @@ -94,7 +94,7 @@ fn test_middlewares() { response: act_num2, finish: act_num3}) .resource("/", |r| - r.route().method(Method::GET).handler(|_| httpcodes::HTTPOk)) + r.route().method(Method::GET).h(httpcodes::HTTPOk)) .finish()]) .serve::<_, ()>("127.0.0.1:58904").unwrap(); sys.run(); From e332c1242fe233fe8dc8e5c336947d0ae24cfb5d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Dec 2017 14:53:40 -0800 Subject: [PATCH 022/279] use Route for Applicaiton handlers --- README.md | 2 +- examples/basic.rs | 10 ++-- examples/state.rs | 2 +- examples/websocket.rs | 2 +- guide/src/qs_12.md | 2 +- guide/src/qs_5.md | 4 +- src/application.rs | 95 +++++++++++++++--------------------- src/dev.rs | 2 +- src/fs.rs | 4 +- src/{route.rs => handler.rs} | 0 src/httpcodes.rs | 2 +- src/httpresponse.rs | 2 +- src/lib.rs | 4 +- src/pipeline.rs | 2 +- src/resource.rs | 6 +-- src/ws.rs | 2 +- 16 files changed, 61 insertions(+), 80 deletions(-) rename src/{route.rs => handler.rs} (100%) diff --git a/README.md b/README.md index 53defdd26..98b3ea4cd 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ fn main() { .middleware(middlewares::Logger::default()) // <- register logger middleware .resource("/ws/", |r| r.method(Method::GET).f(|req| ws::start(req, MyWebSocket))) // <- websocket route - .route("/", fs::StaticFiles::new("examples/static/", true))) // <- serve static files + .route("/", |r| r.h(fs::StaticFiles::new("examples/static/", true)))) // <- serve static files .serve::<_, ()>("127.0.0.1:8080").unwrap(); Arbiter::system().send(msgs::SystemExit(0)); diff --git a/examples/basic.rs b/examples/basic.rs index 3db4a1f38..3e753895b 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -66,8 +66,8 @@ fn main() { .secure(false) .finish() )) - // register simple handle r, handle all methods - .handler("/index.html", index) + // register simple route, handle all methods + .route("/index.html", |r| r.f(index)) // with path parameters .resource("/user/{name}/", |r| r.route().method(Method::GET).f(with_param)) // async handler @@ -81,15 +81,15 @@ fn main() { .header("LOCATION", "/index.html") .body(Body::Empty) })) - .handler("/test", |req| { + .route("/test", |r| r.f(|req| { match *req.method() { Method::GET => httpcodes::HTTPOk, Method::POST => httpcodes::HTTPMethodNotAllowed, _ => httpcodes::HTTPNotFound, } - }) + })) // static files - .route("/static", fs::StaticFiles::new("examples/static/", true))) + .route("/static", |r| r.h(fs::StaticFiles::new("examples/static/", true)))) .serve::<_, ()>("127.0.0.1:8080").unwrap(); println!("Started http server: 127.0.0.1:8080"); diff --git a/examples/state.rs b/examples/state.rs index db0347945..f7e980413 100644 --- a/examples/state.rs +++ b/examples/state.rs @@ -69,7 +69,7 @@ fn main() { .method(Method::GET) .f(|req| ws::start(req, MyWebSocket{counter: 0}))) // register simple handler, handle all methods - .handler("/", index)) + .route("/", |r| r.f(index))) .serve::<_, ()>("127.0.0.1:8080").unwrap(); println!("Started http server: 127.0.0.1:8080"); diff --git a/examples/websocket.rs b/examples/websocket.rs index f6ead4220..0187f0c08 100644 --- a/examples/websocket.rs +++ b/examples/websocket.rs @@ -67,7 +67,7 @@ fn main() { // websocket route .resource("/ws/", |r| r.route().method(Method::GET).f(ws_index)) // static files - .route("/", fs::StaticFiles::new("examples/static/", true))) + .route("/", |r| r.h(fs::StaticFiles::new("examples/static/", true)))) // start http server on 127.0.0.1:8080 .serve::<_, ()>("127.0.0.1:8080").unwrap(); diff --git a/guide/src/qs_12.md b/guide/src/qs_12.md index b85caacfb..cc64a25ed 100644 --- a/guide/src/qs_12.md +++ b/guide/src/qs_12.md @@ -32,7 +32,7 @@ extern crate actix_web; fn main() { actix_web::Application::default("/") - .route("/static", actix_web::fs::StaticFiles::new(".", true)) + .route("/static", |r| r.h(actix_web::fs::StaticFiles::new(".", true))) .finish(); } ``` diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index 9e024926f..53573871c 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -18,7 +18,7 @@ fn index(req: HttpRequest) -> HttpResponse { fn main() { Application::default("/") - .handler("/prefix", index) + .route("/prefix", |r| r.f(index)) .finish(); } ``` @@ -37,7 +37,7 @@ fn index(req: HttpRequest) -> HttpResponse { fn main() { Application::default("/app") - .handler("/prefix", index) + .route("/prefix", |r| r.f(index)) .finish(); } ``` diff --git a/src/application.rs b/src/application.rs index 8d8bf3d02..20de925c3 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,13 +1,10 @@ use std::rc::Rc; use std::collections::HashMap; -use futures::Future; -use error::Error; -use route::{RouteHandler, Reply, Handler, FromRequest, WrapHandler, AsyncHandler}; -use resource::Resource; +use handler::{Reply, RouteHandler}; +use resource::{Route, Resource}; use recognizer::{RouteRecognizer, check_pattern}; use httprequest::HttpRequest; -use httpresponse::HttpResponse; use channel::HttpHandler; use pipeline::Pipeline; use middlewares::Middleware; @@ -18,7 +15,7 @@ pub struct Application { state: Rc, prefix: String, default: Resource, - handlers: HashMap>>, + routes: Vec<(String, Route)>, router: RouteRecognizer>, middlewares: Rc>>, } @@ -34,10 +31,10 @@ impl Application { } h.handle(req) } else { - for (prefix, handler) in &self.handlers { - if req.path().starts_with(prefix) { - req.set_prefix(prefix.len()); - return handler.handle(req) + for route in &self.routes { + if req.path().starts_with(&route.0) && route.1.check(&mut req) { + req.set_prefix(route.0.len()); + return route.1.handle(req) } } self.default.handle(req) @@ -66,7 +63,7 @@ impl Application<()> { state: (), prefix: prefix.into(), default: Resource::default_not_found(), - handlers: HashMap::new(), + routes: Vec::new(), resources: HashMap::new(), middlewares: Vec::new(), }) @@ -85,7 +82,7 @@ impl Application where S: 'static { state: state, prefix: prefix.into(), default: Resource::default_not_found(), - handlers: HashMap::new(), + routes: Vec::new(), resources: HashMap::new(), middlewares: Vec::new(), }) @@ -97,7 +94,7 @@ struct ApplicationBuilderParts { state: S, prefix: String, default: Resource, - handlers: HashMap>>, + routes: Vec<(String, Route)>, resources: HashMap>, middlewares: Vec>, } @@ -168,8 +165,10 @@ impl ApplicationBuilder where S: 'static { self } - /// This method register handler for specified path prefix. - /// Any path that starts with this prefix matches handler. + /// This method register route for specified path prefix. + /// Route maches based on path prefix, variable path patterns are not available + /// in this case. If you need variable path patterns consider using *resource()* + /// method. /// /// ```rust /// extern crate actix_web; @@ -177,49 +176,31 @@ impl ApplicationBuilder where S: 'static { /// /// fn main() { /// let app = Application::default("/") - /// .handler("/test", |req| { - /// match *req.method() { - /// Method::GET => httpcodes::HTTPOk, - /// Method::POST => httpcodes::HTTPMethodNotAllowed, - /// _ => httpcodes::HTTPNotFound, - /// } - /// }) + /// .route("/test", |r| r.f( + /// |req| { + /// match *req.method() { + /// Method::GET => httpcodes::HTTPOk, + /// Method::POST => httpcodes::HTTPMethodNotAllowed, + /// _ => httpcodes::HTTPNotFound, + /// } + /// } + /// )) /// .finish(); /// } /// ``` - pub fn handler(&mut self, path: P, handler: F) -> &mut Self + pub fn route>(&mut self, path: P, f: F) -> &mut Self where P: Into, - F: Fn(HttpRequest) -> R + 'static, - R: FromRequest + 'static + F: FnOnce(&mut Route) + 'static { - self.parts.as_mut().expect("Use after finish") - .handlers.insert(path.into(), Box::new(WrapHandler::new(handler))); + { + let parts = self.parts.as_mut().expect("Use after finish"); + parts.routes.push((path.into(), Route::default())); + f(&mut parts.routes.last_mut().unwrap().1); + } self } - /// This method register handler for specified path prefix. - /// Any path that starts with this prefix matches handler. - pub fn route(&mut self, path: P, handler: H) -> &mut Self - where P: Into, H: Handler - { - self.parts.as_mut().expect("Use after finish") - .handlers.insert(path.into(), Box::new(WrapHandler::new(handler))); - self - } - - /// This method register async handler for specified path prefix. - /// Any path that starts with this prefix matches handler. - pub fn async(&mut self, path: P, handler: F) -> &mut Self - where F: Fn(HttpRequest) -> R + 'static, - R: Future + 'static, - P: Into, - { - self.parts.as_mut().expect("Use after finish") - .handlers.insert(path.into(), Box::new(AsyncHandler::new(handler))); - self - } - - /// Construct application + /// Register a middleware pub fn middleware(&mut self, mw: T) -> &mut Self where T: Middleware + 'static { @@ -232,27 +213,27 @@ impl ApplicationBuilder where S: 'static { pub fn finish(&mut self) -> Application { let parts = self.parts.take().expect("Use after finish"); - let mut handlers = HashMap::new(); let prefix = if parts.prefix.ends_with('/') { parts.prefix } else { parts.prefix + "/" }; - let mut routes = Vec::new(); + let mut resources = Vec::new(); for (path, handler) in parts.resources { - routes.push((path, handler)) + resources.push((path, handler)) } - for (path, handler) in parts.handlers { - handlers.insert(prefix.clone() + path.trim_left_matches('/'), handler); + let mut routes = Vec::new(); + for (path, route) in parts.routes { + routes.push((prefix.clone() + path.trim_left_matches('/'), route)); } Application { state: Rc::new(parts.state), prefix: prefix.clone(), default: parts.default, - handlers: handlers, - router: RouteRecognizer::new(prefix, routes), + routes: routes, + router: RouteRecognizer::new(prefix, resources), middlewares: Rc::new(parts.middlewares), } } diff --git a/src/dev.rs b/src/dev.rs index 7e597c9d6..70f654b2c 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -9,7 +9,7 @@ //! ``` // dev specific -pub use route::Handler; +pub use handler::Handler; pub use pipeline::Pipeline; pub use channel::{HttpChannel, HttpHandler}; pub use recognizer::{FromParam, RouteRecognizer}; diff --git a/src/fs.rs b/src/fs.rs index df275ea83..1bf678eea 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -9,7 +9,7 @@ use std::path::{Path, PathBuf}; use std::ops::{Deref, DerefMut}; use mime_guess::get_mime_type; -use route::{Handler, FromRequest}; +use handler::{Handler, FromRequest}; use recognizer::FromParam; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -198,7 +198,7 @@ impl FromRequest for FilesystemElement { /// /// fn main() { /// let app = actix_web::Application::default("/") -/// .route("/static", actix_web::fs::StaticFiles::new(".", true)) +/// .route("/static", |r| r.h(actix_web::fs::StaticFiles::new(".", true))) /// .finish(); /// } /// ``` diff --git a/src/route.rs b/src/handler.rs similarity index 100% rename from src/route.rs rename to src/handler.rs diff --git a/src/httpcodes.rs b/src/httpcodes.rs index 5aa43b059..e2af3ec50 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -3,7 +3,7 @@ use http::{StatusCode, Error as HttpError}; use body::Body; -use route::{Reply, Handler, RouteHandler, FromRequest}; +use handler::{Reply, Handler, RouteHandler, FromRequest}; use httprequest::HttpRequest; use httpresponse::{HttpResponse, HttpResponseBuilder}; diff --git a/src/httpresponse.rs b/src/httpresponse.rs index cfb83fa0b..4a447abeb 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -11,7 +11,7 @@ use serde::Serialize; use Cookie; use body::Body; use error::Error; -use route::FromRequest; +use handler::FromRequest; use encoding::ContentEncoding; use httprequest::HttpRequest; diff --git a/src/lib.rs b/src/lib.rs index cc69995ad..070514add 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,7 +57,7 @@ mod httpresponse; mod payload; mod resource; mod recognizer; -mod route; +mod handler; mod pipeline; mod server; mod channel; @@ -83,7 +83,7 @@ pub use application::Application; pub use httprequest::{HttpRequest, UrlEncoded}; pub use httpresponse::HttpResponse; pub use payload::{Payload, PayloadItem}; -pub use route::{Reply, Json, FromRequest}; +pub use handler::{Reply, Json, FromRequest}; pub use resource::{Route, Resource}; pub use recognizer::Params; pub use server::HttpServer; diff --git a/src/pipeline.rs b/src/pipeline.rs index 85473fc74..4c9c79369 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -8,7 +8,7 @@ use futures::task::{Task as FutureTask, current as current_task}; use body::{Body, BodyStream}; use context::{Frame, IoContext}; use error::{Error, UnexpectedTaskFrame}; -use route::{Reply, ReplyItem}; +use handler::{Reply, ReplyItem}; use h1writer::{Writer, WriterState}; use httprequest::HttpRequest; use httpresponse::HttpResponse; diff --git a/src/resource.rs b/src/resource.rs index fd669210a..a7b41d564 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -5,7 +5,7 @@ use futures::Future; use error::Error; use pred::{self, Predicate}; -use route::{Reply, Handler, FromRequest, RouteHandler, AsyncHandler, WrapHandler}; +use handler::{Reply, Handler, FromRequest, RouteHandler, AsyncHandler, WrapHandler}; use httpcodes::HTTPNotFound; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -31,7 +31,7 @@ impl Default for Route { impl Route { - fn check(&self, req: &mut HttpRequest) -> bool { + pub(crate) fn check(&self, req: &mut HttpRequest) -> bool { for pred in &self.preds { if !pred.check(req) { return false @@ -40,7 +40,7 @@ impl Route { true } - fn handle(&self, req: HttpRequest) -> Reply { + pub(crate) fn handle(&self, req: HttpRequest) -> Reply { self.handler.handle(req) } diff --git a/src/ws.rs b/src/ws.rs index 6c6f58221..21b630be7 100644 --- a/src/ws.rs +++ b/src/ws.rs @@ -56,7 +56,7 @@ use actix::{Actor, AsyncContext, ResponseType, StreamHandler}; use body::Body; use context::HttpContext; -use route::Reply; +use handler::Reply; use payload::Payload; use error::{Error, WsHandshakeError}; use httprequest::HttpRequest; From e98972e93be747a68c0ac0d74e0d63db0683fbfb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Dec 2017 15:35:07 -0800 Subject: [PATCH 023/279] exclude example from code coverage --- .travis.yml | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 68d29e255..195f539c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,20 +50,7 @@ after_success: - | if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) - cargo tarpaulin --out Xml + cargo tarpaulin --exclude examples --out Xml bash <(curl -s https://codecov.io/bash) - #wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz && - #tar xzf master.tar.gz && - #cd kcov-master && - #mkdir build && - #cd build && - #cmake .. && - #make && - #make install DESTDIR=../../kcov-build && - #cd ../.. && - #rm -rf kcov-master && - #for file in target/debug/actix_web-*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done && - #for file in target/debug/test_*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done && - #bash <(curl -s https://codecov.io/bash) && echo "Uploaded code coverage" fi From f4e9fc7b6a147cdf1dc3b361f380814817960b2b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Dec 2017 16:09:22 -0800 Subject: [PATCH 024/279] rename async to a --- .travis.yml | 2 +- examples/basic.rs | 2 +- guide/src/qs_4.md | 59 +++++++++++++++++++----------- src/application.rs | 3 +- src/lib.rs | 4 ++- src/resource.rs | 89 ++-------------------------------------------- src/route.rs | 88 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 137 insertions(+), 110 deletions(-) create mode 100644 src/route.rs diff --git a/.travis.yml b/.travis.yml index 195f539c4..72e0dbb38 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,7 +50,7 @@ after_success: - | if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) - cargo tarpaulin --exclude examples --out Xml + cargo tarpaulin --exclude ./examples/* --out Xml bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi diff --git a/examples/basic.rs b/examples/basic.rs index 3e753895b..cf9c78aa3 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -71,7 +71,7 @@ fn main() { // with path parameters .resource("/user/{name}/", |r| r.route().method(Method::GET).f(with_param)) // async handler - .resource("/async/{name}", |r| r.route().method(Method::GET).async(index_async)) + .resource("/async/{name}", |r| r.route().method(Method::GET).a(index_async)) // redirect .resource("/", |r| r.route().method(Method::GET).f(|req| { println!("{:?}", req); diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index cc62a0118..c8683591d 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -96,31 +96,50 @@ There are two different types of async handlers. Response object could be generated asynchronously. In this case handle must return `Future` object that resolves to `HttpResponse`, i.e: -```rust,ignore -fn index(req: HttpRequest) -> Box> { - ... -} -``` +```rust +# extern crate actix_web; +# extern crate futures; +# extern crate bytes; +# use actix_web::*; +# use bytes::Bytes; +# use futures::stream::once; +# use futures::future::{FutureResult, result}; +fn index(req: HttpRequest) -> FutureResult { -This handler can be registered with `ApplicationBuilder::async()` and -`Resource::async()` methods. - -Or response body can be generated asynchronously. In this case body -must implement stream trait `Stream`, i.e: - - -```rust,ignore -fn index(req: HttpRequest) -> HttpResponse { - let body: Box> = Box::new(SomeStream::new()); - - HttpResponse::Ok(). - .content_type("application/json") - .body(Body::Streaming(body)).unwrap() + result(HttpResponse::Ok() + .content_type("text/html") + .body(format!("Hello!")) + .map_err(|e| e.into())) } fn main() { Application::default("/") - .async("/async", index) + .route("/async", |r| r.a(index)) + .finish(); +} +``` + +Or response body can be generated asynchronously. In this case body +must implement stream trait `Stream`, i.e: + +```rust +# extern crate actix_web; +# extern crate futures; +# extern crate bytes; +# use actix_web::*; +# use bytes::Bytes; +# use futures::stream::once; +fn index(req: HttpRequest) -> HttpResponse { + let body = once(Ok(Bytes::from_static(b"test"))); + + HttpResponse::Ok() + .content_type("application/json") + .body(Body::Streaming(Box::new(body))).unwrap() +} + +fn main() { + Application::default("/") + .route("/async", |r| r.f(index)) .finish(); } ``` diff --git a/src/application.rs b/src/application.rs index 20de925c3..822a7c472 100644 --- a/src/application.rs +++ b/src/application.rs @@ -2,7 +2,8 @@ use std::rc::Rc; use std::collections::HashMap; use handler::{Reply, RouteHandler}; -use resource::{Route, Resource}; +use route::Route; +use resource::Resource; use recognizer::{RouteRecognizer, check_pattern}; use httprequest::HttpRequest; use channel::HttpHandler; diff --git a/src/lib.rs b/src/lib.rs index 070514add..d365c87c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,6 +55,7 @@ mod encoding; mod httprequest; mod httpresponse; mod payload; +mod route; mod resource; mod recognizer; mod handler; @@ -84,7 +85,8 @@ pub use httprequest::{HttpRequest, UrlEncoded}; pub use httpresponse::HttpResponse; pub use payload::{Payload, PayloadItem}; pub use handler::{Reply, Json, FromRequest}; -pub use resource::{Route, Resource}; +pub use route::Route; +pub use resource::Resource; pub use recognizer::Params; pub use server::HttpServer; pub use context::HttpContext; diff --git a/src/resource.rs b/src/resource.rs index a7b41d564..989d78f80 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,96 +1,13 @@ use std::marker::PhantomData; use http::Method; -use futures::Future; -use error::Error; -use pred::{self, Predicate}; -use handler::{Reply, Handler, FromRequest, RouteHandler, AsyncHandler, WrapHandler}; +use route::Route; +use handler::{Reply, Handler, RouteHandler, WrapHandler}; use httpcodes::HTTPNotFound; use httprequest::HttpRequest; -use httpresponse::HttpResponse; -/// 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 Route { - preds: Vec>>, - handler: Box>, -} - -impl Default for Route { - - fn default() -> Route { - Route { - preds: Vec::new(), - handler: Box::new(WrapHandler::new(|_| HTTPNotFound)), - } - } -} - -impl Route { - - pub(crate) fn check(&self, req: &mut HttpRequest) -> bool { - for pred in &self.preds { - if !pred.check(req) { - return false - } - } - true - } - - pub(crate) fn handle(&self, req: HttpRequest) -> Reply { - self.handler.handle(req) - } - - /// Add match predicate to route. - pub fn p(&mut self, p: Box>) -> &mut Self { - self.preds.push(p); - self - } - - /// Add predicates to route. - pub fn predicates

    (&mut self, preds: P) -> &mut Self - where P: IntoIterator>> - { - self.preds.extend(preds.into_iter()); - self - } - - /// Add method check to route. This method could be called multiple times. - pub fn method(&mut self, method: Method) -> &mut Self { - self.preds.push(pred::Method(method)); - self - } - - /// Set handler object. Usually call to this method is last call - /// during route configuration, because it does not return reference to self. - pub fn h>(&mut self, handler: H) { - self.handler = Box::new(WrapHandler::new(handler)); - } - - /// Set handler function. Usually call to this method is last call - /// during route configuration, because it does not return reference to self. - pub fn f(&mut self, handler: F) - where F: Fn(HttpRequest) -> R + 'static, - R: FromRequest + 'static, - { - self.handler = Box::new(WrapHandler::new(handler)); - } - - /// Set handler function. - pub fn async(&mut self, handler: F) - where F: Fn(HttpRequest) -> R + 'static, - R: Future + 'static, - { - self.handler = Box::new(AsyncHandler::new(handler)); - } -} - -/// Http resource -/// -/// `Resource` is an entry in route table which corresponds to requested URL. +/// *Resource* is an entry in route table which corresponds to requested URL. /// /// Resource in turn has at least one route. /// Route consists of an object that implements `Handler` trait (handler) diff --git a/src/route.rs b/src/route.rs new file mode 100644 index 000000000..7739ff71b --- /dev/null +++ b/src/route.rs @@ -0,0 +1,88 @@ +use http::Method; +use futures::Future; + +use error::Error; +use pred::{self, Predicate}; +use handler::{Reply, Handler, FromRequest, RouteHandler, AsyncHandler, WrapHandler}; +use httpcodes::HTTPNotFound; +use httprequest::HttpRequest; +use httpresponse::HttpResponse; + + +/// 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 Route { + preds: Vec>>, + handler: Box>, +} + +impl Default for Route { + + fn default() -> Route { + Route { + preds: Vec::new(), + handler: Box::new(WrapHandler::new(|_| HTTPNotFound)), + } + } +} + +impl Route { + + pub(crate) fn check(&self, req: &mut HttpRequest) -> bool { + for pred in &self.preds { + if !pred.check(req) { + return false + } + } + true + } + + pub(crate) fn handle(&self, req: HttpRequest) -> Reply { + self.handler.handle(req) + } + + /// Add match predicate to route. + pub fn p(&mut self, p: Box>) -> &mut Self { + self.preds.push(p); + self + } + + /// Add predicates to route. + pub fn predicates

    (&mut self, preds: P) -> &mut Self + where P: IntoIterator>> + { + self.preds.extend(preds.into_iter()); + self + } + + /// Add method check to route. This method could be called multiple times. + pub fn method(&mut self, method: Method) -> &mut Self { + self.preds.push(pred::Method(method)); + self + } + + /// Set handler object. Usually call to this method is last call + /// during route configuration, because it does not return reference to self. + pub fn h>(&mut self, handler: H) { + self.handler = Box::new(WrapHandler::new(handler)); + } + + /// Set handler function. Usually call to this method is last call + /// during route configuration, because it does not return reference to self. + pub fn f(&mut self, handler: F) + where F: Fn(HttpRequest) -> R + 'static, + R: FromRequest + 'static, + { + self.handler = Box::new(WrapHandler::new(handler)); + } + + /// Set async handler function. + pub fn a(&mut self, handler: F) + where F: Fn(HttpRequest) -> R + 'static, + R: Future + 'static, + { + self.handler = Box::new(AsyncHandler::new(handler)); + } +} From 2950c90c77a1f34bb1d1596cd76a349bc147c022 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Dec 2017 16:26:40 -0800 Subject: [PATCH 025/279] doc fixes --- guide/src/SUMMARY.md | 2 +- guide/src/qs_12.md | 13 +++++++------ guide/src/qs_13.md | 10 +++++----- guide/src/qs_6.md | 19 ++++++++++--------- guide/src/qs_7.md | 4 ++-- 5 files changed, 25 insertions(+), 23 deletions(-) diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 1828400d3..85332cf43 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -4,8 +4,8 @@ - [Getting Started](./qs_2.md) - [Application](./qs_3.md) - [Handler](./qs_4.md) +- [State](./qs_6.md) - [Resources and Routes](./qs_5.md) -- [Application state](./qs_6.md) - [Request & Response](./qs_7.md) - [WebSockets](./qs_9.md) - [Middlewares](./qs_10.md) diff --git a/guide/src/qs_12.md b/guide/src/qs_12.md index cc64a25ed..c16ac0f30 100644 --- a/guide/src/qs_12.md +++ b/guide/src/qs_12.md @@ -3,10 +3,10 @@ ## Individual file It is possible to serve static files with custom path pattern and `NamedFile`. To -match path tail we can use `.*` regex. +match path tail we can use `[.*]` regex. ```rust -extern crate actix_web; +# extern crate actix_web; use actix_web::*; use std::path::PathBuf; @@ -24,15 +24,16 @@ fn main() { ## Directory -To serve files from specific directory and sub-directories `StaticFiles` type could be used. +To serve files from specific directory and sub-directories `StaticFiles` could be used. `StaticFiles` could be registered with `Application::route` method. ```rust -extern crate actix_web; +# extern crate actix_web; +use actix_web::*; fn main() { - actix_web::Application::default("/") - .route("/static", |r| r.h(actix_web::fs::StaticFiles::new(".", true))) + Application::default("/") + .route("/static", |r| r.h(fs::StaticFiles::new(".", true))) .finish(); } ``` diff --git a/guide/src/qs_13.md b/guide/src/qs_13.md index 5a2472d4d..b567f6286 100644 --- a/guide/src/qs_13.md +++ b/guide/src/qs_13.md @@ -1,10 +1,10 @@ # HTTP/2 -Actix web automatically upgrades connection to `http/2` if possible. +Actix web automatically upgrades connection to *HTTP/2* if possible. ## Negotiation -`HTTP/2` protocol over tls without prior knowlage requires +*HTTP/2* protocol over tls without prior knowlage requires [tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only `rust-openssl` has support. Turn on `alpn` feature to enable `alpn` negotiation. With enable `alpn` feature `HttpServer` provides @@ -27,14 +27,14 @@ fn main() { HttpServer::new( Application::default("/") - .handler("/index.html", index) + .route("/index.html", |r| r.f(index)) .serve_tls::<_, ()>("127.0.0.1:8080", pkcs12).unwrap(); } ``` -Upgrade to `http/2` schema described in +Upgrade to *HTTP/2* schema described in [rfc section 3.2](https://http2.github.io/http2-spec/#rfc.section.3.2) is not supported. -Starting `http/2` with prior knowledge is supported for both clear text connection +Starting *HTTP/2* with prior knowledge is supported for both clear text connection and tls connection. [rfc section 3.4](https://http2.github.io/http2-spec/#rfc.section.3.4) Please check [example](https://github.com/actix/actix-web/tree/master/examples/tls) diff --git a/guide/src/qs_6.md b/guide/src/qs_6.md index 5e4e220b8..46802014d 100644 --- a/guide/src/qs_6.md +++ b/guide/src/qs_6.md @@ -1,20 +1,21 @@ # Application state -Application state is shared with all routes within same application. -State could be accessed with `HttpRequest::state()` method. It is read-only +Application state is shared with all routes and resources within same application. +State could be accessed with `HttpRequest::state()` method as a read-only item but interior mutability pattern with `RefCell` could be used to archive state mutability. -State could be accessed with `HttpRequest::state()` method or -`HttpContext::state()` in case of http actor. +State could be accessed with `HttpContext::state()` in case of http actor. +State also available to route matching predicates. State is not available +to application middlewares, middlewares receives `HttpRequest<()>` object. Let's write simple application that uses shared state. We are going to store requests count in the state: ```rust -extern crate actix; -extern crate actix_web; - -use std::cell::Cell; +# extern crate actix; +# extern crate actix_web; +# use actix_web::*; +use std::cell::Cell; // This struct represents state struct AppState { @@ -25,7 +26,7 @@ fn index(req: HttpRequest) -> String { let count = req.state().counter.get() + 1; // <- get count req.state().counter.set(count); // <- store new count in state - format!("Request number: {}", count) // <- response with count + format!("Request number: {}", count) // <- response with count } fn main() { diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 8cadb61e7..5e1901c57 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -21,7 +21,7 @@ If other content encoding is selected the compression is enforced for this codec to enable `brotli` response's body compression use `ContentEncoding::Br`: ```rust -extern crate actix_web; +# extern crate actix_web; use actix_web::*; fn index(req: HttpRequest) -> HttpResponse { @@ -39,7 +39,7 @@ type Json where T is the type of a structure to serialize into *JSON*. The type `T` must implement the `Serialize` trait from *serde*. ```rust -extern crate actix_web; +# extern crate actix_web; #[macro_use] extern crate serde_derive; use actix_web::*; From fd6b243cd6d1b3a0896daf988897b982447acd11 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Dec 2017 16:32:31 -0800 Subject: [PATCH 026/279] update examples --- examples/tls/src/main.rs | 4 ++-- examples/websocket-chat/src/main.rs | 6 +++--- src/route.rs | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/tls/src/main.rs b/examples/tls/src/main.rs index 81574e5e5..3368c4ebc 100644 --- a/examples/tls/src/main.rs +++ b/examples/tls/src/main.rs @@ -34,9 +34,9 @@ fn main() { // enable logger .middleware(middlewares::Logger::default()) // register simple handler, handle all methods - .handler("/index.html", index) + .route("/index.html", |r| r.f(index)) // with path parameters - .resource("/", |r| r.handler(Method::GET, |req| { + .resource("/", |r| r.method(Method::GET).f(|req| { httpcodes::HTTPFound .build() .header("LOCATION", "/index.html") diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs index 8a83e35dc..797d4690c 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -199,16 +199,16 @@ fn main() { HttpServer::new( Application::build("/", state) // redirect to websocket.html - .resource("/", |r| r.handler(Method::GET, |req| { + .resource("/", |r| r.method(Method::GET).f(|req| { httpcodes::HTTPFound .build() .header("LOCATION", "/static/websocket.html") .body(Body::Empty) })) // websocket - .resource("/ws/", |r| r.get(chat_route)) + .resource("/ws/", |r| r.route().f(chat_route)) // static resources - .route("/static", fs::StaticFiles::new("static/", true))) + .route("/static", |r| r.h(fs::StaticFiles::new("static/", true)))) .serve::<_, ()>("127.0.0.1:8080").unwrap(); let _ = sys.run(); diff --git a/src/route.rs b/src/route.rs index 7739ff71b..64f037d8d 100644 --- a/src/route.rs +++ b/src/route.rs @@ -43,9 +43,9 @@ impl Route { self.handler.handle(req) } - /// Add match predicate to route. - pub fn p(&mut self, p: Box>) -> &mut Self { - self.preds.push(p); + /// Add method check to route. This method could be called multiple times. + pub fn method(&mut self, method: Method) -> &mut Self { + self.preds.push(pred::Method(method)); self } @@ -57,9 +57,9 @@ impl Route { self } - /// Add method check to route. This method could be called multiple times. - pub fn method(&mut self, method: Method) -> &mut Self { - self.preds.push(pred::Method(method)); + /// Add match predicate to route. + pub fn p(&mut self, p: Box>) -> &mut Self { + self.preds.push(p); self } From 3c9b6ea619ccde4d33b09d74fe6113ff6f6c48a9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Dec 2017 20:38:38 -0800 Subject: [PATCH 027/279] update guide --- guide/src/qs_5.md | 18 ++++++++++++------ guide/src/qs_7.md | 26 ++++++++++++++++++++++++-- src/httpresponse.rs | 1 + 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index 53573871c..a03bf91ee 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -11,7 +11,7 @@ Prefix handler: ```rust # extern crate actix_web; # use actix_web::*; - +# fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } @@ -30,7 +30,7 @@ Application prefix combines with handler prefix i.e ```rust # extern crate actix_web; # use actix_web::*; - +# fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } @@ -51,7 +51,7 @@ if no route could be matched default response `HTTPMethodNotAllowed` get resturn ```rust # extern crate actix_web; # use actix_web::*; - +# fn main() { Application::default("/") .resource("/prefix", |r| { @@ -102,7 +102,7 @@ You can also specify a custom regex in the form `{identifier:regex}`: # fn index(req: HttpRequest) -> String { # format!("Hello, {}", &req.match_info()["name"]) # } - +# fn main() { Application::default("/") .resource(r"{name:\d+}", |r| r.method(Method::GET).f(index)) @@ -115,7 +115,7 @@ implements `FromParam` trait. For example most of standard integer types implements `FromParam` trait. i.e.: ```rust -extern crate actix_web; +# extern crate actix_web; use actix_web::*; fn index(req: HttpRequest) -> Result { @@ -135,7 +135,13 @@ For this example for path '/a/1/2/', values v1 and v2 will resolve to "1" and "2 It is possible to match path tail with custom `.*` regex. -```rust,ignore +```rust +# extern crate actix_web; +# use actix_web::*; +# +# fn index(req: HttpRequest) -> HttpResponse { +# unimplemented!() +# } fn main() { Application::default("/") .resource(r"/test/{tail:.*}", |r| r.method(Method::GET).f(index)) diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 5e1901c57..7668f0fef 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -1,9 +1,31 @@ # HttpRequest & HttpResponse +## Response + +Builder-like patter is used to construct an instance of `HttpResponse`. +`HttpResponse` provides several method that returns `HttpResponseBuilder` instance, +which is implements various convinience methods that helps build response. +Check [documentation](../actix_web/dev/struct.HttpResponseBuilder.html) +for type description. Methods `.body`, `.finish`, `.json` finalizes response creation, +if this methods get call for the same builder instance, builder will panic. + +```rust +# extern crate actix_web; +use actix_web::*; + +fn index(req: HttpRequest) -> HttpResponse { + HttpResponse::Ok() + .content_encoding(ContentEncoding::Br) + .content_type("plain/text") + .header("X-Hdr", "sample") + .body("data").unwrap() +} +# fn main() {} +``` + ## Content encoding -Actix automatically *compress*/*decompress* payload. -Following codecs are supported: +Actix automatically *compress*/*decompress* payload. Following codecs are supported: * Brotli * Gzip diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 4a447abeb..1bda6f186 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -373,6 +373,7 @@ impl HttpResponseBuilder { self } + /// Calls provided closure with builder reference if value is true. pub fn if_true(&mut self, value: bool, f: F) -> &mut Self where F: Fn(&mut HttpResponseBuilder) + 'static { From a83d9b24ae47926dbf2e6b053f94d6b94a209374 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Dec 2017 11:31:35 -0800 Subject: [PATCH 028/279] extrat elements of path pattern --- src/application.rs | 24 ++++++++++++++++-------- src/recognizer.rs | 34 +++++++++++++++++++++++----------- src/resource.rs | 4 ++++ 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/application.rs b/src/application.rs index 822a7c472..e007ea71d 100644 --- a/src/application.rs +++ b/src/application.rs @@ -10,6 +10,19 @@ use channel::HttpHandler; use pipeline::Pipeline; use middlewares::Middleware; +pub struct Router(Rc>>); + +impl Router { + pub fn new(prefix: String, map: HashMap>) -> Router + { + let mut resources = Vec::new(); + for (path, resource) in map { + resources.push((path, resource.get_name(), resource)) + } + + Router(Rc::new(RouteRecognizer::new(prefix, resources))) + } +} /// Application pub struct Application { @@ -17,7 +30,7 @@ pub struct Application { prefix: String, default: Resource, routes: Vec<(String, Route)>, - router: RouteRecognizer>, + router: Router, middlewares: Rc>>, } @@ -26,7 +39,7 @@ impl Application { fn run(&self, req: HttpRequest) -> Reply { let mut req = req.with_state(Rc::clone(&self.state)); - if let Some((params, h)) = self.router.recognize(req.path()) { + if let Some((params, h)) = self.router.0.recognize(req.path()) { if let Some(params) = params { req.set_match_info(params); } @@ -220,11 +233,6 @@ impl ApplicationBuilder where S: 'static { parts.prefix + "/" }; - let mut resources = Vec::new(); - for (path, handler) in parts.resources { - resources.push((path, handler)) - } - let mut routes = Vec::new(); for (path, route) in parts.routes { routes.push((prefix.clone() + path.trim_left_matches('/'), route)); @@ -234,7 +242,7 @@ impl ApplicationBuilder where S: 'static { prefix: prefix.clone(), default: parts.default, routes: routes, - router: RouteRecognizer::new(prefix, resources), + router: Router::new(prefix, parts.resources), middlewares: Rc::new(parts.middlewares), } } diff --git a/src/recognizer.rs b/src/recognizer.rs index 333904906..4b08aaece 100644 --- a/src/recognizer.rs +++ b/src/recognizer.rs @@ -201,7 +201,6 @@ FROM_STR!(std::net::SocketAddr); FROM_STR!(std::net::SocketAddrV4); FROM_STR!(std::net::SocketAddrV6); - pub struct RouteRecognizer { prefix: usize, patterns: RegexSet, @@ -222,13 +221,13 @@ impl Default for RouteRecognizer { impl RouteRecognizer { pub fn new, U>(prefix: P, routes: U) -> Self - where U: IntoIterator + where U: IntoIterator, T)> { let mut paths = Vec::new(); let mut handlers = Vec::new(); for item in routes { - let pat = parse(&item.0); - handlers.push((Pattern::new(&pat), item.1)); + let (pat, elements) = parse(&item.0); + handlers.push((Pattern::new(&pat, elements), item.2)); paths.push(pat); }; let regset = RegexSet::new(&paths); @@ -240,12 +239,12 @@ impl RouteRecognizer { } } - pub fn set_routes(&mut self, routes: Vec<(&str, T)>) { + pub fn set_routes(&mut self, routes: Vec<(&str, Option<&str>, T)>) { let mut paths = Vec::new(); let mut handlers = Vec::new(); for item in routes { - let pat = parse(item.0); - handlers.push((Pattern::new(&pat), item.1)); + let (pat, elements) = parse(item.0); + handlers.push((Pattern::new(&pat, elements), item.2)); paths.push(pat); }; self.patterns = RegexSet::new(&paths).unwrap(); @@ -276,13 +275,19 @@ impl RouteRecognizer { } } +enum PatternElement { + Str(String), + Var(String), +} + struct Pattern { re: Regex, names: Rc>, + elements: Vec, } impl Pattern { - fn new(pattern: &str) -> Self { + fn new(pattern: &str, elements: Vec) -> Self { let re = Regex::new(pattern).unwrap(); let names = re.capture_names() .enumerate() @@ -292,6 +297,7 @@ impl Pattern { Pattern { re, names: Rc::new(names), + elements: elements, } } @@ -306,19 +312,21 @@ impl Pattern { } pub(crate) fn check_pattern(path: &str) { - if let Err(err) = Regex::new(&parse(path)) { + if let Err(err) = Regex::new(&parse(path).0) { panic!("Wrong path pattern: \"{}\" {}", path, err); } } -fn parse(pattern: &str) -> String { +fn parse(pattern: &str) -> (String, Vec) { const DEFAULT_PATTERN: &str = "[^/]+"; let mut re = String::from("^/"); + let mut el = String::new(); let mut in_param = false; let mut in_param_pattern = false; let mut param_name = String::new(); let mut param_pattern = String::from(DEFAULT_PATTERN); + let mut elems = Vec::new(); for (index, ch) in pattern.chars().enumerate() { // All routes must have a leading slash so its optional to have one @@ -329,6 +337,7 @@ fn parse(pattern: &str) -> String { if in_param { // In parameter segment: `{....}` if ch == '}' { + elems.push(PatternElement::Var(String::from(String::from(param_name.as_str())))); re.push_str(&format!(r"(?P<{}>{})", ¶m_name, ¶m_pattern)); param_name.clear(); @@ -350,13 +359,16 @@ fn parse(pattern: &str) -> String { } } else if ch == '{' { in_param = true; + elems.push(PatternElement::Str(String::from(el.as_str()))); + el.clear(); } else { re.push(ch); + el.push(ch); } } re.push('$'); - re + (re, elems) } #[cfg(test)] diff --git a/src/resource.rs b/src/resource.rs index 989d78f80..28fc1a0cf 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -59,6 +59,10 @@ impl Resource where S: 'static { self.name = name.into(); } + pub(crate) fn get_name(&self) -> Option { + if self.name.is_empty() { None } else { Some(self.name.clone()) } + } + /// Register a new route and return mutable reference to *Route* object. /// *Route* is used for route configuration, i.e. adding predicates, setting up handler. /// From 86d7290f9ef6a5d7d92d599aaeeffcd4e841b9bb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Dec 2017 11:43:41 -0800 Subject: [PATCH 029/279] update tests --- src/recognizer.rs | 12 ++++++------ tests/test_httprequest.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/recognizer.rs b/src/recognizer.rs index 4b08aaece..2ee596794 100644 --- a/src/recognizer.rs +++ b/src/recognizer.rs @@ -395,11 +395,11 @@ mod tests { let mut rec = RouteRecognizer::::default(); let routes = vec![ - ("/name", 1), - ("/name/{val}", 2), - ("/name/{val}/index.html", 3), - ("/v{val}/{val2}/index.html", 4), - ("/v/{tail:.*}", 5), + ("/name", None, 1), + ("/name/{val}", None, 2), + ("/name/{val}/index.html", None, 3), + ("/v{val}/{val2}/index.html", None, 4), + ("/v/{tail:.*}", None, 5), ]; rec.set_routes(routes); @@ -434,7 +434,7 @@ mod tests { } fn assert_parse(pattern: &str, expected_re: &str) -> Regex { - let re_str = parse(pattern); + let (re_str, _) = parse(pattern); assert_eq!(&*re_str, expected_re); Regex::new(&re_str).unwrap() } diff --git a/tests/test_httprequest.rs b/tests/test_httprequest.rs index e69ee9249..794864cd0 100644 --- a/tests/test_httprequest.rs +++ b/tests/test_httprequest.rs @@ -92,7 +92,7 @@ fn test_request_match_info() { let mut req = HttpRequest::new(Method::GET, Uri::from_str("/value/?id=test").unwrap(), Version::HTTP_11, HeaderMap::new(), Payload::empty()); - let rec = RouteRecognizer::new("/".to_owned(), vec![("/{key}/".to_owned(), 1)]); + let rec = RouteRecognizer::new("/".to_owned(), vec![("/{key}/".to_owned(), None, 1)]); let (params, _) = rec.recognize(req.path()).unwrap(); let params = params.unwrap(); From bd1e9abdd81cfc4e5d540ed97ba8b6bc84debb07 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Dec 2017 11:50:09 -0800 Subject: [PATCH 030/279] simple readme example --- README.md | 42 ++++++------------------------------------ 1 file changed, 6 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 98b3ea4cd..185bad328 100644 --- a/README.md +++ b/README.md @@ -65,52 +65,22 @@ and tls connection. [rfc section 3.4](https://http2.github.io/http2-spec/#rfc.se ```rust -extern crate actix; -extern crate actix_web; -extern crate env_logger; - +# extern crate actix; +# extern crate actix_web; +# use actix::*; use actix_web::*; -struct MyWebSocket; - -/// Actor with http context -impl Actor for MyWebSocket { - type Context = HttpContext; -} - -/// Standard actix's stream handler for a stream of `ws::Message` -impl StreamHandler for MyWebSocket {} -impl Handler for MyWebSocket { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) - -> Response - { - // process websocket messages - println!("WS: {:?}", msg); - match msg { - ws::Message::Ping(msg) => ws::WsWriter::pong(ctx, &msg), - ws::Message::Text(text) => ws::WsWriter::text(ctx, &text), - ws::Message::Binary(bin) => ws::WsWriter::binary(ctx, bin), - ws::Message::Closed | ws::Message::Error => { - ctx.stop(); - } - _ => (), - } - Self::empty() - } +fn index(req: HttpRequest) -> String { + format!("Hello {}!", &req.match_info()["name"]) } fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - let _ = env_logger::init(); let sys = actix::System::new("ws-example"); HttpServer::new( Application::default("/") - .middleware(middlewares::Logger::default()) // <- register logger middleware - .resource("/ws/", |r| - r.method(Method::GET).f(|req| ws::start(req, MyWebSocket))) // <- websocket route - .route("/", |r| r.h(fs::StaticFiles::new("examples/static/", true)))) // <- serve static files + .resource("/{name}", |r| r.method(Method::GET).f(index))) .serve::<_, ()>("127.0.0.1:8080").unwrap(); Arbiter::system().send(msgs::SystemExit(0)); From 3de43c2a466b34ba8998abcdf81c73a9e97e6976 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Dec 2017 12:25:57 -0800 Subject: [PATCH 031/279] update readme --- README.md | 61 +++++++++++++++++++++---------------------------------- 1 file changed, 23 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 185bad328..528098c6b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,24 @@ # Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](http://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) -Asynchronous web framework for [Actix](https://github.com/actix/actix). +Actix web is a small, fast, down-to-earth, open source rust web framework. + +```rust,ignore +extern crate actix_web; +use actix_web::*; + +fn index(req: HttpRequest) -> String { + format!("Hello {}!", &req.match_info()["name"]) +} + +fn main() { + HttpServer::new( + Application::default("/") + .resource("/{name}", |r| r.method(Method::GET).f(index))) + .serve::<_, ()>("127.0.0.1:8080"); +} +``` + +## Documentation * [User Guide](http://actix.github.io/actix-web/guide/) * [API Documentation (Development)](http://actix.github.io/actix-web/actix_web/) @@ -8,10 +26,6 @@ Asynchronous web framework for [Actix](https://github.com/actix/actix). * Cargo package: [actix-web](https://crates.io/crates/actix-web) * Minimum supported Rust version: 1.20 or later ---- - -Actix web is licensed under the [Apache-2.0 license](http://opensource.org/licenses/APACHE-2.0). - ## Features * Supported HTTP/1 and HTTP/2 protocols @@ -22,15 +36,7 @@ Actix web is licensed under the [Apache-2.0 license](http://opensource.org/licen * Configurable request routing * Multipart streams * Middlewares (Logger, Session included) - -## Usage - -To use `actix-web`, add this to your `Cargo.toml`: - -```toml -[dependencies] -actix-web = { git = "https://github.com/actix/actix-web" } -``` + * Built on top of [Actix](https://github.com/actix/actix). ## HTTP/2 @@ -54,7 +60,7 @@ and tls connection. [rfc section 3.4](https://http2.github.io/http2-spec/#rfc.se [tls example](https://github.com/actix/actix-web/tree/master/examples/tls) -## Example +## Examples * [Basic](https://github.com/actix/actix-web/tree/master/examples/basic.rs) * [Stateful](https://github.com/actix/actix-web/tree/master/examples/state.rs) @@ -63,27 +69,6 @@ and tls connection. [rfc section 3.4](https://http2.github.io/http2-spec/#rfc.se * [Tcp/Websocket chat](https://github.com/actix/actix-web/tree/master/examples/websocket-chat) * [SockJS Server](https://github.com/actix/actix-sockjs) +## License -```rust -# extern crate actix; -# extern crate actix_web; -# -use actix::*; -use actix_web::*; - -fn index(req: HttpRequest) -> String { - format!("Hello {}!", &req.match_info()["name"]) -} - -fn main() { - let sys = actix::System::new("ws-example"); - - HttpServer::new( - Application::default("/") - .resource("/{name}", |r| r.method(Method::GET).f(index))) - .serve::<_, ()>("127.0.0.1:8080").unwrap(); - - Arbiter::system().send(msgs::SystemExit(0)); - let _ = sys.run(); -} -``` +Actix web is licensed under the [Apache-2.0 license](http://opensource.org/licenses/APACHE-2.0). From d8b880e16788c510bd6ff40a538a8d1f1e615a2c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Dec 2017 13:31:06 -0800 Subject: [PATCH 032/279] work on resource_path api --- examples/basic.rs | 2 +- src/application.rs | 33 ++++++++++++++++++++++- src/dev.rs | 4 +-- src/error.rs | 9 +++++++ src/httprequest.rs | 4 +-- src/recognizer.rs | 65 +++++++++++++++++++++++++--------------------- 6 files changed, 82 insertions(+), 35 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index cf9c78aa3..e14b36b86 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -27,7 +27,7 @@ fn index(mut req: HttpRequest) -> Result { req.session().set("counter", 1)?; } - Ok(HttpResponse::Ok().into()) + Ok("Welcome!".into()) } /// async handler diff --git a/src/application.rs b/src/application.rs index e007ea71d..5db0be5d1 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,10 +1,11 @@ use std::rc::Rc; use std::collections::HashMap; +use error::UriGenerationError; use handler::{Reply, RouteHandler}; use route::Route; use resource::Resource; -use recognizer::{RouteRecognizer, check_pattern}; +use recognizer::{RouteRecognizer, check_pattern, PatternElement}; use httprequest::HttpRequest; use channel::HttpHandler; use pipeline::Pipeline; @@ -22,6 +23,36 @@ impl Router { Router(Rc::new(RouteRecognizer::new(prefix, resources))) } + + pub fn has_route(&self, path: &str) -> bool { + self.0.recognize(path).is_some() + } + + pub fn resource_path<'a, U>(&self, prefix: &str, name: &str, elements: U) + -> Result + where U: IntoIterator + { + if let Some(pattern) = self.0.get_pattern(name) { + let mut iter = elements.into_iter(); + let mut vec = vec![prefix]; + for el in pattern.elements() { + match *el { + PatternElement::Str(ref s) => vec.push(s), + PatternElement::Var(_) => { + if let Some(val) = iter.next() { + vec.push(val) + } else { + return Err(UriGenerationError::NotEnoughElements) + } + } + } + } + let s = vec.join("/").to_owned(); + Ok(s) + } else { + Err(UriGenerationError::ResourceNotFound) + } + } } /// Application diff --git a/src/dev.rs b/src/dev.rs index 70f654b2c..921341efb 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -12,8 +12,8 @@ pub use handler::Handler; pub use pipeline::Pipeline; pub use channel::{HttpChannel, HttpHandler}; -pub use recognizer::{FromParam, RouteRecognizer}; +pub use recognizer::{FromParam, RouteRecognizer, Pattern, PatternElement}; +pub use cookie::CookieBuilder; pub use application::ApplicationBuilder; pub use httpresponse::HttpResponseBuilder; -pub use cookie::CookieBuilder; diff --git a/src/error.rs b/src/error.rs index 053df2d7e..dcd02fdf0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -403,6 +403,15 @@ impl ResponseError for UriSegmentError { } } +/// Errors which can occur when attempting to generate resource uri. +#[derive(Fail, Debug, PartialEq)] +pub enum UriGenerationError { + #[fail(display="Resource not found")] + ResourceNotFound, + #[fail(display="Not all path pattern covered")] + NotEnoughElements, +} + #[cfg(test)] mod tests { use std::error::Error as StdError; diff --git a/src/httprequest.rs b/src/httprequest.rs index debfefe3b..6a167d7dd 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -38,7 +38,7 @@ impl Default for HttpMessage { prefix: 0, version: Version::HTTP_11, headers: HeaderMap::new(), - params: Params::empty(), + params: Params::default(), cookies: Vec::new(), cookies_loaded: false, addr: None, @@ -64,7 +64,7 @@ impl HttpRequest<()> { prefix: 0, version: version, headers: headers, - params: Params::empty(), + params: Params::default(), cookies: Vec::new(), cookies_loaded: false, addr: None, diff --git a/src/recognizer.rs b/src/recognizer.rs index 2ee596794..be9667195 100644 --- a/src/recognizer.rs +++ b/src/recognizer.rs @@ -32,6 +32,16 @@ pub struct Params { names: Rc>, } +impl Default for Params { + fn default() -> Params { + Params { + text: String::new(), + names: Rc::new(HashMap::new()), + matches: Vec::new(), + } + } +} + impl Params { pub(crate) fn new(names: Rc>, text: &str, @@ -47,15 +57,6 @@ impl Params { } } - pub(crate) fn empty() -> Self - { - Params { - text: String::new(), - names: Rc::new(HashMap::new()), - matches: Vec::new(), - } - } - /// Check if there are any matched patterns pub fn is_empty(&self) -> bool { self.names.is_empty() @@ -202,9 +203,10 @@ FROM_STR!(std::net::SocketAddrV4); FROM_STR!(std::net::SocketAddrV6); pub struct RouteRecognizer { + re: RegexSet, prefix: usize, - patterns: RegexSet, routes: Vec<(Pattern, T)>, + patterns: HashMap, } impl Default for RouteRecognizer { @@ -212,8 +214,9 @@ impl Default for RouteRecognizer { fn default() -> Self { RouteRecognizer { prefix: 0, - patterns: RegexSet::new([""].iter()).unwrap(), + re: RegexSet::new([""].iter()).unwrap(), routes: Vec::new(), + patterns: HashMap::new(), } } } @@ -225,30 +228,28 @@ impl RouteRecognizer { { let mut paths = Vec::new(); let mut handlers = Vec::new(); + let mut patterns = HashMap::new(); for item in routes { let (pat, elements) = parse(&item.0); - handlers.push((Pattern::new(&pat, elements), item.2)); + let pattern = Pattern::new(&pat, elements); + if let Some(ref name) = item.1 { + let _ = patterns.insert(name.clone(), pattern.clone()); + } + handlers.push((pattern, item.2)); paths.push(pat); }; let regset = RegexSet::new(&paths); RouteRecognizer { + re: regset.unwrap(), prefix: prefix.into().len() - 1, - patterns: regset.unwrap(), routes: handlers, + patterns: patterns, } } - pub fn set_routes(&mut self, routes: Vec<(&str, Option<&str>, T)>) { - let mut paths = Vec::new(); - let mut handlers = Vec::new(); - for item in routes { - let (pat, elements) = parse(item.0); - handlers.push((Pattern::new(&pat, elements), item.2)); - paths.push(pat); - }; - self.patterns = RegexSet::new(&paths).unwrap(); - self.routes = handlers; + pub fn get_pattern(&self, name: &str) -> Option<&Pattern> { + self.patterns.get(name) } pub fn set_prefix>(&mut self, prefix: P) { @@ -263,11 +264,11 @@ impl RouteRecognizer { pub fn recognize(&self, path: &str) -> Option<(Option, &T)> { let p = &path[self.prefix..]; if p.is_empty() { - if let Some(idx) = self.patterns.matches("/").into_iter().next() { + if let Some(idx) = self.re.matches("/").into_iter().next() { let (ref pattern, ref route) = self.routes[idx]; return Some((pattern.match_info(&path[self.prefix..]), route)) } - } else if let Some(idx) = self.patterns.matches(p).into_iter().next() { + } else if let Some(idx) = self.re.matches(p).into_iter().next() { let (ref pattern, ref route) = self.routes[idx]; return Some((pattern.match_info(&path[self.prefix..]), route)) } @@ -275,12 +276,14 @@ impl RouteRecognizer { } } -enum PatternElement { +#[derive(Debug, Clone, PartialEq)] +pub enum PatternElement { Str(String), Var(String), } -struct Pattern { +#[derive(Clone)] +pub struct Pattern { re: Regex, names: Rc>, elements: Vec, @@ -309,6 +312,10 @@ impl Pattern { Some(Params::new(Rc::clone(&self.names), text, &captures)) } + + pub fn elements(&self) -> &Vec { + &self.elements + } } pub(crate) fn check_pattern(path: &str) { @@ -337,7 +344,7 @@ fn parse(pattern: &str) -> (String, Vec) { if in_param { // In parameter segment: `{....}` if ch == '}' { - elems.push(PatternElement::Var(String::from(String::from(param_name.as_str())))); + elems.push(PatternElement::Var(param_name.clone())); re.push_str(&format!(r"(?P<{}>{})", ¶m_name, ¶m_pattern)); param_name.clear(); @@ -359,7 +366,7 @@ fn parse(pattern: &str) -> (String, Vec) { } } else if ch == '{' { in_param = true; - elems.push(PatternElement::Str(String::from(el.as_str()))); + elems.push(PatternElement::Str(el.clone())); el.clear(); } else { re.push(ch); From c3de32c3b3934ad99c07c644de824210dcc63c4a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Dec 2017 17:09:15 -0800 Subject: [PATCH 033/279] added ConnectionInfo --- examples/tls/src/main.rs | 4 +- src/httprequest.rs | 30 ++++++-- src/info.rs | 148 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + src/recognizer.rs | 23 ++---- src/ws.rs | 4 -- 6 files changed, 180 insertions(+), 31 deletions(-) create mode 100644 src/info.rs diff --git a/examples/tls/src/main.rs b/examples/tls/src/main.rs index 3368c4ebc..886049d19 100644 --- a/examples/tls/src/main.rs +++ b/examples/tls/src/main.rs @@ -42,8 +42,8 @@ fn main() { .header("LOCATION", "/index.html") .body(Body::Empty) }))) - .serve_tls::<_, ()>("127.0.0.1:8080", pkcs12).unwrap(); + .serve_tls::<_, ()>("127.0.0.1:8443", pkcs12).unwrap(); - println!("Started http server: 127.0.0.1:8080"); + println!("Started http server: 127.0.0.1:8443"); let _ = sys.run(); } diff --git a/src/httprequest.rs b/src/httprequest.rs index 6a167d7dd..75aa88036 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -9,12 +9,14 @@ use url::form_urlencoded; use http::{header, Uri, Method, Version, HeaderMap, Extensions}; use {Cookie, HttpRange}; +use info::ConnectionInfo; use recognizer::Params; use payload::Payload; use multipart::Multipart; use error::{ParseError, PayloadError, MultipartError, CookieParseError, HttpRangeError, UrlencodedError}; + struct HttpMessage { version: Version, method: Method, @@ -27,6 +29,7 @@ struct HttpMessage { cookies_loaded: bool, addr: Option, payload: Payload, + info: Option>, } impl Default for HttpMessage { @@ -44,6 +47,7 @@ impl Default for HttpMessage { addr: None, payload: Payload::empty(), extensions: Extensions::new(), + info: None, } } } @@ -70,6 +74,7 @@ impl HttpRequest<()> { addr: None, payload: payload, extensions: Extensions::new(), + info: None, }), Rc::new(()) ) @@ -106,6 +111,15 @@ impl HttpRequest { &mut self.as_mut().extensions } + pub(crate) fn set_prefix(&mut self, idx: usize) { + self.as_mut().prefix = idx; + } + + #[doc(hidden)] + pub fn prefix_len(&self) -> usize { + self.0.prefix + } + /// Read the Request Uri. #[inline] pub fn uri(&self) -> &Uri { &self.0.uri } @@ -132,13 +146,15 @@ impl HttpRequest { self.0.uri.path() } - pub(crate) fn set_prefix(&mut self, idx: usize) { - self.as_mut().prefix = idx; - } - - #[doc(hidden)] - pub fn prefix_len(&self) -> usize { - self.0.prefix + /// Load *ConnectionInfo* for currect request. + #[inline] + pub fn load_connection_info(&mut self) -> &ConnectionInfo { + if self.0.info.is_none() { + let info: ConnectionInfo<'static> = unsafe{ + mem::transmute(ConnectionInfo::new(self))}; + self.as_mut().info = Some(info); + } + self.0.info.as_ref().unwrap() } /// Remote IP of client initiated HTTP request. diff --git a/src/info.rs b/src/info.rs new file mode 100644 index 000000000..76420ee6c --- /dev/null +++ b/src/info.rs @@ -0,0 +1,148 @@ +use std::str::FromStr; +use http::header::{self, HeaderName}; +use httprequest::HttpRequest; + +const X_FORWARDED_HOST: &str = "X-FORWARDED-HOST"; +const X_FORWARDED_PROTO: &str = "X-FORWARDED-PROTO"; + + +/// `HttpRequest` connection information +/// +/// While it is possible to create `ConnectionInfo` directly, +/// consider using `HttpRequest::load_connection_info()` which cache result. +pub struct ConnectionInfo<'a> { + scheme: &'a str, + host: &'a str, + remote: String, + forwarded_for: Vec<&'a str>, + forwarded_by: Vec<&'a str>, +} + +impl<'a> ConnectionInfo<'a> { + + /// Create *ConnectionInfo* instance for a request. + pub fn new(req: &'a HttpRequest) -> ConnectionInfo<'a> { + let mut host = None; + let mut scheme = None; + let mut forwarded_for = Vec::new(); + let mut forwarded_by = Vec::new(); + + // load forwarded header + 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(',') { + let mut items = el.splitn(1, '='); + if let Some(name) = items.next() { + if let Some(val) = items.next() { + match &name.to_lowercase() as &str { + "for" => forwarded_for.push(val.trim()), + "by" => forwarded_by.push(val.trim()), + "proto" => if scheme.is_none() { + scheme = Some(val.trim()); + }, + "host" => if host.is_none() { + host = Some(val.trim()); + }, + _ => (), + } + } + } + } + } + } + } + + // scheme + if scheme.is_none() { + if let Some(h) = req.headers().get( + HeaderName::from_str(X_FORWARDED_PROTO).unwrap()) { + if let Ok(h) = h.to_str() { + scheme = h.split(',').next().map(|v| v.trim()); + } + } + if scheme.is_none() { + if let Some(a) = req.uri().scheme_part() { + scheme = Some(a.as_str()) + } + } + } + + // host + if host.is_none() { + if let Some(h) = req.headers().get(HeaderName::from_str(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 Ok(h) = h.to_str() { + host = Some(h); + } + } + if host.is_none() { + if let Some(a) = req.uri().authority_part() { + host = Some(a.as_str()) + } + } + } + } + + ConnectionInfo { + scheme: scheme.unwrap_or("http"), + host: host.unwrap_or("localhost"), + remote: String::new(), + forwarded_for: forwarded_for, + forwarded_by: forwarded_by, + } + } + + /// Scheme of the request. + /// + /// Scheme is resolved through the following headers, in this order: + /// + /// - Forwarded + /// - X-Forwarded-Proto + /// - Uri + #[inline] + pub fn scheme(&self) -> &str { + self.scheme + } + + /// Hostname of the request. + /// + /// Hostname is resolved through the following headers, in this order: + /// + /// - Forwarded + /// - X-Forwarded-Host + /// - Host + /// - Uri + pub fn host(&self) -> &str { + self.host + } + + /// Remote IP of client initiated HTTP request. + /// + /// The IP is resolved through the following headers, in this order: + /// + /// - Forwarded + /// - X-Forwarded-For + /// - peername of opened socket + #[inline] + pub fn remote(&self) -> &str { + &self.remote + } + + /// List of the nodes making the request to the proxy. + #[inline] + pub fn forwarded_for(&self) -> &Vec<&str> { + &self.forwarded_for + } + + /// List of the user-agent facing interface of the proxies + #[inline] + pub fn forwarded_by(&self) -> &Vec<&str> { + &self.forwarded_by + } +} diff --git a/src/lib.rs b/src/lib.rs index d365c87c7..90f2cdba8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,6 +55,7 @@ mod encoding; mod httprequest; mod httpresponse; mod payload; +mod info; mod route; mod resource; mod recognizer; @@ -81,6 +82,7 @@ pub use error::{Error, Result}; pub use encoding::ContentEncoding; pub use body::{Body, Binary}; pub use application::Application; +pub use info::ConnectionInfo; pub use httprequest::{HttpRequest, UrlEncoded}; pub use httpresponse::HttpResponse; pub use payload::{Payload, PayloadItem}; diff --git a/src/recognizer.rs b/src/recognizer.rs index be9667195..9776f3950 100644 --- a/src/recognizer.rs +++ b/src/recognizer.rs @@ -209,28 +209,17 @@ pub struct RouteRecognizer { patterns: HashMap, } -impl Default for RouteRecognizer { - - fn default() -> Self { - RouteRecognizer { - prefix: 0, - re: RegexSet::new([""].iter()).unwrap(), - routes: Vec::new(), - patterns: HashMap::new(), - } - } -} - impl RouteRecognizer { - pub fn new, U>(prefix: P, routes: U) -> Self - where U: IntoIterator, T)> + pub fn new, U, K>(prefix: P, routes: U) -> Self + where U: IntoIterator, T)>, + K: Into, { let mut paths = Vec::new(); let mut handlers = Vec::new(); let mut patterns = HashMap::new(); for item in routes { - let (pat, elements) = parse(&item.0); + let (pat, elements) = parse(&item.0.into()); let pattern = Pattern::new(&pat, elements); if let Some(ref name) = item.1 { let _ = patterns.insert(name.clone(), pattern.clone()); @@ -399,8 +388,6 @@ mod tests { #[test] fn test_recognizer() { - let mut rec = RouteRecognizer::::default(); - let routes = vec![ ("/name", None, 1), ("/name/{val}", None, 2), @@ -408,7 +395,7 @@ mod tests { ("/v{val}/{val2}/index.html", None, 4), ("/v/{tail:.*}", None, 5), ]; - rec.set_routes(routes); + let mut rec = RouteRecognizer::new("/", routes); let (params, val) = rec.recognize("/name").unwrap(); assert_eq!(*val, 1); diff --git a/src/ws.rs b/src/ws.rs index 21b630be7..cbde61e59 100644 --- a/src/ws.rs +++ b/src/ws.rs @@ -66,13 +66,9 @@ use wsframe; use wsproto::*; pub use wsproto::CloseCode; -#[doc(hidden)] const SEC_WEBSOCKET_ACCEPT: &str = "SEC-WEBSOCKET-ACCEPT"; -#[doc(hidden)] const SEC_WEBSOCKET_KEY: &str = "SEC-WEBSOCKET-KEY"; -#[doc(hidden)] const SEC_WEBSOCKET_VERSION: &str = "SEC-WEBSOCKET-VERSION"; -// #[doc(hidden)] // const SEC_WEBSOCKET_PROTOCOL: &'static str = "SEC-WEBSOCKET-PROTOCOL"; From d7e65b62122b50e1d662ec570cfe0e1a286237bd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Dec 2017 21:38:52 -0800 Subject: [PATCH 034/279] add ConnectionInfo tests --- src/h1.rs | 2 +- src/h2.rs | 2 +- src/httprequest.rs | 26 ++++++--- src/info.rs | 113 +++++++++++++++++++++++++++++--------- src/middlewares/logger.rs | 11 ++-- src/recognizer.rs | 2 +- 6 files changed, 114 insertions(+), 42 deletions(-) diff --git a/src/h1.rs b/src/h1.rs index 1222eb618..b7fed38c7 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -175,7 +175,7 @@ impl Http1 not_ready = false; // set remote addr - req.set_remove_addr(self.addr); + req.set_peer_addr(self.addr); // stop keepalive timer self.keepalive_timer.take(); diff --git a/src/h2.rs b/src/h2.rs index 929fb9924..cf89a719c 100644 --- a/src/h2.rs +++ b/src/h2.rs @@ -223,7 +223,7 @@ impl Entry { parts.method, parts.uri, parts.version, parts.headers, payload); // set remote addr - req.set_remove_addr(addr); + req.set_peer_addr(addr); // Payload sender let psender = PayloadType::new(req.headers(), psender); diff --git a/src/httprequest.rs b/src/httprequest.rs index 75aa88036..3c30037eb 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -140,12 +140,27 @@ impl HttpRequest { &self.0.headers } + #[cfg(test)] + pub fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.as_mut().headers + } + /// The target path of this Request. #[inline] pub fn path(&self) -> &str { self.0.uri.path() } + /// Get previously loaded *ConnectionInfo*. + #[inline] + pub fn connection_info(&self) -> Option<&ConnectionInfo> { + if self.0.info.is_none() { + None + } else { + self.0.info.as_ref() + } + } + /// Load *ConnectionInfo* for currect request. #[inline] pub fn load_connection_info(&mut self) -> &ConnectionInfo { @@ -157,19 +172,12 @@ impl HttpRequest { self.0.info.as_ref().unwrap() } - /// Remote IP of client initiated HTTP request. - /// - /// The IP is resolved through the following headers, in this order: - /// - /// - Forwarded - /// - X-Forwarded-For - /// - peername of opened socket #[inline] - pub fn remote(&self) -> Option<&SocketAddr> { + pub fn peer_addr(&self) -> Option<&SocketAddr> { self.0.addr.as_ref() } - pub(crate) fn set_remove_addr(&mut self, addr: Option) { + pub(crate) fn set_peer_addr(&mut self, addr: Option) { self.as_mut().addr = addr } diff --git a/src/info.rs b/src/info.rs index 76420ee6c..a15b62115 100644 --- a/src/info.rs +++ b/src/info.rs @@ -2,6 +2,7 @@ use std::str::FromStr; use http::header::{self, HeaderName}; use httprequest::HttpRequest; +const X_FORWARDED_FOR: &str = "X-FORWARDED-FOR"; const X_FORWARDED_HOST: &str = "X-FORWARDED-HOST"; const X_FORWARDED_PROTO: &str = "X-FORWARDED-PROTO"; @@ -13,9 +14,8 @@ const X_FORWARDED_PROTO: &str = "X-FORWARDED-PROTO"; pub struct ConnectionInfo<'a> { scheme: &'a str, host: &'a str, - remote: String, - forwarded_for: Vec<&'a str>, - forwarded_by: Vec<&'a str>, + remote: Option<&'a str>, + peer: Option, } impl<'a> ConnectionInfo<'a> { @@ -24,20 +24,21 @@ impl<'a> ConnectionInfo<'a> { pub fn new(req: &'a HttpRequest) -> ConnectionInfo<'a> { let mut host = None; let mut scheme = None; - let mut forwarded_for = Vec::new(); - let mut forwarded_by = Vec::new(); + let mut remote = None; + let mut peer = None; // load forwarded header 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(',') { - let mut items = el.splitn(1, '='); + let mut items = el.trim().splitn(2, '='); if let Some(name) = items.next() { if let Some(val) = items.next() { match &name.to_lowercase() as &str { - "for" => forwarded_for.push(val.trim()), - "by" => forwarded_by.push(val.trim()), + "for" => if remote.is_none() { + remote = Some(val.trim()); + }, "proto" => if scheme.is_none() { scheme = Some(val.trim()); }, @@ -89,12 +90,27 @@ impl<'a> ConnectionInfo<'a> { } } + // remote addr + if remote.is_none() { + if let Some(h) = req.headers().get( + HeaderName::from_str(X_FORWARDED_FOR).unwrap()) { + if let Ok(h) = h.to_str() { + remote = h.split(',').next().map(|v| v.trim()); + } + } + if remote.is_none() { + if let Some(addr) = req.peer_addr() { + // get peeraddr from socketaddr + peer = Some(format!("{}", addr)); + } + } + } + ConnectionInfo { scheme: scheme.unwrap_or("http"), host: host.unwrap_or("localhost"), - remote: String::new(), - forwarded_for: forwarded_for, - forwarded_by: forwarded_by, + remote: remote, + peer: peer, } } @@ -130,19 +146,66 @@ impl<'a> ConnectionInfo<'a> { /// - X-Forwarded-For /// - peername of opened socket #[inline] - pub fn remote(&self) -> &str { - &self.remote - } - - /// List of the nodes making the request to the proxy. - #[inline] - pub fn forwarded_for(&self) -> &Vec<&str> { - &self.forwarded_for - } - - /// List of the user-agent facing interface of the proxies - #[inline] - pub fn forwarded_by(&self) -> &Vec<&str> { - &self.forwarded_by + pub fn remote(&self) -> Option<&str> { + if let Some(r) = self.remote { + Some(r) + } else if let Some(ref peer) = self.peer { + Some(peer) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use http::header::HeaderValue; + + #[test] + fn test_forwarded() { + let req = HttpRequest::default(); + let info = ConnectionInfo::new(&req); + assert_eq!(info.scheme(), "http"); + assert_eq!(info.host(), "localhost"); + + let mut req = HttpRequest::default(); + req.headers_mut().insert( + header::FORWARDED, + HeaderValue::from_static( + "for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org")); + + let info = ConnectionInfo::new(&req); + assert_eq!(info.scheme(), "https"); + assert_eq!(info.host(), "rust-lang.org"); + assert_eq!(info.remote(), Some("192.0.2.60")); + + let mut req = HttpRequest::default(); + req.headers_mut().insert( + header::HOST, HeaderValue::from_static("rust-lang.org")); + + let info = ConnectionInfo::new(&req); + assert_eq!(info.scheme(), "http"); + assert_eq!(info.host(), "rust-lang.org"); + assert_eq!(info.remote(), None); + + let mut req = HttpRequest::default(); + req.headers_mut().insert( + HeaderName::from_str(X_FORWARDED_FOR).unwrap(), HeaderValue::from_static("192.0.2.60")); + let info = ConnectionInfo::new(&req); + assert_eq!(info.remote(), Some("192.0.2.60")); + + let mut req = HttpRequest::default(); + req.headers_mut().insert( + HeaderName::from_str(X_FORWARDED_HOST).unwrap(), HeaderValue::from_static("192.0.2.60")); + let info = ConnectionInfo::new(&req); + assert_eq!(info.host(), "192.0.2.60"); + assert_eq!(info.remote(), None); + + let mut req = HttpRequest::default(); + req.headers_mut().insert( + HeaderName::from_str(X_FORWARDED_PROTO).unwrap(), HeaderValue::from_static("https")); + let info = ConnectionInfo::new(&req); + assert_eq!(info.scheme(), "https"); } } diff --git a/src/middlewares/logger.rs b/src/middlewares/logger.rs index 92117ff00..51667a22c 100644 --- a/src/middlewares/logger.rs +++ b/src/middlewares/logger.rs @@ -102,6 +102,7 @@ impl Logger { impl Middleware for Logger { fn start(&self, req: &mut HttpRequest) -> Started { + req.load_connection_info(); req.extensions().insert(StartTime(time::now())); Started::Done } @@ -112,7 +113,6 @@ impl Middleware for Logger { } } - /// A formatting style for the `Logger`, consisting of multiple /// `FormatText`s concatenated into one line. #[derive(Clone)] @@ -237,11 +237,12 @@ impl FormatText { fmt.write_fmt(format_args!("{:.6}", response_time_ms)) }, FormatText::RemoteAddr => { - if let Some(addr) = req.remote() { - addr.fmt(fmt) - } else { - "-".fmt(fmt) + if let Some(addr) = req.connection_info() { + if let Some(remote) = addr.remote() { + return remote.fmt(fmt); + } } + "-".fmt(fmt) } FormatText::RequestTime => { entry_time.strftime("[%d/%b/%Y:%H:%M:%S %z]") diff --git a/src/recognizer.rs b/src/recognizer.rs index 9776f3950..3fb34330c 100644 --- a/src/recognizer.rs +++ b/src/recognizer.rs @@ -395,7 +395,7 @@ mod tests { ("/v{val}/{val2}/index.html", None, 4), ("/v/{tail:.*}", None, 5), ]; - let mut rec = RouteRecognizer::new("/", routes); + let rec = RouteRecognizer::new("/", routes); let (params, val) = rec.recognize("/name").unwrap(); assert_eq!(*val, 1); From 20af8822fdd110647d6fcb87d2d5a62334170db2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Dec 2017 21:53:00 -0800 Subject: [PATCH 035/279] cleanup --- src/info.rs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/info.rs b/src/info.rs index a15b62115..a02ffc6ad 100644 --- a/src/info.rs +++ b/src/info.rs @@ -63,9 +63,7 @@ impl<'a> ConnectionInfo<'a> { } } if scheme.is_none() { - if let Some(a) = req.uri().scheme_part() { - scheme = Some(a.as_str()) - } + scheme = req.uri().scheme_part().map(|a| a.as_str()); } } @@ -78,14 +76,10 @@ impl<'a> ConnectionInfo<'a> { } if host.is_none() { if let Some(h) = req.headers().get(header::HOST) { - if let Ok(h) = h.to_str() { - host = Some(h); - } + host = h.to_str().ok(); } if host.is_none() { - if let Some(a) = req.uri().authority_part() { - host = Some(a.as_str()) - } + host = req.uri().authority_part().map(|a| a.as_str()) } } } @@ -99,10 +93,8 @@ impl<'a> ConnectionInfo<'a> { } } if remote.is_none() { - if let Some(addr) = req.peer_addr() { - // get peeraddr from socketaddr - peer = Some(format!("{}", addr)); - } + // get peeraddr from socketaddr + peer = req.peer_addr().map(|addr| format!("{}", addr)); } } From c2bfc091bd37893ca4594feb7a0e1d5b0a3ce83b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Dec 2017 22:14:38 -0800 Subject: [PATCH 036/279] fix travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 72e0dbb38..883a9d40c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,7 +50,7 @@ after_success: - | if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) - cargo tarpaulin --exclude ./examples/* --out Xml + cargo tarpaulin --out Xml bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi From 903b391e0aed33dc72d368dd7ccb8b103ad6bddb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Dec 2017 07:47:42 -0800 Subject: [PATCH 037/279] move ConnectionInfo to dev --- src/dev.rs | 1 + src/lib.rs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dev.rs b/src/dev.rs index 921341efb..47efce082 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -9,6 +9,7 @@ //! ``` // dev specific +pub use info::ConnectionInfo; pub use handler::Handler; pub use pipeline::Pipeline; pub use channel::{HttpChannel, HttpHandler}; diff --git a/src/lib.rs b/src/lib.rs index 90f2cdba8..301c72696 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,7 +82,6 @@ pub use error::{Error, Result}; pub use encoding::ContentEncoding; pub use body::{Body, Binary}; pub use application::Application; -pub use info::ConnectionInfo; pub use httprequest::{HttpRequest, UrlEncoded}; pub use httpresponse::HttpResponse; pub use payload::{Payload, PayloadItem}; From 04ded5ba6826ceee2e89d69025fbe607bf4b2ae5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Dec 2017 07:49:01 -0800 Subject: [PATCH 038/279] hide pkcs --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 301c72696..25a9b2049 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,8 +97,10 @@ pub use http::{Method, StatusCode, Version}; pub use cookie::Cookie; pub use http_range::HttpRange; +#[doc(hidden)] #[cfg(feature="tls")] pub use native_tls::Pkcs12; +#[doc(hidden)] #[cfg(feature="openssl")] pub use openssl::pkcs12::Pkcs12; From 87c7441f7df028063ffdf839b2f5fef6194f7966 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Dec 2017 08:03:08 -0800 Subject: [PATCH 039/279] remove Applicaiton::route, resource is enough --- examples/basic.rs | 12 ++++++------ examples/state.rs | 5 ++--- examples/websocket.rs | 4 ++-- guide/src/qs_12.md | 2 +- guide/src/qs_2.md | 2 +- guide/src/qs_3.md | 6 +++--- guide/src/qs_4.md | 4 ++-- guide/src/qs_5.md | 6 +++--- src/application.rs | 35 ----------------------------------- src/fs.rs | 2 +- src/resource.rs | 33 ++++++++++++++++++++++++++++++++- 11 files changed, 53 insertions(+), 58 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index e14b36b86..db43fce50 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -67,13 +67,13 @@ fn main() { .finish() )) // register simple route, handle all methods - .route("/index.html", |r| r.f(index)) + .resource("/index.html", |r| r.f(index)) // with path parameters - .resource("/user/{name}/", |r| r.route().method(Method::GET).f(with_param)) + .resource("/user/{name}/", |r| r.method(Method::GET).f(with_param)) // async handler - .resource("/async/{name}", |r| r.route().method(Method::GET).a(index_async)) + .resource("/async/{name}", |r| r.method(Method::GET).a(index_async)) // redirect - .resource("/", |r| r.route().method(Method::GET).f(|req| { + .resource("/", |r| r.method(Method::GET).f(|req| { println!("{:?}", req); httpcodes::HTTPFound @@ -81,7 +81,7 @@ fn main() { .header("LOCATION", "/index.html") .body(Body::Empty) })) - .route("/test", |r| r.f(|req| { + .resource("/test", |r| r.f(|req| { match *req.method() { Method::GET => httpcodes::HTTPOk, Method::POST => httpcodes::HTTPMethodNotAllowed, @@ -89,7 +89,7 @@ fn main() { } })) // static files - .route("/static", |r| r.h(fs::StaticFiles::new("examples/static/", true)))) + .resource("/static", |r| r.h(fs::StaticFiles::new("examples/static/", true)))) .serve::<_, ()>("127.0.0.1:8080").unwrap(); println!("Started http server: 127.0.0.1:8080"); diff --git a/examples/state.rs b/examples/state.rs index f7e980413..2e8df9256 100644 --- a/examples/state.rs +++ b/examples/state.rs @@ -65,11 +65,10 @@ fn main() { .middleware(middlewares::Logger::default()) // websocket route .resource( - "/ws/", |r| r.route() - .method(Method::GET) + "/ws/", |r| r.method(Method::GET) .f(|req| ws::start(req, MyWebSocket{counter: 0}))) // register simple handler, handle all methods - .route("/", |r| r.f(index))) + .resource("/", |r| r.f(index))) .serve::<_, ()>("127.0.0.1:8080").unwrap(); println!("Started http server: 127.0.0.1:8080"); diff --git a/examples/websocket.rs b/examples/websocket.rs index 0187f0c08..ba316b601 100644 --- a/examples/websocket.rs +++ b/examples/websocket.rs @@ -65,9 +65,9 @@ fn main() { // enable logger .middleware(middlewares::Logger::default()) // websocket route - .resource("/ws/", |r| r.route().method(Method::GET).f(ws_index)) + .resource("/ws/", |r| r.method(Method::GET).f(ws_index)) // static files - .route("/", |r| r.h(fs::StaticFiles::new("examples/static/", true)))) + .resource("/", |r| r.h(fs::StaticFiles::new("examples/static/", true)))) // start http server on 127.0.0.1:8080 .serve::<_, ()>("127.0.0.1:8080").unwrap(); diff --git a/guide/src/qs_12.md b/guide/src/qs_12.md index c16ac0f30..4cfa8b5f4 100644 --- a/guide/src/qs_12.md +++ b/guide/src/qs_12.md @@ -33,7 +33,7 @@ use actix_web::*; fn main() { Application::default("/") - .route("/static", |r| r.h(fs::StaticFiles::new(".", true))) + .resource("/static", |r| r.h(fs::StaticFiles::new(".", true))) .finish(); } ``` diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md index b8179db35..41c62a6f1 100644 --- a/guide/src/qs_2.md +++ b/guide/src/qs_2.md @@ -80,7 +80,7 @@ fn main() { HttpServer::new( Application::default("/") - .resource("/", |r| r.route().f(index))) + .resource("/", |r| r.f(index))) .serve::<_, ()>("127.0.0.1:8088").unwrap(); println!("Started http server: 127.0.0.1:8088"); diff --git a/guide/src/qs_3.md b/guide/src/qs_3.md index f94e27266..e70e04bef 100644 --- a/guide/src/qs_3.md +++ b/guide/src/qs_3.md @@ -41,13 +41,13 @@ use tokio_core::net::TcpStream; fn main() { HttpServer::::new(vec![ Application::default("/app1") - .resource("/", |r| r.route().f(|r| httpcodes::HTTPOk)) + .resource("/", |r| r.f(|r| httpcodes::HTTPOk)) .finish(), Application::default("/app2") - .resource("/", |r| r.route().f(|r| httpcodes::HTTPOk)) + .resource("/", |r| r.f(|r| httpcodes::HTTPOk)) .finish(), Application::default("/") - .resource("/", |r| r.route().f(|r| httpcodes::HTTPOk)) + .resource("/", |r| r.f(|r| httpcodes::HTTPOk)) .finish(), ]); } diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index c8683591d..bd37434b2 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -114,7 +114,7 @@ fn index(req: HttpRequest) -> FutureResult { fn main() { Application::default("/") - .route("/async", |r| r.a(index)) + .resource("/async", |r| r.route().a(index)) .finish(); } ``` @@ -139,7 +139,7 @@ fn index(req: HttpRequest) -> HttpResponse { fn main() { Application::default("/") - .route("/async", |r| r.f(index)) + .resource("/async", |r| r.f(index)) .finish(); } ``` diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index a03bf91ee..b2ae22753 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -18,7 +18,7 @@ fn index(req: HttpRequest) -> HttpResponse { fn main() { Application::default("/") - .route("/prefix", |r| r.f(index)) + .resource("/prefix", |r| r.f(index)) .finish(); } ``` @@ -37,7 +37,7 @@ fn index(req: HttpRequest) -> HttpResponse { fn main() { Application::default("/app") - .route("/prefix", |r| r.f(index)) + .resource("/prefix", |r| r.f(index)) .finish(); } ``` @@ -126,7 +126,7 @@ fn index(req: HttpRequest) -> Result { fn main() { Application::default("/") - .resource(r"/a/{v1}/{v2}/", |r| r.route().f(index)) + .resource(r"/a/{v1}/{v2}/", |r| r.f(index)) .finish(); } ``` diff --git a/src/application.rs b/src/application.rs index 5db0be5d1..d0274725a 100644 --- a/src/application.rs +++ b/src/application.rs @@ -210,41 +210,6 @@ impl ApplicationBuilder where S: 'static { self } - /// This method register route for specified path prefix. - /// Route maches based on path prefix, variable path patterns are not available - /// in this case. If you need variable path patterns consider using *resource()* - /// method. - /// - /// ```rust - /// extern crate actix_web; - /// use actix_web::*; - /// - /// fn main() { - /// let app = Application::default("/") - /// .route("/test", |r| r.f( - /// |req| { - /// match *req.method() { - /// Method::GET => httpcodes::HTTPOk, - /// Method::POST => httpcodes::HTTPMethodNotAllowed, - /// _ => httpcodes::HTTPNotFound, - /// } - /// } - /// )) - /// .finish(); - /// } - /// ``` - pub fn route>(&mut self, path: P, f: F) -> &mut Self - where P: Into, - F: FnOnce(&mut Route) + 'static - { - { - let parts = self.parts.as_mut().expect("Use after finish"); - parts.routes.push((path.into(), Route::default())); - f(&mut parts.routes.last_mut().unwrap().1); - } - self - } - /// Register a middleware pub fn middleware(&mut self, mw: T) -> &mut Self where T: Middleware + 'static diff --git a/src/fs.rs b/src/fs.rs index 1bf678eea..7f0a2a2dc 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -198,7 +198,7 @@ impl FromRequest for FilesystemElement { /// /// fn main() { /// let app = actix_web::Application::default("/") -/// .route("/static", |r| r.h(actix_web::fs::StaticFiles::new(".", true))) +/// .resource("/static", |r| r.h(actix_web::fs::StaticFiles::new(".", true))) /// .finish(); /// } /// ``` diff --git a/src/resource.rs b/src/resource.rs index 28fc1a0cf..f499c42b1 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use http::Method; use route::Route; -use handler::{Reply, Handler, RouteHandler, WrapHandler}; +use handler::{Reply, Handler, FromRequest, RouteHandler, WrapHandler}; use httpcodes::HTTPNotFound; use httprequest::HttpRequest; @@ -79,17 +79,48 @@ impl Resource where S: 'static { /// .f(|r| HttpResponse::Ok())) /// .finish(); /// } + /// ``` pub fn route(&mut self) -> &mut Route { self.routes.push(Route::default()); self.routes.last_mut().unwrap() } /// Register a new route and add method check to route. + /// + /// This is sortcut for: + /// ```rust,ignore + /// Resource::resource("/", |r| r.route().method(Method::GET).f(index) + /// ``` pub fn method(&mut self, method: Method) -> &mut Route { self.routes.push(Route::default()); self.routes.last_mut().unwrap().method(method) } + /// Register a new route and add handler object. + /// + /// This is sortcut for: + /// ```rust,ignore + /// Resource::resource("/", |r| r.route().h(handler) + /// ``` + pub fn h>(&mut self, handler: H) { + self.routes.push(Route::default()); + self.routes.last_mut().unwrap().h(handler) + } + + /// Register a new route and add handler function. + /// + /// This is sortcut for: + /// ```rust,ignore + /// Resource::resource("/", |r| r.route().f(index) + /// ``` + pub fn f(&mut self, handler: F) + where F: Fn(HttpRequest) -> R + 'static, + R: FromRequest + 'static, + { + self.routes.push(Route::default()); + self.routes.last_mut().unwrap().f(handler) + } + /// Default handler is used if no matched route found. /// By default `HTTPNotFound` is used. pub fn default_handler(&mut self, handler: H) where H: Handler { From c63f05864713c2030dcd4d6ed137b6809779a974 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Dec 2017 11:00:39 -0800 Subject: [PATCH 040/279] simplify application creation --- README.md | 3 +- examples/basic.rs | 2 +- examples/state.rs | 6 +- examples/websocket.rs | 2 +- guide/src/qs_10.md | 8 +-- guide/src/qs_12.md | 4 +- guide/src/qs_13.md | 4 +- guide/src/qs_2.md | 4 +- guide/src/qs_3.md | 17 ++--- guide/src/qs_4.md | 10 +-- guide/src/qs_5.md | 16 ++--- guide/src/qs_6.md | 2 +- guide/src/qs_7.md | 2 +- src/application.rs | 112 ++++++++++++++---------------- src/channel.rs | 17 +++++ src/dev.rs | 3 +- src/error.rs | 2 +- src/fs.rs | 7 +- src/lib.rs | 2 +- src/middlewares/defaultheaders.rs | 4 +- src/middlewares/logger.rs | 4 +- src/recognizer.rs | 5 ++ src/resource.rs | 19 ++--- src/server.rs | 8 ++- src/ws.rs | 2 +- tests/test_server.rs | 23 +++--- 26 files changed, 150 insertions(+), 138 deletions(-) diff --git a/README.md b/README.md index 528098c6b..9502c6501 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ Actix web is a small, fast, down-to-earth, open source rust web framework. ```rust,ignore -extern crate actix_web; use actix_web::*; fn index(req: HttpRequest) -> String { @@ -12,7 +11,7 @@ fn index(req: HttpRequest) -> String { fn main() { HttpServer::new( - Application::default("/") + Application::new("/") .resource("/{name}", |r| r.method(Method::GET).f(index))) .serve::<_, ()>("127.0.0.1:8080"); } diff --git a/examples/basic.rs b/examples/basic.rs index db43fce50..164ca7a18 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -57,7 +57,7 @@ fn main() { let sys = actix::System::new("ws-example"); HttpServer::new( - Application::default("/") + Application::new("/") // enable logger .middleware(middlewares::Logger::default()) // cookie session middleware diff --git a/examples/state.rs b/examples/state.rs index 2e8df9256..c199dd5e5 100644 --- a/examples/state.rs +++ b/examples/state.rs @@ -60,13 +60,13 @@ fn main() { let sys = actix::System::new("ws-example"); HttpServer::new( - Application::build("/", AppState{counter: Cell::new(0)}) + Application::with_state("/", AppState{counter: Cell::new(0)}) // enable logger .middleware(middlewares::Logger::default()) // websocket route .resource( - "/ws/", |r| r.method(Method::GET) - .f(|req| ws::start(req, MyWebSocket{counter: 0}))) + "/ws/", |r| + r.method(Method::GET).f(|req| ws::start(req, MyWebSocket{counter: 0}))) // register simple handler, handle all methods .resource("/", |r| r.f(index))) .serve::<_, ()>("127.0.0.1:8080").unwrap(); diff --git a/examples/websocket.rs b/examples/websocket.rs index ba316b601..93b407464 100644 --- a/examples/websocket.rs +++ b/examples/websocket.rs @@ -61,7 +61,7 @@ fn main() { let sys = actix::System::new("ws-example"); HttpServer::new( - Application::default("/") + Application::new("/") // enable logger .middleware(middlewares::Logger::default()) // websocket route diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index 23275cb4f..664106f99 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -15,12 +15,12 @@ Default `Logger` could be created with `default` method, it uses the default for %a %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i" %T ``` ```rust -extern crate actix_web; +# extern crate actix_web; use actix_web::Application; use actix_web::middlewares::Logger; fn main() { - Application::default("/") + Application::new("/") .middleware(Logger::default()) .middleware(Logger::new("%a %{User-Agent}i")) .finish(); @@ -67,11 +67,11 @@ Tto set default response headers `DefaultHeaders` middleware could be used. *DefaultHeaders* middleware does not set header if response headers already contains it. ```rust -extern crate actix_web; +# extern crate actix_web; use actix_web::*; fn main() { - let app = Application::default("/") + let app = Application::new("/") .middleware( middlewares::DefaultHeaders::build() .header("X-Version", "0.2") diff --git a/guide/src/qs_12.md b/guide/src/qs_12.md index 4cfa8b5f4..7cabe7449 100644 --- a/guide/src/qs_12.md +++ b/guide/src/qs_12.md @@ -16,7 +16,7 @@ fn index(req: HttpRequest) -> Result { } fn main() { - Application::default("/") + Application::new("/") .resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index)) .finish(); } @@ -32,7 +32,7 @@ To serve files from specific directory and sub-directories `StaticFiles` could b use actix_web::*; fn main() { - Application::default("/") + Application::new("/") .resource("/static", |r| r.h(fs::StaticFiles::new(".", true))) .finish(); } diff --git a/guide/src/qs_13.md b/guide/src/qs_13.md index b567f6286..c3b0b0e72 100644 --- a/guide/src/qs_13.md +++ b/guide/src/qs_13.md @@ -26,8 +26,8 @@ fn main() { let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap().parse("12345").unwrap(); HttpServer::new( - Application::default("/") - .route("/index.html", |r| r.f(index)) + Application::new("/") + .resource("/index.html", |r| r.f(index)) .serve_tls::<_, ()>("127.0.0.1:8080", pkcs12).unwrap(); } ``` diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md index 41c62a6f1..f8187c70d 100644 --- a/guide/src/qs_2.md +++ b/guide/src/qs_2.md @@ -48,7 +48,7 @@ request handler with the application's `resource` on a particular *HTTP method* # "Hello world!" # } # fn main() { - let app = Application::default("/") + let app = Application::new("/") .resource("/", |r| r.method(Method::GET).f(index)) .finish(); # } @@ -79,7 +79,7 @@ fn main() { let sys = actix::System::new("example"); HttpServer::new( - Application::default("/") + Application::new("/") .resource("/", |r| r.f(index))) .serve::<_, ()>("127.0.0.1:8088").unwrap(); diff --git a/guide/src/qs_3.md b/guide/src/qs_3.md index e70e04bef..2b954045e 100644 --- a/guide/src/qs_3.md +++ b/guide/src/qs_3.md @@ -20,7 +20,7 @@ has same url path prefix: # "Hello world!" # } # fn main() { - let app = Application::default("/prefix") + let app = Application::new("/prefix") .resource("/index.html", |r| r.method(Method::GET).f(index)) .finish() # } @@ -40,15 +40,12 @@ use tokio_core::net::TcpStream; fn main() { HttpServer::::new(vec![ - Application::default("/app1") - .resource("/", |r| r.f(|r| httpcodes::HTTPOk)) - .finish(), - Application::default("/app2") - .resource("/", |r| r.f(|r| httpcodes::HTTPOk)) - .finish(), - Application::default("/") - .resource("/", |r| r.f(|r| httpcodes::HTTPOk)) - .finish(), + Application::new("/app1") + .resource("/", |r| r.f(|r| httpcodes::HTTPOk)), + Application::new("/app2") + .resource("/", |r| r.f(|r| httpcodes::HTTPOk)), + Application::new("/") + .resource("/", |r| r.f(|r| httpcodes::HTTPOk)), ]); } ``` diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index bd37434b2..ae5ae19c2 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -46,8 +46,8 @@ fn index(req: HttpRequest) -> Box> { Let's create response for custom type that serializes to `application/json` response: ```rust -extern crate actix; -extern crate actix_web; +# extern crate actix; +# extern crate actix_web; extern crate serde; extern crate serde_json; #[macro_use] extern crate serde_derive; @@ -77,7 +77,7 @@ fn main() { let sys = actix::System::new("example"); HttpServer::new( - Application::default("/") + Application::new("/") .resource("/", |r| r.method( Method::GET).f(|req| {MyObj{name: "user".to_owned()}}))) .serve::<_, ()>("127.0.0.1:8088").unwrap(); @@ -113,7 +113,7 @@ fn index(req: HttpRequest) -> FutureResult { } fn main() { - Application::default("/") + Application::new("/") .resource("/async", |r| r.route().a(index)) .finish(); } @@ -138,7 +138,7 @@ fn index(req: HttpRequest) -> HttpResponse { } fn main() { - Application::default("/") + Application::new("/") .resource("/async", |r| r.f(index)) .finish(); } diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index b2ae22753..bd046ddac 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -17,7 +17,7 @@ fn index(req: HttpRequest) -> HttpResponse { } fn main() { - Application::default("/") + Application::new("/") .resource("/prefix", |r| r.f(index)) .finish(); } @@ -36,7 +36,7 @@ fn index(req: HttpRequest) -> HttpResponse { } fn main() { - Application::default("/app") + Application::new("/app") .resource("/prefix", |r| r.f(index)) .finish(); } @@ -53,7 +53,7 @@ if no route could be matched default response `HTTPMethodNotAllowed` get resturn # use actix_web::*; # fn main() { - Application::default("/") + Application::new("/") .resource("/prefix", |r| { r.method(Method::GET).h(httpcodes::HTTPOk); r.method(Method::POST).h(httpcodes::HTTPForbidden); @@ -86,7 +86,7 @@ fn index(req: HttpRequest) -> String { } fn main() { - Application::default("/") + Application::new("/") .resource("/{name}", |r| r.method(Method::GET).f(index)) .finish(); } @@ -104,7 +104,7 @@ You can also specify a custom regex in the form `{identifier:regex}`: # } # fn main() { - Application::default("/") + Application::new("/") .resource(r"{name:\d+}", |r| r.method(Method::GET).f(index)) .finish(); } @@ -125,7 +125,7 @@ fn index(req: HttpRequest) -> Result { } fn main() { - Application::default("/") + Application::new("/") .resource(r"/a/{v1}/{v2}/", |r| r.f(index)) .finish(); } @@ -143,7 +143,7 @@ It is possible to match path tail with custom `.*` regex. # unimplemented!() # } fn main() { - Application::default("/") + Application::new("/") .resource(r"/test/{tail:.*}", |r| r.method(Method::GET).f(index)) .finish(); } @@ -179,7 +179,7 @@ fn index(req: HttpRequest) -> Result { } fn main() { - Application::default("/") + Application::new("/") .resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index)) .finish(); } diff --git a/guide/src/qs_6.md b/guide/src/qs_6.md index 46802014d..73a342ca3 100644 --- a/guide/src/qs_6.md +++ b/guide/src/qs_6.md @@ -30,7 +30,7 @@ fn index(req: HttpRequest) -> String { } fn main() { - Application::build("/", AppState{counter: Cell::new(0)}) + Application::with_state("/", AppState{counter: Cell::new(0)}) .resource("/", |r| r.method(Method::GET).f(index)) .finish(); } diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 7668f0fef..e1f31dd98 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -75,7 +75,7 @@ fn index(req: HttpRequest) -> Result> { } fn main() { - Application::default("/") + Application::new("/") .resource(r"/a/{name}", |r| r.method(Method::GET).f(index)) .finish(); } diff --git a/src/application.rs b/src/application.rs index d0274725a..7dc811095 100644 --- a/src/application.rs +++ b/src/application.rs @@ -3,11 +3,10 @@ use std::collections::HashMap; use error::UriGenerationError; use handler::{Reply, RouteHandler}; -use route::Route; use resource::Resource; use recognizer::{RouteRecognizer, check_pattern, PatternElement}; use httprequest::HttpRequest; -use channel::HttpHandler; +use channel::{HttpHandler, IntoHttpHandler}; use pipeline::Pipeline; use middlewares::Middleware; @@ -56,16 +55,15 @@ impl Router { } /// Application -pub struct Application { +pub struct HttpApplication { state: Rc, prefix: String, default: Resource, - routes: Vec<(String, Route)>, router: Router, middlewares: Rc>>, } -impl Application { +impl HttpApplication { fn run(&self, req: HttpRequest) -> Reply { let mut req = req.with_state(Rc::clone(&self.state)); @@ -73,21 +71,16 @@ impl Application { if let Some((params, h)) = self.router.0.recognize(req.path()) { if let Some(params) = params { req.set_match_info(params); + req.set_prefix(self.router.0.prefix()); } h.handle(req) } else { - for route in &self.routes { - if req.path().starts_with(&route.0) && route.1.check(&mut req) { - req.set_prefix(route.0.len()); - return route.1.handle(req) - } - } self.default.handle(req) } } } -impl HttpHandler for Application { +impl HttpHandler for HttpApplication { fn handle(&self, req: HttpRequest) -> Result { if req.path().starts_with(&self.prefix) { @@ -99,16 +92,31 @@ impl HttpHandler for Application { } } +struct ApplicationParts { + state: S, + prefix: String, + default: Resource, + resources: HashMap>, + middlewares: Vec>, +} + +/// Structure that follows the builder pattern for building `Application` structs. +pub struct Application { + parts: Option>, +} + impl Application<()> { - /// Create default `ApplicationBuilder` with no state - pub fn default>(prefix: T) -> ApplicationBuilder<()> { - ApplicationBuilder { - parts: Some(ApplicationBuilderParts { + /// Create application with empty state. Application can + /// be configured with builder-like pattern. + /// + /// This method accepts path prefix for which it should serve requests. + pub fn new>(prefix: T) -> Application<()> { + Application { + parts: Some(ApplicationParts { state: (), prefix: prefix.into(), default: Resource::default_not_found(), - routes: Vec::new(), resources: HashMap::new(), middlewares: Vec::new(), }) @@ -118,38 +126,22 @@ impl Application<()> { impl Application where S: 'static { - /// Create application builder with specific state. State is shared with all - /// routes within same application and could be - /// accessed with `HttpContext::state()` method. - pub fn build>(prefix: T, state: S) -> ApplicationBuilder { - ApplicationBuilder { - parts: Some(ApplicationBuilderParts { + /// Create application with specific state. Application can be + /// configured with builder-like pattern. + /// + /// State is shared with all reousrces within same application and could be + /// accessed with `HttpRequest::state()` method. + pub fn with_state>(prefix: T, state: S) -> Application { + Application { + parts: Some(ApplicationParts { state: state, prefix: prefix.into(), default: Resource::default_not_found(), - routes: Vec::new(), resources: HashMap::new(), middlewares: Vec::new(), }) } } -} - -struct ApplicationBuilderParts { - state: S, - prefix: String, - default: Resource, - routes: Vec<(String, Route)>, - resources: HashMap>, - middlewares: Vec>, -} - -/// Structure that follows the builder pattern for building `Application` structs. -pub struct ApplicationBuilder { - parts: Option>, -} - -impl ApplicationBuilder where S: 'static { /// Configure resource for specific path. /// @@ -170,11 +162,11 @@ impl ApplicationBuilder where S: 'static { /// store userid and friend in the exposed Params object: /// /// ```rust - /// extern crate actix_web; + /// # extern crate actix_web; /// use actix_web::*; /// /// fn main() { - /// let app = Application::default("/") + /// let app = Application::new("/") /// .resource("/test", |r| { /// r.method(Method::GET).f(|_| httpcodes::HTTPOk); /// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed); @@ -219,39 +211,43 @@ impl ApplicationBuilder where S: 'static { self } - /// Construct application - pub fn finish(&mut self) -> Application { + /// Finish application configuration and create HttpHandler object + pub fn finish(&mut self) -> HttpApplication { let parts = self.parts.take().expect("Use after finish"); - let prefix = if parts.prefix.ends_with('/') { parts.prefix } else { parts.prefix + "/" }; - - let mut routes = Vec::new(); - for (path, route) in parts.routes { - routes.push((prefix.clone() + path.trim_left_matches('/'), route)); - } - Application { + HttpApplication { state: Rc::new(parts.state), prefix: prefix.clone(), default: parts.default, - routes: routes, router: Router::new(prefix, parts.resources), middlewares: Rc::new(parts.middlewares), } } } -impl From> for Application { - fn from(mut builder: ApplicationBuilder) -> Application { - builder.finish() +impl IntoHttpHandler for Application { + type Handler = HttpApplication; + + fn into_handler(mut self) -> HttpApplication { + self.finish() } } -impl Iterator for ApplicationBuilder { - type Item = Application; +impl<'a, S: 'static> IntoHttpHandler for &'a mut Application { + type Handler = HttpApplication; + + fn into_handler(self) -> HttpApplication { + self.finish() + } +} + +#[doc(hidden)] +impl Iterator for Application { + type Item = HttpApplication; fn next(&mut self) -> Option { if self.parts.is_some() { diff --git a/src/channel.rs b/src/channel.rs index a15591c9f..ac9875a47 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -17,6 +17,23 @@ pub trait HttpHandler: 'static { fn handle(&self, req: HttpRequest) -> Result; } +/// Conversion helper trait +pub trait IntoHttpHandler { + /// The associated type which is result of conversion. + type Handler: HttpHandler; + + /// Convert into `HttpHandler` object. + fn into_handler(self) -> Self::Handler; +} + +impl IntoHttpHandler for T { + type Handler = T; + + fn into_handler(self) -> Self::Handler { + self + } +} + enum HttpProtocol where T: AsyncRead + AsyncWrite + 'static, H: 'static { diff --git a/src/dev.rs b/src/dev.rs index 47efce082..fefcfc71f 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -12,9 +12,8 @@ pub use info::ConnectionInfo; pub use handler::Handler; pub use pipeline::Pipeline; -pub use channel::{HttpChannel, HttpHandler}; +pub use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; pub use recognizer::{FromParam, RouteRecognizer, Pattern, PatternElement}; pub use cookie::CookieBuilder; -pub use application::ApplicationBuilder; pub use httpresponse::HttpResponseBuilder; diff --git a/src/error.rs b/src/error.rs index dcd02fdf0..61aa07df8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -227,7 +227,7 @@ pub enum HttpRangeError { InvalidRange, /// Returned if first-byte-pos of all of the byte-range-spec /// values is greater than the content size. - /// See https://github.com/golang/go/commit/aa9b3d7 + /// See `https://github.com/golang/go/commit/aa9b3d7` #[fail(display="First-byte-pos of all of the byte-range-spec values is greater than the content size")] NoOverlap, } diff --git a/src/fs.rs b/src/fs.rs index 7f0a2a2dc..2c000ffed 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -194,11 +194,12 @@ impl FromRequest for FilesystemElement { /// Can be registered with `Application::route_handler()`. /// /// ```rust -/// extern crate actix_web; +/// # extern crate actix_web; +/// use actix_web::{fs, Application}; /// /// fn main() { -/// let app = actix_web::Application::default("/") -/// .resource("/static", |r| r.h(actix_web::fs::StaticFiles::new(".", true))) +/// let app = Application::new("/") +/// .resource("/static", |r| r.h(fs::StaticFiles::new(".", true))) /// .finish(); /// } /// ``` diff --git a/src/lib.rs b/src/lib.rs index 25a9b2049..8c9218775 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -//! Web framework for [Actix](https://github.com/actix/actix) +//! Actix web is a small, fast, down-to-earth, open source rust web framework. #![cfg_attr(actix_nightly, feature( specialization, // for impl ErrorResponse for std::error::Error diff --git a/src/middlewares/defaultheaders.rs b/src/middlewares/defaultheaders.rs index 08d6a923f..5f4dd8bcd 100644 --- a/src/middlewares/defaultheaders.rs +++ b/src/middlewares/defaultheaders.rs @@ -11,11 +11,11 @@ use middlewares::{Response, Middleware}; /// This middleware does not set header if response headers already contains it. /// /// ```rust -/// extern crate actix_web; +/// # extern crate actix_web; /// use actix_web::*; /// /// fn main() { -/// let app = Application::default("/") +/// let app = Application::new("/") /// .middleware( /// middlewares::DefaultHeaders::build() /// .header("X-Version", "0.2") diff --git a/src/middlewares/logger.rs b/src/middlewares/logger.rs index 51667a22c..a18e8ad15 100644 --- a/src/middlewares/logger.rs +++ b/src/middlewares/logger.rs @@ -22,12 +22,12 @@ use middlewares::{Middleware, Started, Finished}; /// %a %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i" %T /// ``` /// ```rust -/// extern crate actix_web; +/// # extern crate actix_web; /// use actix_web::Application; /// use actix_web::middlewares::Logger; /// /// fn main() { -/// let app = Application::default("/") +/// let app = Application::new("/") /// .middleware(Logger::default()) /// .middleware(Logger::new("%a %{User-Agent}i")) /// .finish(); diff --git a/src/recognizer.rs b/src/recognizer.rs index 3fb34330c..459086550 100644 --- a/src/recognizer.rs +++ b/src/recognizer.rs @@ -241,6 +241,11 @@ impl RouteRecognizer { self.patterns.get(name) } + /// Length of the prefix + pub fn prefix(&self) -> usize { + self.prefix + } + pub fn set_prefix>(&mut self, prefix: P) { let p = prefix.into(); if p.ends_with('/') { diff --git a/src/resource.rs b/src/resource.rs index f499c42b1..3dc50c959 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -18,13 +18,13 @@ use httprequest::HttpRequest; /// route considired matched and route handler get called. /// /// ```rust -/// extern crate actix_web; +/// # extern crate actix_web; /// use actix_web::*; /// /// fn main() { -/// let app = Application::default("/") +/// let app = Application::new("/") /// .resource( -/// "/", |r| r.route().method(Method::GET).f(|r| HttpResponse::Ok())) +/// "/", |r| r.method(Method::GET).f(|r| HttpResponse::Ok())) /// .finish(); /// } pub struct Resource { @@ -67,11 +67,11 @@ impl Resource where S: 'static { /// *Route* is used for route configuration, i.e. adding predicates, setting up handler. /// /// ```rust - /// extern crate actix_web; + /// # extern crate actix_web; /// use actix_web::*; /// /// fn main() { - /// let app = Application::default("/") + /// let app = Application::new("/") /// .resource( /// "/", |r| r.route() /// .p(pred::Any(vec![pred::Get(), pred::Put()])) @@ -87,7 +87,8 @@ impl Resource where S: 'static { /// Register a new route and add method check to route. /// - /// This is sortcut for: + /// This is shortcut for: + /// /// ```rust,ignore /// Resource::resource("/", |r| r.route().method(Method::GET).f(index) /// ``` @@ -98,7 +99,8 @@ impl Resource where S: 'static { /// Register a new route and add handler object. /// - /// This is sortcut for: + /// This is shortcut for: + /// /// ```rust,ignore /// Resource::resource("/", |r| r.route().h(handler) /// ``` @@ -109,7 +111,8 @@ impl Resource where S: 'static { /// Register a new route and add handler function. /// - /// This is sortcut for: + /// This is shortcut for: + /// /// ```rust,ignore /// Resource::resource("/", |r| r.route().f(index) /// ``` diff --git a/src/server.rs b/src/server.rs index d3fd36147..0a58449ff 100644 --- a/src/server.rs +++ b/src/server.rs @@ -24,7 +24,7 @@ use openssl::pkcs12::ParsedPkcs12; #[cfg(feature="alpn")] use tokio_openssl::{SslStream, SslAcceptorExt}; -use channel::{HttpChannel, HttpHandler}; +use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; /// An HTTP Server @@ -47,8 +47,10 @@ impl Actor for HttpServer { impl HttpServer where H: HttpHandler { /// Create new http server with vec of http handlers - pub fn new>(handler: U) -> Self { - let apps: Vec<_> = handler.into_iter().collect(); + pub fn new>(handler: U) -> Self + where V: IntoHttpHandler + { + let apps: Vec<_> = handler.into_iter().map(|h| h.into_handler()).collect(); HttpServer {h: Rc::new(apps), io: PhantomData, diff --git a/src/ws.rs b/src/ws.rs index cbde61e59..551dee622 100644 --- a/src/ws.rs +++ b/src/ws.rs @@ -42,7 +42,7 @@ //! } //! //! fn main() { -//! Application::default("/") +//! Application::new("/") //! .resource("/ws/", |r| r.method(Method::GET).f(ws_index)) // <- register websocket route //! .finish(); //! } diff --git a/tests/test_server.rs b/tests/test_server.rs index 45d0f46e4..7704c3e35 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -11,20 +11,13 @@ use tokio_core::net::TcpListener; use actix::*; use actix_web::*; - -fn create_server() -> HttpServer> { - HttpServer::new( - vec![Application::default("/") - .resource("/", |r| - r.route().method(Method::GET).h(httpcodes::HTTPOk)) - .finish()]) -} - #[test] fn test_serve() { thread::spawn(|| { let sys = System::new("test"); - let srv = create_server(); + let srv = HttpServer::new( + vec![Application::new("/") + .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))]); srv.serve::<_, ()>("127.0.0.1:58902").unwrap(); sys.run(); }); @@ -42,7 +35,9 @@ fn test_serve_incoming() { thread::spawn(move || { let sys = System::new("test"); - let srv = create_server(); + let srv = HttpServer::new( + Application::new("/") + .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))); let tcp = TcpListener::from_listener(tcp, &addr2, Arbiter::handle()).unwrap(); srv.serve_incoming::<_, ()>(tcp.incoming()).unwrap(); sys.run(); @@ -89,19 +84,17 @@ fn test_middlewares() { let sys = System::new("test"); HttpServer::new( - vec![Application::default("/") + vec![Application::new("/") .middleware(MiddlewareTest{start: act_num1, response: act_num2, finish: act_num3}) - .resource("/", |r| - r.route().method(Method::GET).h(httpcodes::HTTPOk)) + .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk)) .finish()]) .serve::<_, ()>("127.0.0.1:58904").unwrap(); sys.run(); }); assert!(reqwest::get("http://localhost:58904/").unwrap().status().is_success()); - assert_eq!(num1.load(Ordering::Relaxed), 1); assert_eq!(num2.load(Ordering::Relaxed), 1); assert_eq!(num3.load(Ordering::Relaxed), 1); From 8d52e2bbd9ff16ec27c609264c326279c26da4ed Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Dec 2017 13:02:53 -0800 Subject: [PATCH 041/279] tests for default resource --- src/application.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/application.rs b/src/application.rs index 7dc811095..f0833e416 100644 --- a/src/application.rs +++ b/src/application.rs @@ -257,3 +257,53 @@ impl Iterator for Application { } } } + + +#[cfg(test)] +mod tests { + use std::str::FromStr; + use http::{Method, Version, Uri, HeaderMap, StatusCode}; + use super::*; + use handler::ReplyItem; + use httprequest::HttpRequest; + use httpresponse::HttpResponse; + use payload::Payload; + use httpcodes; + + impl Reply { + fn msg(self) -> Option { + match self.into() { + ReplyItem::Message(resp) => Some(resp), + _ => None, + } + } + } + + #[test] + fn test_default_resource() { + let app = Application::new("/") + .resource("/test", |r| r.h(httpcodes::HTTPOk)) + .finish(); + + let req = HttpRequest::new( + Method::GET, Uri::from_str("/test").unwrap(), + Version::HTTP_11, HeaderMap::new(), Payload::empty()); + let resp = app.run(req).msg().unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let req = HttpRequest::new( + Method::GET, Uri::from_str("/blah").unwrap(), + Version::HTTP_11, HeaderMap::new(), Payload::empty()); + let resp = app.run(req).msg().unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + + let app = Application::new("/") + .default_resource(|r| r.h(httpcodes::HTTPMethodNotAllowed)) + .finish(); + let req = HttpRequest::new( + Method::GET, Uri::from_str("/blah").unwrap(), + Version::HTTP_11, HeaderMap::new(), Payload::empty()); + let resp = app.run(req).msg().unwrap(); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + } +} From 0dd27bd22437ff9a12129a7be36e2efb7b930fb1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Dec 2017 16:26:27 -0800 Subject: [PATCH 042/279] added HttpRequest::url_for --- src/application.rs | 62 ++++-------------------------- src/dev.rs | 1 + src/error.rs | 16 +++++++- src/httprequest.rs | 79 +++++++++++++++++++++++++++++++++------ src/lib.rs | 1 + src/recognizer.rs | 28 +++++--------- src/resource.rs | 5 ++- src/router.rs | 73 ++++++++++++++++++++++++++++++++++++ tests/test_httprequest.rs | 2 +- 9 files changed, 179 insertions(+), 88 deletions(-) create mode 100644 src/router.rs diff --git a/src/application.rs b/src/application.rs index f0833e416..ff3374619 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,59 +1,15 @@ use std::rc::Rc; use std::collections::HashMap; -use error::UriGenerationError; use handler::{Reply, RouteHandler}; +use router::Router; use resource::Resource; -use recognizer::{RouteRecognizer, check_pattern, PatternElement}; +use recognizer::check_pattern; use httprequest::HttpRequest; use channel::{HttpHandler, IntoHttpHandler}; use pipeline::Pipeline; use middlewares::Middleware; -pub struct Router(Rc>>); - -impl Router { - pub fn new(prefix: String, map: HashMap>) -> Router - { - let mut resources = Vec::new(); - for (path, resource) in map { - resources.push((path, resource.get_name(), resource)) - } - - Router(Rc::new(RouteRecognizer::new(prefix, resources))) - } - - pub fn has_route(&self, path: &str) -> bool { - self.0.recognize(path).is_some() - } - - pub fn resource_path<'a, U>(&self, prefix: &str, name: &str, elements: U) - -> Result - where U: IntoIterator - { - if let Some(pattern) = self.0.get_pattern(name) { - let mut iter = elements.into_iter(); - let mut vec = vec![prefix]; - for el in pattern.elements() { - match *el { - PatternElement::Str(ref s) => vec.push(s), - PatternElement::Var(_) => { - if let Some(val) = iter.next() { - vec.push(val) - } else { - return Err(UriGenerationError::NotEnoughElements) - } - } - } - } - let s = vec.join("/").to_owned(); - Ok(s) - } else { - Err(UriGenerationError::ResourceNotFound) - } - } -} - /// Application pub struct HttpApplication { state: Rc, @@ -66,12 +22,12 @@ pub struct HttpApplication { impl HttpApplication { fn run(&self, req: HttpRequest) -> Reply { - let mut req = req.with_state(Rc::clone(&self.state)); + let mut req = req.with_state(Rc::clone(&self.state), self.router.clone()); - if let Some((params, h)) = self.router.0.recognize(req.path()) { + if let Some((params, h)) = self.router.query(req.path()) { if let Some(params) = params { req.set_match_info(params); - req.set_prefix(self.router.0.prefix()); + req.set_prefix(self.router.prefix().len()); } h.handle(req) } else { @@ -214,14 +170,10 @@ impl Application where S: 'static { /// Finish application configuration and create HttpHandler object pub fn finish(&mut self) -> HttpApplication { let parts = self.parts.take().expect("Use after finish"); - let prefix = if parts.prefix.ends_with('/') { - parts.prefix - } else { - parts.prefix + "/" - }; + let prefix = parts.prefix.trim().trim_right_matches('/'); HttpApplication { state: Rc::new(parts.state), - prefix: prefix.clone(), + prefix: prefix.to_owned(), default: parts.default, router: Router::new(prefix, parts.resources), middlewares: Rc::new(parts.middlewares), diff --git a/src/dev.rs b/src/dev.rs index fefcfc71f..833fecf03 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -11,6 +11,7 @@ // dev specific pub use info::ConnectionInfo; pub use handler::Handler; +pub use router::Router; pub use pipeline::Pipeline; pub use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; pub use recognizer::{FromParam, RouteRecognizer, Pattern, PatternElement}; diff --git a/src/error.rs b/src/error.rs index 61aa07df8..d2434b6db 100644 --- a/src/error.rs +++ b/src/error.rs @@ -15,6 +15,7 @@ use http::{header, StatusCode, Error as HttpError}; use http::uri::InvalidUriBytes; use http_range::HttpRangeParseError; use serde_json::error::Error as JsonError; +use url::ParseError as UrlParseError; // re-exports pub use cookie::{ParseError as CookieParseError}; @@ -405,11 +406,24 @@ impl ResponseError for UriSegmentError { /// Errors which can occur when attempting to generate resource uri. #[derive(Fail, Debug, PartialEq)] -pub enum UriGenerationError { +pub enum UrlGenerationError { #[fail(display="Resource not found")] ResourceNotFound, #[fail(display="Not all path pattern covered")] NotEnoughElements, + #[fail(display="Router is not available")] + RouterNotAvailable, + #[fail(display="{}", _0)] + ParseError(#[cause] UrlParseError), +} + +/// `InternalServerError` for `UrlGeneratorError` +impl ResponseError for UrlGenerationError {} + +impl From for UrlGenerationError { + fn from(err: UrlParseError) -> Self { + UrlGenerationError::ParseError(err) + } } #[cfg(test)] diff --git a/src/httprequest.rs b/src/httprequest.rs index 3c30037eb..07ae8b4e1 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -5,15 +5,16 @@ use std::net::SocketAddr; use std::collections::HashMap; use bytes::BytesMut; use futures::{Async, Future, Stream, Poll}; -use url::form_urlencoded; +use url::{Url, form_urlencoded}; use http::{header, Uri, Method, Version, HeaderMap, Extensions}; use {Cookie, HttpRange}; use info::ConnectionInfo; +use router::Router; use recognizer::Params; use payload::Payload; use multipart::Multipart; -use error::{ParseError, PayloadError, +use error::{ParseError, PayloadError, UrlGenerationError, MultipartError, CookieParseError, HttpRangeError, UrlencodedError}; @@ -30,6 +31,7 @@ struct HttpMessage { addr: Option, payload: Payload, info: Option>, + } impl Default for HttpMessage { @@ -53,7 +55,7 @@ impl Default for HttpMessage { } /// An HTTP Request -pub struct HttpRequest(Rc, Rc); +pub struct HttpRequest(Rc, Rc, Option>); impl HttpRequest<()> { /// Construct a new Request. @@ -76,13 +78,14 @@ impl HttpRequest<()> { extensions: Extensions::new(), info: None, }), - Rc::new(()) + Rc::new(()), + None, ) } /// Construct new http request with state. - pub fn with_state(self, state: Rc) -> HttpRequest { - HttpRequest(self.0, state) + pub fn with_state(self, state: Rc, router: Router) -> HttpRequest { + HttpRequest(self.0, state, Some(router)) } } @@ -90,10 +93,11 @@ impl HttpRequest { /// Construct new http request without state. pub fn clone_without_state(&self) -> HttpRequest { - HttpRequest(Rc::clone(&self.0), Rc::new(())) + HttpRequest(Rc::clone(&self.0), Rc::new(()), None) } /// get mutable reference for inner message + #[inline] fn as_mut(&mut self) -> &mut HttpMessage { let r: &HttpMessage = self.0.as_ref(); #[allow(mutable_transmutes)] @@ -101,6 +105,7 @@ impl HttpRequest { } /// Shared application state + #[inline] pub fn state(&self) -> &S { &self.1 } @@ -111,6 +116,7 @@ impl HttpRequest { &mut self.as_mut().extensions } + #[inline] pub(crate) fn set_prefix(&mut self, idx: usize) { self.as_mut().prefix = idx; } @@ -162,7 +168,6 @@ impl HttpRequest { } /// Load *ConnectionInfo* for currect request. - #[inline] pub fn load_connection_info(&mut self) -> &ConnectionInfo { if self.0.info.is_none() { let info: ConnectionInfo<'static> = unsafe{ @@ -172,17 +177,35 @@ impl HttpRequest { self.0.info.as_ref().unwrap() } + pub fn url_for(&mut self, name: &str, elements: U) -> Result + where U: IntoIterator, + I: AsRef, + { + if self.router().is_none() { + Err(UrlGenerationError::RouterNotAvailable) + } else { + let path = self.router().unwrap().resource_path(name, elements)?; + let conn = self.load_connection_info(); + Ok(Url::parse(&format!("{}://{}{}", conn.scheme(), conn.host(), path))?) + } + } + + #[inline] + pub fn router(&self) -> Option<&Router> { + self.2.as_ref() + } + #[inline] pub fn peer_addr(&self) -> Option<&SocketAddr> { self.0.addr.as_ref() } + #[inline] pub(crate) fn set_peer_addr(&mut self, addr: Option) { self.as_mut().addr = addr } /// Return a new iterator that yields pairs of `Cow` for query parameters - #[inline] pub fn query(&self) -> HashMap { let mut q: HashMap = HashMap::new(); if let Some(query) = self.0.uri.query().as_ref() { @@ -206,6 +229,7 @@ impl HttpRequest { } /// Return request cookies. + #[inline] pub fn cookies(&self) -> &Vec> { &self.0.cookies } @@ -245,6 +269,7 @@ impl HttpRequest { pub fn match_info(&self) -> &Params { &self.0.params } /// Set request Params. + #[inline] pub fn set_match_info(&mut self, params: Params) { self.as_mut().params = params; } @@ -324,6 +349,7 @@ impl HttpRequest { } /// Return payload + #[inline] pub fn take_payload(&mut self) -> Payload { mem::replace(&mut self.as_mut().payload, Payload::empty()) } @@ -387,13 +413,13 @@ impl Default for HttpRequest<()> { /// Construct default request fn default() -> HttpRequest { - HttpRequest(Rc::new(HttpMessage::default()), Rc::new(())) + HttpRequest(Rc::new(HttpMessage::default()), Rc::new(()), None) } } impl Clone for HttpRequest { fn clone(&self) -> HttpRequest { - HttpRequest(Rc::clone(&self.0), Rc::clone(&self.1)) + HttpRequest(Rc::clone(&self.0), Rc::clone(&self.1), None) } } @@ -454,9 +480,10 @@ impl Future for UrlEncoded { #[cfg(test)] mod tests { use super::*; + use http::Uri; use std::str::FromStr; use payload::Payload; - use http::Uri; + use resource::Resource; #[test] fn test_urlencoded_error() { @@ -502,4 +529,32 @@ mod tests { assert_eq!(req.urlencoded().err().unwrap(), UrlencodedError::ContentType); } + + #[test] + fn test_url_for() { + let mut headers = HeaderMap::new(); + headers.insert(header::HOST, + header::HeaderValue::from_static("www.rust-lang.org")); + let mut req = HttpRequest::new( + Method::GET, Uri::from_str("/").unwrap(), + Version::HTTP_11, headers, Payload::empty()); + + let mut resource = Resource::default(); + resource.name("index"); + let mut map = HashMap::new(); + map.insert("/user/{name}.{ext}".to_owned(), resource); + let router = Router::new("", map); + + assert_eq!(req.url_for("unknown", &["test"]), + Err(UrlGenerationError::RouterNotAvailable)); + + let mut req = req.with_state(Rc::new(()), router); + + assert_eq!(req.url_for("unknown", &["test"]), + Err(UrlGenerationError::ResourceNotFound)); + assert_eq!(req.url_for("index", &["test"]), + Err(UrlGenerationError::NotEnoughElements)); + let url = req.url_for("index", &["test", "html"]); + assert_eq!(url.ok().unwrap().as_str(), "http://www.rust-lang.org/user/test.html"); + } } diff --git a/src/lib.rs b/src/lib.rs index 8c9218775..f490ec44b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,6 +57,7 @@ mod httpresponse; mod payload; mod info; mod route; +mod router; mod resource; mod recognizer; mod handler; diff --git a/src/recognizer.rs b/src/recognizer.rs index 459086550..5f2a9b00a 100644 --- a/src/recognizer.rs +++ b/src/recognizer.rs @@ -204,16 +204,17 @@ FROM_STR!(std::net::SocketAddrV6); pub struct RouteRecognizer { re: RegexSet, - prefix: usize, + prefix: String, routes: Vec<(Pattern, T)>, patterns: HashMap, } impl RouteRecognizer { - pub fn new, U, K>(prefix: P, routes: U) -> Self + pub fn new(prefix: P, routes: U) -> Self where U: IntoIterator, T)>, K: Into, + P: Into, { let mut paths = Vec::new(); let mut handlers = Vec::new(); @@ -231,7 +232,7 @@ impl RouteRecognizer { RouteRecognizer { re: regset.unwrap(), - prefix: prefix.into().len() - 1, + prefix: prefix.into(), routes: handlers, patterns: patterns, } @@ -242,29 +243,20 @@ impl RouteRecognizer { } /// Length of the prefix - pub fn prefix(&self) -> usize { - self.prefix - } - - pub fn set_prefix>(&mut self, prefix: P) { - let p = prefix.into(); - if p.ends_with('/') { - self.prefix = p.len() - 1; - } else { - self.prefix = p.len(); - } + pub fn prefix(&self) -> &str { + &self.prefix } pub fn recognize(&self, path: &str) -> Option<(Option, &T)> { - let p = &path[self.prefix..]; + let p = &path[self.prefix.len()..]; if p.is_empty() { if let Some(idx) = self.re.matches("/").into_iter().next() { let (ref pattern, ref route) = self.routes[idx]; - return Some((pattern.match_info(&path[self.prefix..]), route)) + return Some((pattern.match_info(&path[self.prefix.len()..]), route)) } } else if let Some(idx) = self.re.matches(p).into_iter().next() { let (ref pattern, ref route) = self.routes[idx]; - return Some((pattern.match_info(&path[self.prefix..]), route)) + return Some((pattern.match_info(&path[self.prefix.len()..]), route)) } None } @@ -400,7 +392,7 @@ mod tests { ("/v{val}/{val2}/index.html", None, 4), ("/v/{tail:.*}", None, 5), ]; - let rec = RouteRecognizer::new("/", routes); + let rec = RouteRecognizer::new("", routes); let (params, val) = rec.recognize("/name").unwrap(); assert_eq!(*val, 1); diff --git a/src/resource.rs b/src/resource.rs index 3dc50c959..4652a32ff 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -44,7 +44,7 @@ impl Default for Resource { } } -impl Resource where S: 'static { +impl Resource { pub(crate) fn default_not_found() -> Self { Resource { @@ -62,6 +62,9 @@ impl Resource where S: 'static { pub(crate) fn get_name(&self) -> Option { if self.name.is_empty() { None } else { Some(self.name.clone()) } } +} + +impl Resource { /// Register a new route and return mutable reference to *Route* object. /// *Route* is used for route configuration, i.e. adding predicates, setting up handler. diff --git a/src/router.rs b/src/router.rs new file mode 100644 index 000000000..2009d9510 --- /dev/null +++ b/src/router.rs @@ -0,0 +1,73 @@ +use std::rc::Rc; +use std::collections::HashMap; + +use error::UrlGenerationError; +use resource::Resource; +use recognizer::{Params, RouteRecognizer, PatternElement}; + + +/// Interface for application router. +pub struct Router(Rc>>); + +impl Router { + pub(crate) fn new(prefix: &str, map: HashMap>) -> Router + { + let prefix = prefix.trim().trim_right_matches('/').to_owned(); + let mut resources = Vec::new(); + for (path, resource) in map { + resources.push((path, resource.get_name(), resource)) + } + + Router(Rc::new(RouteRecognizer::new(prefix, resources))) + } + + /// Router prefix + #[inline] + pub(crate) fn prefix(&self) -> &str { + self.0.prefix() + } + + /// Query for matched resource + pub fn query(&self, path: &str) -> Option<(Option, &Resource)> { + self.0.recognize(path) + } + + /// Check if application contains matching route. + pub fn has_route(&self, path: &str) -> bool { + self.0.recognize(path).is_some() + } + + /// Build named resource path + pub fn resource_path(&self, name: &str, elements: U) + -> Result + where U: IntoIterator, + I: AsRef, + { + if let Some(pattern) = self.0.get_pattern(name) { + let mut path = String::from(self.prefix()); + path.push('/'); + let mut iter = elements.into_iter(); + for el in pattern.elements() { + match *el { + PatternElement::Str(ref s) => path.push_str(s), + PatternElement::Var(_) => { + if let Some(val) = iter.next() { + path.push_str(val.as_ref()) + } else { + return Err(UrlGenerationError::NotEnoughElements) + } + } + } + } + Ok(path) + } else { + Err(UrlGenerationError::ResourceNotFound) + } + } +} + +impl Clone for Router { + fn clone(&self) -> Router { + Router(Rc::clone(&self.0)) + } +} diff --git a/tests/test_httprequest.rs b/tests/test_httprequest.rs index 794864cd0..263ba094d 100644 --- a/tests/test_httprequest.rs +++ b/tests/test_httprequest.rs @@ -92,7 +92,7 @@ fn test_request_match_info() { let mut req = HttpRequest::new(Method::GET, Uri::from_str("/value/?id=test").unwrap(), Version::HTTP_11, HeaderMap::new(), Payload::empty()); - let rec = RouteRecognizer::new("/".to_owned(), vec![("/{key}/".to_owned(), None, 1)]); + let rec = RouteRecognizer::new("", vec![("/{key}/".to_owned(), None, 1)]); let (params, _) = rec.recognize(req.path()).unwrap(); let params = params.unwrap(); From a18bd5dac0e3a7952cef8a59da8a3da44bc30eec Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Dec 2017 16:34:54 -0800 Subject: [PATCH 043/279] add doc ref --- src/router.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/router.rs b/src/router.rs index 2009d9510..ec07d3c58 100644 --- a/src/router.rs +++ b/src/router.rs @@ -37,7 +37,10 @@ impl Router { self.0.recognize(path).is_some() } - /// Build named resource path + /// Build named resource path. + /// + /// Check [`HttpRequest::url_for()`](../struct.HttpRequest.html#method.url_for) + /// for detailed information. pub fn resource_path(&self, name: &str, elements: U) -> Result where U: IntoIterator, From 63502fa833c66267d786f9a67f63da1d6dd1c0da Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Dec 2017 16:40:23 -0800 Subject: [PATCH 044/279] test for Router::has_route --- src/httprequest.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/httprequest.rs b/src/httprequest.rs index 07ae8b4e1..f4b052a73 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -550,6 +550,9 @@ mod tests { let mut req = req.with_state(Rc::new(()), router); + assert_eq!(req.router().unwrap().has_route("index")); + assert_eq!(!req.router().unwrap().has_route("unknown")); + assert_eq!(req.url_for("unknown", &["test"]), Err(UrlGenerationError::ResourceNotFound)); assert_eq!(req.url_for("index", &["test"]), From 9ea0781aba57a6cc1d29313d4d71db6be35e5ead Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Dec 2017 16:58:49 -0800 Subject: [PATCH 045/279] fix test --- src/httprequest.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index f4b052a73..807059b13 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -544,15 +544,14 @@ mod tests { let mut map = HashMap::new(); map.insert("/user/{name}.{ext}".to_owned(), resource); let router = Router::new("", map); + assert!(router.has_route("/user/test.html")); + assert!(!router.has_route("/test/unknown")); assert_eq!(req.url_for("unknown", &["test"]), Err(UrlGenerationError::RouterNotAvailable)); let mut req = req.with_state(Rc::new(()), router); - assert_eq!(req.router().unwrap().has_route("index")); - assert_eq!(!req.router().unwrap().has_route("unknown")); - assert_eq!(req.url_for("unknown", &["test"]), Err(UrlGenerationError::ResourceNotFound)); assert_eq!(req.url_for("index", &["test"]), From 4b03d0340455ccc1111ede113407b317b8f4eabe Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Dec 2017 17:06:40 -0800 Subject: [PATCH 046/279] rearrange exports --- src/dev.rs | 20 -------------------- src/httprequest.rs | 3 ++- src/lib.rs | 27 ++++++++++++++++++++++++--- 3 files changed, 26 insertions(+), 24 deletions(-) delete mode 100644 src/dev.rs diff --git a/src/dev.rs b/src/dev.rs deleted file mode 100644 index 833fecf03..000000000 --- a/src/dev.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! The `actix-web` prelude for library developers -//! -//! The purpose of this module is to alleviate imports of many common actix traits -//! by adding a glob import to the top of actix heavy modules: -//! -//! ``` -//! # #![allow(unused_imports)] -//! use actix_web::dev::*; -//! ``` - -// dev specific -pub use info::ConnectionInfo; -pub use handler::Handler; -pub use router::Router; -pub use pipeline::Pipeline; -pub use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; -pub use recognizer::{FromParam, RouteRecognizer, Pattern, PatternElement}; - -pub use cookie::CookieBuilder; -pub use httpresponse::HttpResponseBuilder; diff --git a/src/httprequest.rs b/src/httprequest.rs index 807059b13..11acf1d7e 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -6,9 +6,10 @@ use std::collections::HashMap; use bytes::BytesMut; use futures::{Async, Future, Stream, Poll}; use url::{Url, form_urlencoded}; +pub use http_range::HttpRange; use http::{header, Uri, Method, Version, HeaderMap, Extensions}; -use {Cookie, HttpRange}; +use Cookie; use info::ConnectionInfo; use router::Router; use recognizer::Params; diff --git a/src/lib.rs b/src/lib.rs index f490ec44b..8eaec276f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,7 +73,6 @@ mod h2writer; pub mod fs; pub mod ws; -pub mod dev; pub mod error; pub mod httpcodes; pub mod multipart; @@ -89,14 +88,12 @@ pub use payload::{Payload, PayloadItem}; pub use handler::{Reply, Json, FromRequest}; pub use route::Route; pub use resource::Resource; -pub use recognizer::Params; pub use server::HttpServer; pub use context::HttpContext; // re-exports pub use http::{Method, StatusCode, Version}; pub use cookie::Cookie; -pub use http_range::HttpRange; #[doc(hidden)] #[cfg(feature="tls")] @@ -105,3 +102,27 @@ pub use native_tls::Pkcs12; #[doc(hidden)] #[cfg(feature="openssl")] pub use openssl::pkcs12::Pkcs12; + +pub mod dev { +//! The `actix-web` prelude for library developers +//! +//! The purpose of this module is to alleviate imports of many common actix traits +//! by adding a glob import to the top of actix heavy modules: +//! +//! ``` +//! # #![allow(unused_imports)] +//! use actix_web::dev::*; +//! ``` + + // dev specific + pub use info::ConnectionInfo; + pub use handler::Handler; + pub use router::Router; + pub use pipeline::Pipeline; + pub use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; + pub use recognizer::{FromParam, RouteRecognizer, Params, Pattern, PatternElement}; + + pub use cookie::CookieBuilder; + pub use http_range::HttpRange; + pub use httpresponse::HttpResponseBuilder; +} From 2a0d5db41a692e26f48d780b4ecac8af7856f56a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Dec 2017 18:39:13 -0800 Subject: [PATCH 047/279] more tests --- src/application.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/application.rs b/src/application.rs index ff3374619..ae5e5c007 100644 --- a/src/application.rs +++ b/src/application.rs @@ -258,4 +258,21 @@ mod tests { let resp = app.run(req).msg().unwrap(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); } + + #[test] + fn test_unhandled_prefix() { + let app = Application::new("/test") + .resource("/test", |r| r.h(httpcodes::HTTPOk)) + .finish(); + assert!(app.handle(HttpRequest::default()).is_err()); + } + + #[test] + fn test_state() { + let app = Application::with_state("/", 10) + .resource("/", |r| r.h(httpcodes::HTTPOk)) + .finish(); + assert_eq!( + app.run(HttpRequest::default()).msg().unwrap().status(), StatusCode::OK); + } } From 9e3aa5915518841132bcfbbbfcae04ae168ab33d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Dec 2017 10:23:14 -0800 Subject: [PATCH 048/279] ignore tests --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 883a9d40c..5449c1ab5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,7 +50,7 @@ after_success: - | if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) - cargo tarpaulin --out Xml + cargo tarpaulin --ignore-tests --out Xml bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi From 968f5d39d6cf9759b94854c523bc5f2a56596301 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Dec 2017 16:22:26 -0800 Subject: [PATCH 049/279] added external resources; refactor route recognizer --- .travis.yml | 2 +- Cargo.toml | 1 + src/application.rs | 75 ++++++-- src/fs.rs | 2 +- src/httprequest.rs | 43 ++++- src/lib.rs | 9 +- src/param.rs | 203 ++++++++++++++++++++ src/recognizer.rs | 324 ++------------------------------ src/resource.rs | 4 +- src/router.rs | 381 +++++++++++++++++++++++++++++++++++--- tests/test_httprequest.rs | 11 +- 11 files changed, 686 insertions(+), 369 deletions(-) create mode 100644 src/param.rs diff --git a/.travis.yml b/.travis.yml index 5449c1ab5..883a9d40c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,7 +50,7 @@ after_success: - | if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) - cargo tarpaulin --ignore-tests --out Xml + cargo tarpaulin --out Xml bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi diff --git a/Cargo.toml b/Cargo.toml index bb4b7b932..2ff98b27f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ serde_json = "1.0" flate2 = "0.2" brotli2 = "^0.3.2" percent-encoding = "1.0" +smallvec = "0.6" # redis-async = { git="https://github.com/benashford/redis-async-rs" } diff --git a/src/application.rs b/src/application.rs index ae5e5c007..0c2b376c3 100644 --- a/src/application.rs +++ b/src/application.rs @@ -2,9 +2,8 @@ use std::rc::Rc; use std::collections::HashMap; use handler::{Reply, RouteHandler}; -use router::Router; +use router::{Router, Pattern}; use resource::Resource; -use recognizer::check_pattern; use httprequest::HttpRequest; use channel::{HttpHandler, IntoHttpHandler}; use pipeline::Pipeline; @@ -24,11 +23,7 @@ impl HttpApplication { fn run(&self, req: HttpRequest) -> Reply { let mut req = req.with_state(Rc::clone(&self.state), self.router.clone()); - if let Some((params, h)) = self.router.query(req.path()) { - if let Some(params) = params { - req.set_match_info(params); - req.set_prefix(self.router.prefix().len()); - } + if let Some(h) = self.router.recognize(&mut req) { h.handle(req) } else { self.default.handle(req) @@ -52,7 +47,8 @@ struct ApplicationParts { state: S, prefix: String, default: Resource, - resources: HashMap>, + resources: HashMap>>, + external: HashMap, middlewares: Vec>, } @@ -74,6 +70,7 @@ impl Application<()> { prefix: prefix.into(), default: Resource::default_not_found(), resources: HashMap::new(), + external: HashMap::new(), middlewares: Vec::new(), }) } @@ -94,6 +91,7 @@ impl Application where S: 'static { prefix: prefix.into(), default: Resource::default_not_found(), resources: HashMap::new(), + external: HashMap::new(), middlewares: Vec::new(), }) } @@ -130,19 +128,22 @@ impl Application where S: 'static { /// .finish(); /// } /// ``` - pub fn resource>(&mut self, path: P, f: F) -> &mut Self + pub fn resource(&mut self, path: &str, f: F) -> &mut Self where F: FnOnce(&mut Resource) + 'static { { let parts = self.parts.as_mut().expect("Use after finish"); // add resource - let path = path.into(); - if !parts.resources.contains_key(&path) { - check_pattern(&path); - parts.resources.insert(path.clone(), Resource::default()); + let mut resource = Resource::default(); + f(&mut resource); + + let pattern = Pattern::new(resource.get_name(), path); + if parts.resources.contains_key(&pattern) { + panic!("Resource {:?} is registered.", path); } - f(parts.resources.get_mut(&path).unwrap()); + + parts.resources.insert(pattern, Some(resource)); } self } @@ -158,6 +159,44 @@ impl Application where S: 'static { self } + /// Register external resource. + /// + /// External resources are useful for URL generation purposes only and + /// are never considered for matching at request time. + /// Call to `HttpRequest::url_for()` will work as expected. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::*; + /// + /// fn index(mut req: HttpRequest) -> Result { + /// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?; + /// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); + /// Ok(httpcodes::HTTPOk.into()) + /// } + /// + /// fn main() { + /// let app = Application::new("/") + /// .resource("/index.html", |r| r.f(index)) + /// .external_resource("youtube", "https://youtube.com/watch/{video_id}") + /// .finish(); + /// } + /// ``` + pub fn external_resource(&mut self, name: T, url: U) -> &mut Self + where T: AsRef, U: AsRef + { + { + let parts = self.parts.as_mut().expect("Use after finish"); + + if parts.external.contains_key(name.as_ref()) { + panic!("External resource {:?} is registered.", name.as_ref()); + } + parts.external.insert( + String::from(name.as_ref()), Pattern::new(name.as_ref(), url.as_ref())); + } + self + } + /// Register a middleware pub fn middleware(&mut self, mw: T) -> &mut Self where T: Middleware + 'static @@ -171,11 +210,17 @@ impl Application where S: 'static { pub fn finish(&mut self) -> HttpApplication { let parts = self.parts.take().expect("Use after finish"); let prefix = parts.prefix.trim().trim_right_matches('/'); + + let mut resources = parts.resources; + for (_, pattern) in parts.external { + resources.insert(pattern, None); + } + HttpApplication { state: Rc::new(parts.state), prefix: prefix.to_owned(), default: parts.default, - router: Router::new(prefix, parts.resources), + router: Router::new(prefix, resources), middlewares: Rc::new(parts.middlewares), } } diff --git a/src/fs.rs b/src/fs.rs index 2c000ffed..a5a015d1a 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -9,8 +9,8 @@ use std::path::{Path, PathBuf}; use std::ops::{Deref, DerefMut}; use mime_guess::get_mime_type; +use param::FromParam; use handler::{Handler, FromRequest}; -use recognizer::FromParam; use httprequest::HttpRequest; use httpresponse::HttpResponse; use httpcodes::HTTPOk; diff --git a/src/httprequest.rs b/src/httprequest.rs index 11acf1d7e..3747aabbb 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -11,8 +11,8 @@ use http::{header, Uri, Method, Version, HeaderMap, Extensions}; use Cookie; use info::ConnectionInfo; +use param::Params; use router::Router; -use recognizer::Params; use payload::Payload; use multipart::Multipart; use error::{ParseError, PayloadError, UrlGenerationError, @@ -26,7 +26,7 @@ struct HttpMessage { prefix: usize, headers: HeaderMap, extensions: Extensions, - params: Params, + params: Params<'static>, cookies: Vec>, cookies_loaded: bool, addr: Option, @@ -186,8 +186,12 @@ impl HttpRequest { Err(UrlGenerationError::RouterNotAvailable) } else { let path = self.router().unwrap().resource_path(name, elements)?; - let conn = self.load_connection_info(); - Ok(Url::parse(&format!("{}://{}{}", conn.scheme(), conn.host(), path))?) + if path.starts_with('/') { + let conn = self.load_connection_info(); + Ok(Url::parse(&format!("{}://{}{}", conn.scheme(), conn.host(), path))?) + } else { + Ok(Url::parse(&path)?) + } } } @@ -267,12 +271,14 @@ impl HttpRequest { /// Route supports glob patterns: * for a single wildcard segment and :param /// for matching storing that segment of the request url in the Params object. #[inline] - pub fn match_info(&self) -> &Params { &self.0.params } + pub fn match_info(&self) -> &Params { + unsafe{ mem::transmute(&self.0.params) } + } /// Set request Params. #[inline] - pub fn set_match_info(&mut self, params: Params) { - self.as_mut().params = params; + pub(crate) fn match_info_mut(&mut self) -> &mut Params { + unsafe{ mem::transmute(&mut self.as_mut().params) } } /// Checks if a connection should be kept alive. @@ -431,7 +437,7 @@ impl fmt::Debug for HttpRequest { if !self.query_string().is_empty() { let _ = write!(f, " query: ?{:?}\n", self.query_string()); } - if !self.0.params.is_empty() { + if !self.match_info().is_empty() { let _ = write!(f, " params: {:?}\n", self.0.params); } let _ = write!(f, " headers:\n"); @@ -483,6 +489,7 @@ mod tests { use super::*; use http::Uri; use std::str::FromStr; + use router::Pattern; use payload::Payload; use resource::Resource; @@ -543,7 +550,7 @@ mod tests { let mut resource = Resource::default(); resource.name("index"); let mut map = HashMap::new(); - map.insert("/user/{name}.{ext}".to_owned(), resource); + map.insert(Pattern::new("index", "/user/{name}.{ext}"), Some(resource)); let router = Router::new("", map); assert!(router.has_route("/user/test.html")); assert!(!router.has_route("/test/unknown")); @@ -560,4 +567,22 @@ mod tests { let url = req.url_for("index", &["test", "html"]); assert_eq!(url.ok().unwrap().as_str(), "http://www.rust-lang.org/user/test.html"); } + + #[test] + fn test_url_for_external() { + let req = HttpRequest::new( + Method::GET, Uri::from_str("/").unwrap(), + Version::HTTP_11, HeaderMap::new(), Payload::empty()); + + let mut resource = Resource::<()>::default(); + resource.name("index"); + let mut map = HashMap::new(); + map.insert(Pattern::new("youtube", "https://youtube.com/watch/{video_id}"), None); + let router = Router::new("", map); + assert!(!router.has_route("https://youtube.com/watch/unknown")); + + let mut req = req.with_state(Rc::new(()), router); + let url = req.url_for("youtube", &["oHg5SJYRHA0"]); + assert_eq!(url.ok().unwrap().as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); + } } diff --git a/src/lib.rs b/src/lib.rs index 8eaec276f..f5633a240 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,7 @@ extern crate serde_json; extern crate flate2; extern crate brotli2; extern crate percent_encoding; +extern crate smallvec; extern crate actix; extern crate h2 as http2; @@ -58,8 +59,9 @@ mod payload; mod info; mod route; mod router; +mod param; mod resource; -mod recognizer; +// mod recognizer; mod handler; mod pipeline; mod server; @@ -117,10 +119,11 @@ pub mod dev { // dev specific pub use info::ConnectionInfo; pub use handler::Handler; - pub use router::Router; + pub use router::{Router, Pattern}; pub use pipeline::Pipeline; pub use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; - pub use recognizer::{FromParam, RouteRecognizer, Params, Pattern, PatternElement}; + // pub use recognizer::RouteRecognizer; + pub use param::{FromParam, Params}; pub use cookie::CookieBuilder; pub use http_range::HttpRange; diff --git a/src/param.rs b/src/param.rs new file mode 100644 index 000000000..3981a81bf --- /dev/null +++ b/src/param.rs @@ -0,0 +1,203 @@ +use std; +use std::ops::Index; +use std::path::PathBuf; +use std::str::FromStr; + +use failure::Fail; +use http::{StatusCode}; +use smallvec::SmallVec; + +use body::Body; +use httpresponse::HttpResponse; +use error::{ResponseError, UriSegmentError}; + + +/// A trait to abstract the idea of creating a new instance of a type from a path parameter. +pub trait FromParam: Sized { + /// The associated error which can be returned from parsing. + type Err: ResponseError; + + /// Parses a string `s` to return a value of this type. + fn from_param(s: &str) -> Result; +} + +/// Route match information +/// +/// If resource path contains variable patterns, `Params` stores this variables. +#[derive(Debug)] +pub struct Params<'a>(SmallVec<[(&'a str, &'a str); 4]>); + +impl<'a> Default for Params<'a> { + fn default() -> Params<'a> { + Params(SmallVec::new()) + } +} + +impl<'a> Params<'a> { + + pub(crate) fn add(&mut self, name: &'a str, value: &'a str) { + self.0.push((name, value)); + } + + /// Check if there are any matched patterns + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Get matched parameter by name without type conversion + pub fn get(&self, key: &str) -> Option<&'a str> { + for item in &self.0 { + if key == item.0 { + return Some(item.1) + } + } + None + } + + /// Get matched `FromParam` compatible parameter by name. + /// + /// If keyed parameter is not available empty string is used as default value. + /// + /// ```rust + /// # extern crate actix_web; + /// # use actix_web::*; + /// fn index(req: HttpRequest) -> Result { + /// let ivalue: isize = req.match_info().query("val")?; + /// Ok(format!("isuze value: {:?}", ivalue)) + /// } + /// # fn main() {} + /// ``` + pub fn query(&self, key: &str) -> Result::Err> + { + if let Some(s) = self.get(key) { + T::from_param(s) + } else { + T::from_param("") + } + } +} + +impl<'a, 'b> Index<&'b str> for Params<'a> { + type Output = str; + + fn index(&self, name: &'b str) -> &str { + self.get(name).expect("Value for parameter is not available") + } +} + +/// Creates a `PathBuf` from a path parameter. The returned `PathBuf` is +/// percent-decoded. If a segment is equal to "..", the previous segment (if +/// any) is skipped. +/// +/// For security purposes, if a segment meets any of the following conditions, +/// an `Err` is returned indicating the condition met: +/// +/// * Decoded segment starts with any of: `.` (except `..`), `*` +/// * Decoded segment ends with any of: `:`, `>`, `<` +/// * Decoded segment contains any of: `/` +/// * On Windows, decoded segment contains any of: '\' +/// * Percent-encoding results in invalid UTF8. +/// +/// As a result of these conditions, a `PathBuf` parsed from request path parameter is +/// safe to interpolate within, or use as a suffix of, a path without additional +/// checks. +impl FromParam for PathBuf { + type Err = UriSegmentError; + + fn from_param(val: &str) -> Result { + let mut buf = PathBuf::new(); + for segment in val.split('/') { + if segment == ".." { + buf.pop(); + } else if segment.starts_with('.') { + return Err(UriSegmentError::BadStart('.')) + } else if segment.starts_with('*') { + return Err(UriSegmentError::BadStart('*')) + } else if segment.ends_with(':') { + return Err(UriSegmentError::BadEnd(':')) + } else if segment.ends_with('>') { + return Err(UriSegmentError::BadEnd('>')) + } else if segment.ends_with('<') { + return Err(UriSegmentError::BadEnd('<')) + } else if segment.is_empty() { + continue + } else if cfg!(windows) && segment.contains('\\') { + return Err(UriSegmentError::BadChar('\\')) + } else { + buf.push(segment) + } + } + + Ok(buf) + } +} + +#[derive(Fail, Debug)] +#[fail(display="Error")] +pub struct BadRequest(T); + +impl BadRequest { + pub fn cause(&self) -> &T { + &self.0 + } +} + +impl ResponseError for BadRequest + where T: Send + Sync + std::fmt::Debug +std::fmt::Display + 'static, +BadRequest: Fail +{ + fn error_response(&self) -> HttpResponse { + HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) + } +} + +macro_rules! FROM_STR { + ($type:ty) => { + impl FromParam for $type { + type Err = BadRequest<<$type as FromStr>::Err>; + + fn from_param(val: &str) -> Result { + <$type as FromStr>::from_str(val).map_err(BadRequest) + } + } + } +} + +FROM_STR!(u8); +FROM_STR!(u16); +FROM_STR!(u32); +FROM_STR!(u64); +FROM_STR!(usize); +FROM_STR!(i8); +FROM_STR!(i16); +FROM_STR!(i32); +FROM_STR!(i64); +FROM_STR!(isize); +FROM_STR!(f32); +FROM_STR!(f64); +FROM_STR!(String); +FROM_STR!(std::net::IpAddr); +FROM_STR!(std::net::Ipv4Addr); +FROM_STR!(std::net::Ipv6Addr); +FROM_STR!(std::net::SocketAddr); +FROM_STR!(std::net::SocketAddrV4); +FROM_STR!(std::net::SocketAddrV6); + +#[cfg(test)] +mod tests { + use super::*; + use std::iter::FromIterator; + + #[test] + fn test_path_buf() { + assert_eq!(PathBuf::from_param("/test/.tt"), Err(UriSegmentError::BadStart('.'))); + assert_eq!(PathBuf::from_param("/test/*tt"), Err(UriSegmentError::BadStart('*'))); + assert_eq!(PathBuf::from_param("/test/tt:"), Err(UriSegmentError::BadEnd(':'))); + assert_eq!(PathBuf::from_param("/test/tt<"), Err(UriSegmentError::BadEnd('<'))); + assert_eq!(PathBuf::from_param("/test/tt>"), Err(UriSegmentError::BadEnd('>'))); + assert_eq!(PathBuf::from_param("/seg1/seg2/"), + Ok(PathBuf::from_iter(vec!["seg1", "seg2"]))); + assert_eq!(PathBuf::from_param("/seg1/../seg2/"), + Ok(PathBuf::from_iter(vec!["seg2"]))); + } +} diff --git a/src/recognizer.rs b/src/recognizer.rs index 5f2a9b00a..79be1cb1b 100644 --- a/src/recognizer.rs +++ b/src/recognizer.rs @@ -1,325 +1,50 @@ -use std; -use std::rc::Rc; -use std::path::PathBuf; -use std::ops::Index; -use std::str::FromStr; -use std::collections::HashMap; - -use failure::Fail; -use http::{StatusCode}; -use regex::{Regex, RegexSet, Captures}; - -use body::Body; -use httpresponse::HttpResponse; -use error::{ResponseError, UriSegmentError}; - -/// A trait to abstract the idea of creating a new instance of a type from a path parameter. -pub trait FromParam: Sized { - /// The associated error which can be returned from parsing. - type Err: ResponseError; - - /// Parses a string `s` to return a value of this type. - fn from_param(s: &str) -> Result; -} - -/// Route match information -/// -/// If resource path contains variable patterns, `Params` stores this variables. -#[derive(Debug)] -pub struct Params { - text: String, - matches: Vec>, - names: Rc>, -} - -impl Default for Params { - fn default() -> Params { - Params { - text: String::new(), - names: Rc::new(HashMap::new()), - matches: Vec::new(), - } - } -} - -impl Params { - pub(crate) fn new(names: Rc>, - text: &str, - captures: &Captures) -> Self - { - Params { - names, - text: text.into(), - matches: captures - .iter() - .map(|capture| capture.map(|m| (m.start(), m.end()))) - .collect(), - } - } - - /// Check if there are any matched patterns - pub fn is_empty(&self) -> bool { - self.names.is_empty() - } - - fn by_idx(&self, index: usize) -> Option<&str> { - self.matches - .get(index + 1) - .and_then(|m| m.map(|(start, end)| &self.text[start..end])) - } - - /// Get matched parameter by name without type conversion - pub fn get(&self, key: &str) -> Option<&str> { - self.names.get(key).and_then(|&i| self.by_idx(i - 1)) - } - - /// Get matched `FromParam` compatible parameter by name. - /// - /// If keyed parameter is not available empty string is used as default value. - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// fn index(req: HttpRequest) -> Result { - /// let ivalue: isize = req.match_info().query("val")?; - /// Ok(format!("isuze value: {:?}", ivalue)) - /// } - /// # fn main() {} - /// ``` - pub fn query(&self, key: &str) -> Result::Err> - { - if let Some(s) = self.get(key) { - T::from_param(s) - } else { - T::from_param("") - } - } -} - -impl<'a> Index<&'a str> for Params { - type Output = str; - - fn index(&self, name: &'a str) -> &str { - self.get(name).expect("Value for parameter is not available") - } -} - -/// Creates a `PathBuf` from a path parameter. The returned `PathBuf` is -/// percent-decoded. If a segment is equal to "..", the previous segment (if -/// any) is skipped. -/// -/// For security purposes, if a segment meets any of the following conditions, -/// an `Err` is returned indicating the condition met: -/// -/// * Decoded segment starts with any of: `.` (except `..`), `*` -/// * Decoded segment ends with any of: `:`, `>`, `<` -/// * Decoded segment contains any of: `/` -/// * On Windows, decoded segment contains any of: '\' -/// * Percent-encoding results in invalid UTF8. -/// -/// As a result of these conditions, a `PathBuf` parsed from request path parameter is -/// safe to interpolate within, or use as a suffix of, a path without additional -/// checks. -impl FromParam for PathBuf { - type Err = UriSegmentError; - - fn from_param(val: &str) -> Result { - let mut buf = PathBuf::new(); - for segment in val.split('/') { - if segment == ".." { - buf.pop(); - } else if segment.starts_with('.') { - return Err(UriSegmentError::BadStart('.')) - } else if segment.starts_with('*') { - return Err(UriSegmentError::BadStart('*')) - } else if segment.ends_with(':') { - return Err(UriSegmentError::BadEnd(':')) - } else if segment.ends_with('>') { - return Err(UriSegmentError::BadEnd('>')) - } else if segment.ends_with('<') { - return Err(UriSegmentError::BadEnd('<')) - } else if segment.is_empty() { - continue - } else if cfg!(windows) && segment.contains('\\') { - return Err(UriSegmentError::BadChar('\\')) - } else { - buf.push(segment) - } - } - - Ok(buf) - } -} - -#[derive(Fail, Debug)] -#[fail(display="Error")] -pub struct BadRequest(T); - -impl BadRequest { - pub fn cause(&self) -> &T { - &self.0 - } -} - -impl ResponseError for BadRequest - where T: Send + Sync + std::fmt::Debug +std::fmt::Display + 'static, - BadRequest: Fail -{ - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) - } -} - -macro_rules! FROM_STR { - ($type:ty) => { - impl FromParam for $type { - type Err = BadRequest<<$type as FromStr>::Err>; - - fn from_param(val: &str) -> Result { - <$type as FromStr>::from_str(val).map_err(BadRequest) - } - } - } -} - -FROM_STR!(u8); -FROM_STR!(u16); -FROM_STR!(u32); -FROM_STR!(u64); -FROM_STR!(usize); -FROM_STR!(i8); -FROM_STR!(i16); -FROM_STR!(i32); -FROM_STR!(i64); -FROM_STR!(isize); -FROM_STR!(f32); -FROM_STR!(f64); -FROM_STR!(String); -FROM_STR!(std::net::IpAddr); -FROM_STR!(std::net::Ipv4Addr); -FROM_STR!(std::net::Ipv6Addr); -FROM_STR!(std::net::SocketAddr); -FROM_STR!(std::net::SocketAddrV4); -FROM_STR!(std::net::SocketAddrV6); +use regex::RegexSet; pub struct RouteRecognizer { re: RegexSet, - prefix: String, - routes: Vec<(Pattern, T)>, - patterns: HashMap, + routes: Vec, } impl RouteRecognizer { - pub fn new(prefix: P, routes: U) -> Self - where U: IntoIterator, T)>, - K: Into, - P: Into, + pub fn new(routes: U) -> Self + where U: IntoIterator, K: Into, { let mut paths = Vec::new(); - let mut handlers = Vec::new(); - let mut patterns = HashMap::new(); + let mut routes = Vec::new(); for item in routes { - let (pat, elements) = parse(&item.0.into()); - let pattern = Pattern::new(&pat, elements); - if let Some(ref name) = item.1 { - let _ = patterns.insert(name.clone(), pattern.clone()); - } - handlers.push((pattern, item.2)); - paths.push(pat); + let pattern = parse(&item.0.into()); + paths.push(pattern); + routes.push(item.1); }; let regset = RegexSet::new(&paths); RouteRecognizer { re: regset.unwrap(), - prefix: prefix.into(), - routes: handlers, - patterns: patterns, + routes: routes, } } - pub fn get_pattern(&self, name: &str) -> Option<&Pattern> { - self.patterns.get(name) - } - - /// Length of the prefix - pub fn prefix(&self) -> &str { - &self.prefix - } - - pub fn recognize(&self, path: &str) -> Option<(Option, &T)> { - let p = &path[self.prefix.len()..]; - if p.is_empty() { + pub fn recognize(&self, path: &str) -> Option<&T> { + if path.is_empty() { if let Some(idx) = self.re.matches("/").into_iter().next() { - let (ref pattern, ref route) = self.routes[idx]; - return Some((pattern.match_info(&path[self.prefix.len()..]), route)) + return Some(&self.routes[idx]) } - } else if let Some(idx) = self.re.matches(p).into_iter().next() { - let (ref pattern, ref route) = self.routes[idx]; - return Some((pattern.match_info(&path[self.prefix.len()..]), route)) + } else if let Some(idx) = self.re.matches(path).into_iter().next() { + return Some(&self.routes[idx]) } None } } -#[derive(Debug, Clone, PartialEq)] -pub enum PatternElement { - Str(String), - Var(String), -} - -#[derive(Clone)] -pub struct Pattern { - re: Regex, - names: Rc>, - elements: Vec, -} - -impl Pattern { - fn new(pattern: &str, elements: Vec) -> Self { - let re = Regex::new(pattern).unwrap(); - let names = re.capture_names() - .enumerate() - .filter_map(|(i, name)| name.map(|name| (name.to_owned(), i))) - .collect(); - - Pattern { - re, - names: Rc::new(names), - elements: elements, - } - } - - fn match_info(&self, text: &str) -> Option { - let captures = match self.re.captures(text) { - Some(captures) => captures, - None => return None, - }; - - Some(Params::new(Rc::clone(&self.names), text, &captures)) - } - - pub fn elements(&self) -> &Vec { - &self.elements - } -} - -pub(crate) fn check_pattern(path: &str) { - if let Err(err) = Regex::new(&parse(path).0) { - panic!("Wrong path pattern: \"{}\" {}", path, err); - } -} - -fn parse(pattern: &str) -> (String, Vec) { +fn parse(pattern: &str) -> String { const DEFAULT_PATTERN: &str = "[^/]+"; let mut re = String::from("^/"); - let mut el = String::new(); let mut in_param = false; let mut in_param_pattern = false; let mut param_name = String::new(); let mut param_pattern = String::from(DEFAULT_PATTERN); - let mut elems = Vec::new(); for (index, ch) in pattern.chars().enumerate() { // All routes must have a leading slash so its optional to have one @@ -330,7 +55,6 @@ fn parse(pattern: &str) -> (String, Vec) { if in_param { // In parameter segment: `{....}` if ch == '}' { - elems.push(PatternElement::Var(param_name.clone())); re.push_str(&format!(r"(?P<{}>{})", ¶m_name, ¶m_pattern)); param_name.clear(); @@ -352,16 +76,13 @@ fn parse(pattern: &str) -> (String, Vec) { } } else if ch == '{' { in_param = true; - elems.push(PatternElement::Str(el.clone())); - el.clear(); } else { re.push(ch); - el.push(ch); } } re.push('$'); - (re, elems) + re } #[cfg(test)] @@ -370,19 +91,6 @@ mod tests { use super::*; use std::iter::FromIterator; - #[test] - fn test_path_buf() { - assert_eq!(PathBuf::from_param("/test/.tt"), Err(UriSegmentError::BadStart('.'))); - assert_eq!(PathBuf::from_param("/test/*tt"), Err(UriSegmentError::BadStart('*'))); - assert_eq!(PathBuf::from_param("/test/tt:"), Err(UriSegmentError::BadEnd(':'))); - assert_eq!(PathBuf::from_param("/test/tt<"), Err(UriSegmentError::BadEnd('<'))); - assert_eq!(PathBuf::from_param("/test/tt>"), Err(UriSegmentError::BadEnd('>'))); - assert_eq!(PathBuf::from_param("/seg1/seg2/"), - Ok(PathBuf::from_iter(vec!["seg1", "seg2"]))); - assert_eq!(PathBuf::from_param("/seg1/../seg2/"), - Ok(PathBuf::from_iter(vec!["seg2"]))); - } - #[test] fn test_recognizer() { let routes = vec![ diff --git a/src/resource.rs b/src/resource.rs index 4652a32ff..f4677ad08 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -59,8 +59,8 @@ impl Resource { self.name = name.into(); } - pub(crate) fn get_name(&self) -> Option { - if self.name.is_empty() { None } else { Some(self.name.clone()) } + pub(crate) fn get_name(&self) -> &str { + &self.name } } diff --git a/src/router.rs b/src/router.rs index ec07d3c58..b90f79c56 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,40 +1,98 @@ +use std::mem; use std::rc::Rc; +use std::hash::{Hash, Hasher}; use std::collections::HashMap; +use regex::{Regex, RegexSet}; + use error::UrlGenerationError; use resource::Resource; -use recognizer::{Params, RouteRecognizer, PatternElement}; +use httprequest::HttpRequest; /// Interface for application router. -pub struct Router(Rc>>); +pub struct Router(Rc>); + +struct Inner { + prefix: String, + regset: RegexSet, + named: HashMap, + patterns: Vec, + resources: Vec>, +} impl Router { - pub(crate) fn new(prefix: &str, map: HashMap>) -> Router + /// Create new router + pub fn new(prefix: &str, map: HashMap>>) -> Router { let prefix = prefix.trim().trim_right_matches('/').to_owned(); + let mut named = HashMap::new(); + let mut patterns = Vec::new(); let mut resources = Vec::new(); - for (path, resource) in map { - resources.push((path, resource.get_name(), resource)) + let mut paths = Vec::new(); + + for (pattern, resource) in map { + if !pattern.name().is_empty() { + let name = pattern.name().into(); + named.insert(name, (pattern.clone(), resource.is_none())); + } + + if let Some(resource) = resource { + paths.push(pattern.pattern().to_owned()); + patterns.push(pattern); + resources.push(resource); + } } - Router(Rc::new(RouteRecognizer::new(prefix, resources))) + Router(Rc::new( + Inner{ prefix: prefix, + regset: RegexSet::new(&paths).unwrap(), + named: named, + patterns: patterns, + resources: resources })) } /// Router prefix #[inline] pub(crate) fn prefix(&self) -> &str { - self.0.prefix() + &self.0.prefix } /// Query for matched resource - pub fn query(&self, path: &str) -> Option<(Option, &Resource)> { - self.0.recognize(path) + pub fn recognize(&self, req: &mut HttpRequest) -> Option<&Resource> { + let mut idx = None; + { + let path = &req.path()[self.0.prefix.len()..]; + if path.is_empty() { + if let Some(i) = self.0.regset.matches("/").into_iter().next() { + idx = Some(i); + } + } else if let Some(i) = self.0.regset.matches(path).into_iter().next() { + idx = Some(i); + } + } + + if let Some(idx) = idx { + let path: &str = unsafe{ mem::transmute(&req.path()[self.0.prefix.len()..]) }; + req.set_prefix(self.prefix().len()); + self.0.patterns[idx].update_match_info(path, req); + return Some(&self.0.resources[idx]) + } else { + None + } } /// Check if application contains matching route. pub fn has_route(&self, path: &str) -> bool { - self.0.recognize(path).is_some() + let p = &path[self.0.prefix.len()..]; + if p.is_empty() { + if self.0.regset.matches("/").into_iter().next().is_some() { + return true + } + } else if self.0.regset.matches(p).into_iter().next().is_some() { + return true + } + false } /// Build named resource path. @@ -46,23 +104,12 @@ impl Router { where U: IntoIterator, I: AsRef, { - if let Some(pattern) = self.0.get_pattern(name) { - let mut path = String::from(self.prefix()); - path.push('/'); - let mut iter = elements.into_iter(); - for el in pattern.elements() { - match *el { - PatternElement::Str(ref s) => path.push_str(s), - PatternElement::Var(_) => { - if let Some(val) = iter.next() { - path.push_str(val.as_ref()) - } else { - return Err(UrlGenerationError::NotEnoughElements) - } - } - } + if let Some(pattern) = self.0.named.get(name) { + if pattern.1 { + pattern.0.path(None, elements) + } else { + pattern.0.path(Some(&self.0.prefix), elements) } - Ok(path) } else { Err(UrlGenerationError::ResourceNotFound) } @@ -74,3 +121,285 @@ impl Clone for Router { Router(Rc::clone(&self.0)) } } + +#[derive(Debug, Clone, PartialEq)] +enum PatternElement { + Str(String), + Var(String), +} + +#[derive(Clone)] +pub struct Pattern { + re: Regex, + name: String, + pattern: String, + names: Vec, + elements: Vec, +} + +impl Pattern { + /// Parse path pattern and create new `Pattern` instance. + /// + /// Panics if path pattern is wrong. + pub fn new(name: &str, path: &str) -> Self { + let (pattern, elements) = Pattern::parse(path); + + let re = match Regex::new(&pattern) { + Ok(re) => re, + Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err) + }; + let names = re.capture_names() + .filter_map(|name| name.map(|name| name.to_owned())) + .collect(); + + Pattern { + re: re, + name: name.into(), + pattern: pattern, + names: names, + elements: elements, + } + } + + /// Returns name of the pattern + pub fn name(&self) -> &str { + &self.name + } + + /// Returns path of the pattern + pub fn pattern(&self) -> &str { + &self.pattern + } + + /// Extract pattern parameters from the text + pub(crate) fn update_match_info(&self, text: &str, req: &mut HttpRequest) { + if !self.names.is_empty() { + if let Some(captures) = self.re.captures(text) { + let mut idx = 0; + for capture in captures.iter() { + if let Some(ref m) = capture { + if idx != 0 { + req.match_info_mut().add(&self.names[idx-1], m.as_str()); + } + idx += 1; + } + } + }; + } + } + + /// Build pattern path. + pub fn path(&self, prefix: Option<&str>, elements: U) + -> Result + where U: IntoIterator, + I: AsRef, + { + let mut iter = elements.into_iter(); + let mut path = if let Some(prefix) = prefix { + let mut path = String::from(prefix); + path.push('/'); + path + } else { + String::new() + }; + for el in &self.elements { + match *el { + PatternElement::Str(ref s) => path.push_str(s), + PatternElement::Var(_) => { + if let Some(val) = iter.next() { + path.push_str(val.as_ref()) + } else { + return Err(UrlGenerationError::NotEnoughElements) + } + } + } + } + Ok(path) + } + + fn parse(pattern: &str) -> (String, Vec) { + const DEFAULT_PATTERN: &str = "[^/]+"; + + let mut re = String::from("^/"); + let mut el = String::new(); + let mut in_param = false; + let mut in_param_pattern = false; + let mut param_name = String::new(); + let mut param_pattern = String::from(DEFAULT_PATTERN); + let mut elems = Vec::new(); + + for (index, ch) in pattern.chars().enumerate() { + // All routes must have a leading slash so its optional to have one + if index == 0 && ch == '/' { + continue; + } + + if in_param { + // In parameter segment: `{....}` + if ch == '}' { + elems.push(PatternElement::Var(param_name.clone())); + re.push_str(&format!(r"(?P<{}>{})", ¶m_name, ¶m_pattern)); + + param_name.clear(); + param_pattern = String::from(DEFAULT_PATTERN); + + in_param_pattern = false; + in_param = false; + } else if ch == ':' { + // The parameter name has been determined; custom pattern land + in_param_pattern = true; + param_pattern.clear(); + } else if in_param_pattern { + // Ignore leading whitespace for pattern + if !(ch == ' ' && param_pattern.is_empty()) { + param_pattern.push(ch); + } + } else { + param_name.push(ch); + } + } else if ch == '{' { + in_param = true; + elems.push(PatternElement::Str(el.clone())); + el.clear(); + } else { + re.push(ch); + el.push(ch); + } + } + + re.push('$'); + (re, elems) + } +} + +impl PartialEq for Pattern { + fn eq(&self, other: &Pattern) -> bool { + self.pattern == other.pattern + } +} + +impl Eq for Pattern {} + +impl Hash for Pattern { + fn hash(&self, state: &mut H) { + self.pattern.hash(state); + } +} + +#[cfg(test)] +mod tests { + use regex::Regex; + use super::*; + use http::{Uri, Version, Method}; + use http::header::HeaderMap; + use std::str::FromStr; + use payload::Payload; + + #[test] + fn test_recognizer() { + let mut routes = HashMap::new(); + routes.insert(Pattern::new("", "/name"), Some(Resource::default())); + routes.insert(Pattern::new("", "/name/{val}"), Some(Resource::default())); + routes.insert(Pattern::new("", "/name/{val}/index.html"), Some(Resource::default())); + routes.insert(Pattern::new("", "/v{val}/{val2}/index.html"), Some(Resource::default())); + routes.insert(Pattern::new("", "/v/{tail:.*}"), Some(Resource::default())); + routes.insert(Pattern::new("", "{test}/index.html"), Some(Resource::default())); + let rec = Router::new("", routes); + + let mut req = HttpRequest::new( + Method::GET, Uri::from_str("/name").unwrap(), + Version::HTTP_11, HeaderMap::new(), Payload::empty()); + assert!(rec.recognize(&mut req).is_some()); + assert!(req.match_info().is_empty()); + + let mut req = HttpRequest::new( + Method::GET, Uri::from_str("/name/value").unwrap(), + Version::HTTP_11, HeaderMap::new(), Payload::empty()); + assert!(rec.recognize(&mut req).is_some()); + assert_eq!(req.match_info().get("val").unwrap(), "value"); + assert_eq!(&req.match_info()["val"], "value"); + + let mut req = HttpRequest::new( + Method::GET, Uri::from_str("/name/value2/index.html").unwrap(), + Version::HTTP_11, HeaderMap::new(), Payload::empty()); + assert!(rec.recognize(&mut req).is_some()); + assert_eq!(req.match_info().get("val").unwrap(), "value2"); + + let mut req = HttpRequest::new( + Method::GET, Uri::from_str("/vtest/ttt/index.html").unwrap(), + Version::HTTP_11, HeaderMap::new(), Payload::empty()); + assert!(rec.recognize(&mut req).is_some()); + assert_eq!(req.match_info().get("val").unwrap(), "test"); + assert_eq!(req.match_info().get("val2").unwrap(), "ttt"); + + let mut req = HttpRequest::new( + Method::GET, Uri::from_str("/v/blah-blah/index.html").unwrap(), + Version::HTTP_11, HeaderMap::new(), Payload::empty()); + assert!(rec.recognize(&mut req).is_some()); + assert_eq!(req.match_info().get("tail").unwrap(), "blah-blah/index.html"); + + let mut req = HttpRequest::new( + Method::GET, Uri::from_str("/bbb/index.html").unwrap(), + Version::HTTP_11, HeaderMap::new(), Payload::empty()); + assert!(rec.recognize(&mut req).is_some()); + assert_eq!(req.match_info().get("test").unwrap(), "bbb"); + } + + fn assert_parse(pattern: &str, expected_re: &str) -> Regex { + let (re_str, _) = Pattern::parse(pattern); + assert_eq!(&*re_str, expected_re); + Regex::new(&re_str).unwrap() + } + + #[test] + fn test_parse_static() { + let re = assert_parse("/", r"^/$"); + assert!(re.is_match("/")); + assert!(!re.is_match("/a")); + + let re = assert_parse("/name", r"^/name$"); + assert!(re.is_match("/name")); + assert!(!re.is_match("/name1")); + assert!(!re.is_match("/name/")); + assert!(!re.is_match("/name~")); + + let re = assert_parse("/name/", r"^/name/$"); + assert!(re.is_match("/name/")); + assert!(!re.is_match("/name")); + assert!(!re.is_match("/name/gs")); + + let re = assert_parse("/user/profile", r"^/user/profile$"); + assert!(re.is_match("/user/profile")); + assert!(!re.is_match("/user/profile/profile")); + } + + #[test] + fn test_parse_param() { + let re = assert_parse("/user/{id}", r"^/user/(?P[^/]+)$"); + assert!(re.is_match("/user/profile")); + assert!(re.is_match("/user/2345")); + assert!(!re.is_match("/user/2345/")); + assert!(!re.is_match("/user/2345/sdg")); + + let captures = re.captures("/user/profile").unwrap(); + assert_eq!(captures.get(1).unwrap().as_str(), "profile"); + assert_eq!(captures.name("id").unwrap().as_str(), "profile"); + + let captures = re.captures("/user/1245125").unwrap(); + assert_eq!(captures.get(1).unwrap().as_str(), "1245125"); + assert_eq!(captures.name("id").unwrap().as_str(), "1245125"); + + let re = assert_parse( + "/v{version}/resource/{id}", + r"^/v(?P[^/]+)/resource/(?P[^/]+)$", + ); + assert!(re.is_match("/v1/resource/320120")); + assert!(!re.is_match("/v/resource/1")); + assert!(!re.is_match("/resource")); + + let captures = re.captures("/v151/resource/adahg32").unwrap(); + assert_eq!(captures.get(1).unwrap().as_str(), "151"); + assert_eq!(captures.name("version").unwrap().as_str(), "151"); + assert_eq!(captures.name("id").unwrap().as_str(), "adahg32"); + } +} diff --git a/tests/test_httprequest.rs b/tests/test_httprequest.rs index 263ba094d..f3519dbe6 100644 --- a/tests/test_httprequest.rs +++ b/tests/test_httprequest.rs @@ -4,6 +4,7 @@ extern crate time; use std::str; use std::str::FromStr; +use std::collections::HashMap; use actix_web::*; use actix_web::dev::*; use http::{header, Method, Version, HeaderMap, Uri}; @@ -92,11 +93,13 @@ fn test_request_match_info() { let mut req = HttpRequest::new(Method::GET, Uri::from_str("/value/?id=test").unwrap(), Version::HTTP_11, HeaderMap::new(), Payload::empty()); - let rec = RouteRecognizer::new("", vec![("/{key}/".to_owned(), None, 1)]); - let (params, _) = rec.recognize(req.path()).unwrap(); - let params = params.unwrap(); + let mut resource = Resource::default(); + resource.name("index"); + let mut map = HashMap::new(); + map.insert(Pattern::new("index", "/{key}/"), Some(resource)); + let router = Router::new("", map); + assert!(router.recognize(&mut req).is_some()); - req.set_match_info(params); assert_eq!(req.match_info().get("key"), Some("value")); } From dff7618f35c058ad67405a33f7de6349b92c6942 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Dec 2017 16:40:29 -0800 Subject: [PATCH 050/279] rearrange exports --- guide/src/qs_7.md | 2 ++ src/httprequest.rs | 4 ++-- src/httpresponse.rs | 3 ++- src/lib.rs | 19 ++++++++++++------- tests/test_httpresponse.rs | 2 +- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index e1f31dd98..e65905f86 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -12,6 +12,7 @@ if this methods get call for the same builder instance, builder will panic. ```rust # extern crate actix_web; use actix_web::*; +use actix_web::headers::ContentEncoding; fn index(req: HttpRequest) -> HttpResponse { HttpResponse::Ok() @@ -45,6 +46,7 @@ to enable `brotli` response's body compression use `ContentEncoding::Br`: ```rust # extern crate actix_web; use actix_web::*; +use actix_web::headers::ContentEncoding; fn index(req: HttpRequest) -> HttpResponse { HttpResponse::Ok() diff --git a/src/httprequest.rs b/src/httprequest.rs index 3747aabbb..284adf9cb 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -6,10 +6,10 @@ use std::collections::HashMap; use bytes::BytesMut; use futures::{Async, Future, Stream, Poll}; use url::{Url, form_urlencoded}; -pub use http_range::HttpRange; +use cookie::Cookie; +use http_range::HttpRange; use http::{header, Uri, Method, Version, HeaderMap, Extensions}; -use Cookie; use info::ConnectionInfo; use param::Params; use router::Router; diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 1bda6f186..0f01d96fa 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -8,7 +8,8 @@ use http::{StatusCode, Version, HeaderMap, HttpTryFrom, Error as HttpError}; use http::header::{self, HeaderName, HeaderValue}; use serde_json; use serde::Serialize; -use Cookie; +use cookie::Cookie; + use body::Body; use error::Error; use handler::FromRequest; diff --git a/src/lib.rs b/src/lib.rs index f5633a240..b772ec9fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,10 +81,9 @@ pub mod multipart; pub mod middlewares; pub mod pred; pub use error::{Error, Result}; -pub use encoding::ContentEncoding; pub use body::{Body, Binary}; pub use application::Application; -pub use httprequest::{HttpRequest, UrlEncoded}; +pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; pub use payload::{Payload, PayloadItem}; pub use handler::{Reply, Json, FromRequest}; @@ -95,7 +94,6 @@ pub use context::HttpContext; // re-exports pub use http::{Method, StatusCode, Version}; -pub use cookie::Cookie; #[doc(hidden)] #[cfg(feature="tls")] @@ -105,6 +103,16 @@ pub use native_tls::Pkcs12; #[cfg(feature="openssl")] pub use openssl::pkcs12::Pkcs12; +pub mod headers { +//! Headers implementation + + pub use encoding::ContentEncoding; + + pub use cookie::Cookie; + pub use cookie::CookieBuilder; + pub use http_range::HttpRange; +} + pub mod dev { //! The `actix-web` prelude for library developers //! @@ -116,16 +124,13 @@ pub mod dev { //! use actix_web::dev::*; //! ``` - // dev specific pub use info::ConnectionInfo; pub use handler::Handler; pub use router::{Router, Pattern}; pub use pipeline::Pipeline; pub use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; - // pub use recognizer::RouteRecognizer; pub use param::{FromParam, Params}; - pub use cookie::CookieBuilder; - pub use http_range::HttpRange; + pub use httprequest::UrlEncoded; pub use httpresponse::HttpResponseBuilder; } diff --git a/tests/test_httpresponse.rs b/tests/test_httpresponse.rs index 8a239ae70..79e629ed9 100644 --- a/tests/test_httpresponse.rs +++ b/tests/test_httpresponse.rs @@ -20,7 +20,7 @@ fn test_response_cookies() { let resp = httpcodes::HTTPOk .build() - .cookie(Cookie::build("name", "value") + .cookie(headers::Cookie::build("name", "value") .domain("www.rust-lang.org") .path("/test") .http_only(true) From 0abb3863dc0a1b9911c1d9ce6f4e278277ef54c8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Dec 2017 17:38:18 -0800 Subject: [PATCH 051/279] simplify api --- src/encoding.rs | 26 ++++++++++++++------------ src/h1writer.rs | 22 +++++++++++----------- src/h2writer.rs | 16 ++++++++-------- src/httprequest.rs | 26 +++++++++----------------- src/httpresponse.rs | 6 +++--- src/middlewares/logger.rs | 10 ++++------ 6 files changed, 49 insertions(+), 57 deletions(-) diff --git a/src/encoding.rs b/src/encoding.rs index 4774b4c7a..2768dfd18 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -346,7 +346,7 @@ impl PayloadEncoder { }; // Enable content encoding only if response does not contain Content-Encoding header - let mut encoding = if has_body && !resp.headers.contains_key(CONTENT_ENCODING) { + let mut encoding = if has_body && !resp.headers().contains_key(CONTENT_ENCODING) { let encoding = match *resp.content_encoding() { ContentEncoding::Auto => { // negotiate content-encoding @@ -362,7 +362,8 @@ impl PayloadEncoder { } encoding => encoding, }; - resp.headers.insert(CONTENT_ENCODING, HeaderValue::from_static(encoding.as_str())); + resp.headers_mut().insert( + CONTENT_ENCODING, HeaderValue::from_static(encoding.as_str())); encoding } else { ContentEncoding::Identity @@ -377,8 +378,8 @@ impl PayloadEncoder { if resp.chunked() { error!("Chunked transfer is enabled but body is set to Empty"); } - resp.headers.insert(CONTENT_LENGTH, HeaderValue::from_static("0")); - resp.headers.remove(TRANSFER_ENCODING); + resp.headers_mut().insert(CONTENT_LENGTH, HeaderValue::from_static("0")); + resp.headers_mut().remove(TRANSFER_ENCODING); TransferEncoding::length(0) }, Body::Binary(ref mut bytes) => { @@ -399,31 +400,31 @@ impl PayloadEncoder { let _ = enc.write_eof(); let b = enc.get_mut().take(); - resp.headers.insert( + resp.headers_mut().insert( CONTENT_LENGTH, HeaderValue::from_str(format!("{}", b.len()).as_str()).unwrap()); *bytes = Binary::from(b); encoding = ContentEncoding::Identity; TransferEncoding::eof() } else { - resp.headers.insert( + resp.headers_mut().insert( CONTENT_LENGTH, HeaderValue::from_str(format!("{}", bytes.len()).as_str()).unwrap()); - resp.headers.remove(TRANSFER_ENCODING); + resp.headers_mut().remove(TRANSFER_ENCODING); TransferEncoding::length(bytes.len() as u64) } } Body::Streaming(_) | Body::StreamingContext => { if resp.chunked() { - resp.headers.remove(CONTENT_LENGTH); + resp.headers_mut().remove(CONTENT_LENGTH); if version != Version::HTTP_11 { error!("Chunked transfer encoding is forbidden for {:?}", version); } if version == Version::HTTP_2 { - resp.headers.remove(TRANSFER_ENCODING); + resp.headers_mut().remove(TRANSFER_ENCODING); TransferEncoding::eof() } else { - resp.headers.insert( + resp.headers_mut().insert( TRANSFER_ENCODING, HeaderValue::from_static("chunked")); TransferEncoding::chunked() } @@ -447,11 +448,12 @@ impl PayloadEncoder { if version == Version::HTTP_2 { error!("Connection upgrade is forbidden for HTTP/2"); } else { - resp.headers.insert(CONNECTION, HeaderValue::from_static("upgrade")); + resp.headers_mut().insert( + CONNECTION, HeaderValue::from_static("upgrade")); } if encoding != ContentEncoding::Identity { encoding = ContentEncoding::Identity; - resp.headers.remove(CONTENT_ENCODING); + resp.headers_mut().remove(CONTENT_ENCODING); } TransferEncoding::eof() } diff --git a/src/h1writer.rs b/src/h1writer.rs index 63c98e876..447758cf6 100644 --- a/src/h1writer.rs +++ b/src/h1writer.rs @@ -116,7 +116,7 @@ impl Writer for H1Writer { fn start(&mut self, req: &mut HttpRequest, msg: &mut HttpResponse) -> Result { - trace!("Prepare response with status: {:?}", msg.status); + trace!("Prepare response with status: {:?}", msg.status()); // prepare task self.started = true; @@ -126,32 +126,32 @@ impl Writer for H1Writer { // Connection upgrade let version = msg.version().unwrap_or_else(|| req.version()); if msg.upgrade() { - msg.headers.insert(CONNECTION, HeaderValue::from_static("upgrade")); + msg.headers_mut().insert(CONNECTION, HeaderValue::from_static("upgrade")); } // keep-alive else if self.keepalive { if version < Version::HTTP_11 { - msg.headers.insert(CONNECTION, HeaderValue::from_static("keep-alive")); + msg.headers_mut().insert(CONNECTION, HeaderValue::from_static("keep-alive")); } } else if version >= Version::HTTP_11 { - msg.headers.insert(CONNECTION, HeaderValue::from_static("close")); + msg.headers_mut().insert(CONNECTION, HeaderValue::from_static("close")); } // render message { let buffer = self.encoder.get_mut(); if let Body::Binary(ref bytes) = *msg.body() { - buffer.reserve(100 + msg.headers.len() * AVERAGE_HEADER_SIZE + bytes.len()); + buffer.reserve(100 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len()); } else { - buffer.reserve(100 + msg.headers.len() * AVERAGE_HEADER_SIZE); + buffer.reserve(100 + msg.headers().len() * AVERAGE_HEADER_SIZE); } - if version == Version::HTTP_11 && msg.status == StatusCode::OK { + if version == Version::HTTP_11 && msg.status() == StatusCode::OK { buffer.extend(b"HTTP/1.1 200 OK\r\n"); } else { - let _ = write!(buffer, "{:?} {}\r\n", version, msg.status); + let _ = write!(buffer, "{:?} {}\r\n", version, msg.status()); } - for (key, value) in &msg.headers { + for (key, value) in msg.headers() { let t: &[u8] = key.as_ref(); buffer.extend(t); buffer.extend(b": "); @@ -161,7 +161,7 @@ impl Writer for H1Writer { // using http::h1::date is quite a lot faster than generating // a unique Date header each time like req/s goes up about 10% - if !msg.headers.contains_key(DATE) { + if !msg.headers().contains_key(DATE) { buffer.reserve(date::DATE_VALUE_LENGTH + 8); buffer.extend(b"Date: "); let mut bytes = [0u8; 29]; @@ -171,7 +171,7 @@ impl Writer for H1Writer { } // default content-type - if !msg.headers.contains_key(CONTENT_TYPE) { + if !msg.headers().contains_key(CONTENT_TYPE) { buffer.extend(b"ContentType: application/octet-stream\r\n".as_ref()); } diff --git a/src/h2writer.rs b/src/h2writer.rs index e3e04bd77..82a1b96e0 100644 --- a/src/h2writer.rs +++ b/src/h2writer.rs @@ -108,7 +108,7 @@ impl Writer for H2Writer { fn start(&mut self, req: &mut HttpRequest, msg: &mut HttpResponse) -> Result { - trace!("Prepare response with status: {:?}", msg.status); + trace!("Prepare response with status: {:?}", msg.status()); // prepare response self.started = true; @@ -116,25 +116,25 @@ impl Writer for H2Writer { self.eof = if let Body::Empty = *msg.body() { true } else { false }; // http2 specific - msg.headers.remove(CONNECTION); - msg.headers.remove(TRANSFER_ENCODING); + msg.headers_mut().remove(CONNECTION); + msg.headers_mut().remove(TRANSFER_ENCODING); // using http::h1::date is quite a lot faster than generating // a unique Date header each time like req/s goes up about 10% - if !msg.headers.contains_key(DATE) { + if !msg.headers().contains_key(DATE) { let mut bytes = [0u8; 29]; date::extend(&mut bytes[..]); - msg.headers.insert(DATE, HeaderValue::try_from(&bytes[..]).unwrap()); + msg.headers_mut().insert(DATE, HeaderValue::try_from(&bytes[..]).unwrap()); } // default content-type - if !msg.headers.contains_key(CONTENT_TYPE) { - msg.headers.insert( + if !msg.headers().contains_key(CONTENT_TYPE) { + msg.headers_mut().insert( CONTENT_TYPE, HeaderValue::from_static("application/octet-stream")); } let mut resp = Response::new(()); - *resp.status_mut() = msg.status; + *resp.status_mut() = msg.status(); *resp.version_mut() = Version::HTTP_2; for (key, value) in msg.headers().iter() { resp.headers_mut().insert(key, value.clone()); diff --git a/src/httprequest.rs b/src/httprequest.rs index 284adf9cb..36dcf22c4 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -97,11 +97,13 @@ impl HttpRequest { HttpRequest(Rc::clone(&self.0), Rc::new(()), None) } - /// get mutable reference for inner message + // get mutable reference for inner message + // mutable reference should not be returned as result for request's method #[inline] - fn as_mut(&mut self) -> &mut HttpMessage { + #[allow(mutable_transmutes)] + #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))] + fn as_mut(&self) -> &mut HttpMessage { let r: &HttpMessage = self.0.as_ref(); - #[allow(mutable_transmutes)] unsafe{mem::transmute(r)} } @@ -158,18 +160,8 @@ impl HttpRequest { self.0.uri.path() } - /// Get previously loaded *ConnectionInfo*. - #[inline] - pub fn connection_info(&self) -> Option<&ConnectionInfo> { - if self.0.info.is_none() { - None - } else { - self.0.info.as_ref() - } - } - - /// Load *ConnectionInfo* for currect request. - pub fn load_connection_info(&mut self) -> &ConnectionInfo { + /// Get *ConnectionInfo* for currect request. + pub fn connection_info(&self) -> &ConnectionInfo { if self.0.info.is_none() { let info: ConnectionInfo<'static> = unsafe{ mem::transmute(ConnectionInfo::new(self))}; @@ -178,7 +170,7 @@ impl HttpRequest { self.0.info.as_ref().unwrap() } - pub fn url_for(&mut self, name: &str, elements: U) -> Result + pub fn url_for(&self, name: &str, elements: U) -> Result where U: IntoIterator, I: AsRef, { @@ -187,7 +179,7 @@ impl HttpRequest { } else { let path = self.router().unwrap().resource_path(name, elements)?; if path.starts_with('/') { - let conn = self.load_connection_info(); + let conn = self.connection_info(); Ok(Url::parse(&format!("{}://{}{}", conn.scheme(), conn.host(), path))?) } else { Ok(Url::parse(&path)?) diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 0f01d96fa..e877a761a 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -29,9 +29,9 @@ pub enum ConnectionType { /// An HTTP Response pub struct HttpResponse { - pub version: Option, - pub headers: HeaderMap, - pub status: StatusCode, + version: Option, + headers: HeaderMap, + status: StatusCode, reason: Option<&'static str>, body: Body, chunked: bool, diff --git a/src/middlewares/logger.rs b/src/middlewares/logger.rs index a18e8ad15..5e443fce6 100644 --- a/src/middlewares/logger.rs +++ b/src/middlewares/logger.rs @@ -102,7 +102,6 @@ impl Logger { impl Middleware for Logger { fn start(&self, req: &mut HttpRequest) -> Started { - req.load_connection_info(); req.extensions().insert(StartTime(time::now())); Started::Done } @@ -237,12 +236,11 @@ impl FormatText { fmt.write_fmt(format_args!("{:.6}", response_time_ms)) }, FormatText::RemoteAddr => { - if let Some(addr) = req.connection_info() { - if let Some(remote) = addr.remote() { - return remote.fmt(fmt); - } + if let Some(remote) = req.connection_info().remote() { + return remote.fmt(fmt); + } else { + "-".fmt(fmt) } - "-".fmt(fmt) } FormatText::RequestTime => { entry_time.strftime("[%d/%b/%Y:%H:%M:%S %z]") From d595dd850e73f4ce075390c82f40745982be1180 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Dec 2017 18:00:20 -0800 Subject: [PATCH 052/279] load cookies automatically --- src/httprequest.rs | 70 ++++++++++++++++---------------------- src/middlewares/session.rs | 2 +- src/router.rs | 3 +- tests/test_httprequest.rs | 11 +++--- tests/test_httpresponse.rs | 4 +-- 5 files changed, 38 insertions(+), 52 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index 36dcf22c4..44e13f875 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -23,12 +23,10 @@ struct HttpMessage { version: Version, method: Method, uri: Uri, - prefix: usize, headers: HeaderMap, extensions: Extensions, params: Params<'static>, - cookies: Vec>, - cookies_loaded: bool, + cookies: Option>>, addr: Option, payload: Payload, info: Option>, @@ -41,12 +39,10 @@ impl Default for HttpMessage { HttpMessage { method: Method::GET, uri: Uri::default(), - prefix: 0, version: Version::HTTP_11, headers: HeaderMap::new(), params: Params::default(), - cookies: Vec::new(), - cookies_loaded: false, + cookies: None, addr: None, payload: Payload::empty(), extensions: Extensions::new(), @@ -68,12 +64,10 @@ impl HttpRequest<()> { Rc::new(HttpMessage { method: method, uri: uri, - prefix: 0, version: version, headers: headers, params: Params::default(), - cookies: Vec::new(), - cookies_loaded: false, + cookies: None, addr: None, payload: payload, extensions: Extensions::new(), @@ -119,14 +113,13 @@ impl HttpRequest { &mut self.as_mut().extensions } - #[inline] - pub(crate) fn set_prefix(&mut self, idx: usize) { - self.as_mut().prefix = idx; - } - #[doc(hidden)] pub fn prefix_len(&self) -> usize { - self.0.prefix + if let Some(router) = self.router() { + router.prefix().len() + } else { + 0 + } } /// Read the Request Uri. @@ -225,37 +218,34 @@ impl HttpRequest { } } - /// Return request cookies. + /// Load request cookies. #[inline] - pub fn cookies(&self) -> &Vec> { - &self.0.cookies - } - - /// Return request cookie. - pub fn cookie(&self, name: &str) -> Option<&Cookie> { - for cookie in &self.0.cookies { - if cookie.name() == name { - return Some(cookie) - } - } - None - } - - /// Load cookies - pub fn load_cookies(&mut self) -> Result<&Vec>, CookieParseError> - { - if !self.0.cookies_loaded { + pub fn cookies(&self) -> Result<&Vec>, CookieParseError> { + if self.0.cookies.is_none() { let msg = self.as_mut(); - msg.cookies_loaded = true; + let mut cookies = Vec::new(); if let Some(val) = msg.headers.get(header::COOKIE) { let s = str::from_utf8(val.as_bytes()) .map_err(CookieParseError::from)?; for cookie in s.split("; ") { - msg.cookies.push(Cookie::parse_encoded(cookie)?.into_owned()); + cookies.push(Cookie::parse_encoded(cookie)?.into_owned()); + } + } + msg.cookies = Some(cookies) + } + Ok(self.0.cookies.as_ref().unwrap()) + } + + /// Return request cookie. + pub fn cookie(&self, name: &str) -> Option<&Cookie> { + if let Ok(cookies) = self.cookies() { + for cookie in cookies { + if cookie.name() == name { + return Some(cookie) } } } - Ok(&self.0.cookies) + None } /// Get a reference to the Params object. @@ -535,7 +525,7 @@ mod tests { let mut headers = HeaderMap::new(); headers.insert(header::HOST, header::HeaderValue::from_static("www.rust-lang.org")); - let mut req = HttpRequest::new( + let req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, Payload::empty()); @@ -550,7 +540,7 @@ mod tests { assert_eq!(req.url_for("unknown", &["test"]), Err(UrlGenerationError::RouterNotAvailable)); - let mut req = req.with_state(Rc::new(()), router); + let req = req.with_state(Rc::new(()), router); assert_eq!(req.url_for("unknown", &["test"]), Err(UrlGenerationError::ResourceNotFound)); @@ -573,7 +563,7 @@ mod tests { let router = Router::new("", map); assert!(!router.has_route("https://youtube.com/watch/unknown")); - let mut req = req.with_state(Rc::new(()), router); + let req = req.with_state(Rc::new(()), router); let url = req.url_for("youtube", &["oHg5SJYRHA0"]); assert_eq!(url.ok().unwrap().as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); } diff --git a/src/middlewares/session.rs b/src/middlewares/session.rs index 1ed02ee0c..f962171a7 100644 --- a/src/middlewares/session.rs +++ b/src/middlewares/session.rs @@ -259,7 +259,7 @@ impl CookieSessionInner { } fn load(&self, req: &mut HttpRequest) -> HashMap { - if let Ok(cookies) = req.load_cookies() { + if let Ok(cookies) = req.cookies() { for cookie in cookies { if cookie.name() == self.name { let mut jar = CookieJar::new(); diff --git a/src/router.rs b/src/router.rs index b90f79c56..3d03e11a0 100644 --- a/src/router.rs +++ b/src/router.rs @@ -54,7 +54,7 @@ impl Router { /// Router prefix #[inline] - pub(crate) fn prefix(&self) -> &str { + pub fn prefix(&self) -> &str { &self.0.prefix } @@ -74,7 +74,6 @@ impl Router { if let Some(idx) = idx { let path: &str = unsafe{ mem::transmute(&req.path()[self.0.prefix.len()..]) }; - req.set_prefix(self.prefix().len()); self.0.patterns[idx].update_match_info(path, req); return Some(&self.0.resources[idx]) } else { diff --git a/tests/test_httprequest.rs b/tests/test_httprequest.rs index f3519dbe6..b6fecce57 100644 --- a/tests/test_httprequest.rs +++ b/tests/test_httprequest.rs @@ -20,12 +20,10 @@ fn test_debug() { #[test] fn test_no_request_cookies() { - let mut req = HttpRequest::new( + let req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, HeaderMap::new(), Payload::empty()); - assert!(req.cookies().is_empty()); - let _ = req.load_cookies(); - assert!(req.cookies().is_empty()); + assert!(req.cookies().unwrap().is_empty()); } #[test] @@ -34,12 +32,11 @@ fn test_request_cookies() { headers.insert(header::COOKIE, header::HeaderValue::from_static("cookie1=value1; cookie2=value2")); - let mut req = HttpRequest::new( + let req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, Payload::empty()); - assert!(req.cookies().is_empty()); { - let cookies = req.load_cookies().unwrap(); + let cookies = req.cookies().unwrap(); assert_eq!(cookies.len(), 2); assert_eq!(cookies[0].name(), "cookie1"); assert_eq!(cookies[0].value(), "value1"); diff --git a/tests/test_httpresponse.rs b/tests/test_httpresponse.rs index 79e629ed9..53b1149b9 100644 --- a/tests/test_httpresponse.rs +++ b/tests/test_httpresponse.rs @@ -14,9 +14,9 @@ fn test_response_cookies() { headers.insert(header::COOKIE, header::HeaderValue::from_static("cookie1=value1; cookie2=value2")); - let mut req = HttpRequest::new( + let req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, Payload::empty()); - let cookies = req.load_cookies().unwrap(); + let cookies = req.cookies().unwrap(); let resp = httpcodes::HTTPOk .build() From 3f06439d3e8f7931d3b5447b9a69170bcc47c000 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Dec 2017 18:08:16 -0800 Subject: [PATCH 053/279] update examples --- examples/tls/src/main.rs | 4 ++-- examples/websocket-chat/src/main.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/tls/src/main.rs b/examples/tls/src/main.rs index 886049d19..d40719e56 100644 --- a/examples/tls/src/main.rs +++ b/examples/tls/src/main.rs @@ -30,11 +30,11 @@ fn main() { let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap().parse("12345").unwrap(); HttpServer::new( - Application::default("/") + Application::new("/") // enable logger .middleware(middlewares::Logger::default()) // register simple handler, handle all methods - .route("/index.html", |r| r.f(index)) + .resource("/index.html", |r| r.f(index)) // with path parameters .resource("/", |r| r.method(Method::GET).f(|req| { httpcodes::HTTPFound diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs index 797d4690c..7547b505d 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -197,7 +197,7 @@ fn main() { // Create Http server with websocket support HttpServer::new( - Application::build("/", state) + Application::with_state("/", state) // redirect to websocket.html .resource("/", |r| r.method(Method::GET).f(|req| { httpcodes::HTTPFound @@ -208,7 +208,7 @@ fn main() { // websocket .resource("/ws/", |r| r.route().f(chat_route)) // static resources - .route("/static", |r| r.h(fs::StaticFiles::new("static/", true)))) + .resource("/static", |r| r.h(fs::StaticFiles::new("static/", true)))) .serve::<_, ()>("127.0.0.1:8080").unwrap(); let _ = sys.run(); From b71ddf7b4cceb24041d29ee60d399580fb36b553 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Dec 2017 21:52:46 -0800 Subject: [PATCH 054/279] pass local addr to channel; use bitflags --- Cargo.toml | 5 +-- src/channel.rs | 18 +++++--- src/h1.rs | 97 ++++++++++++++++++++++++++------------------ src/h1writer.rs | 41 +++++++++++-------- src/h2.rs | 73 ++++++++++++++++++++------------- src/h2writer.rs | 37 +++++++++-------- src/lib.rs | 3 +- src/server.rs | 40 +++++++++++------- tests/test_server.rs | 2 +- 9 files changed, 190 insertions(+), 126 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2ff98b27f..7af676c5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,6 @@ httparse = "0.1" http-range = "0.1" mime = "0.3" mime_guess = "1.8" -cookie = { version="0.10", features=["percent-encode", "secure"] } regex = "0.2" sha1 = "0.2" url = "1.5" @@ -53,8 +52,8 @@ flate2 = "0.2" brotli2 = "^0.3.2" percent-encoding = "1.0" smallvec = "0.6" - -# redis-async = { git="https://github.com/benashford/redis-async-rs" } +bitflags = "1.0" +cookie = { version="0.10", features=["percent-encode", "secure"] } # tokio bytes = "0.4" diff --git a/src/channel.rs b/src/channel.rs index ac9875a47..3a253862f 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -51,16 +51,21 @@ pub struct HttpChannel impl HttpChannel where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static { - pub fn new(stream: T, addr: Option, router: Rc>, http2: bool) - -> HttpChannel { + pub fn new(stream: T, + local: SocketAddr, + secure: bool, + peer: Option, + router: Rc>, + http2: bool) -> HttpChannel + { if http2 { HttpChannel { proto: Some(HttpProtocol::H2( - h2::Http2::new(stream, addr, router, Bytes::new()))) } + h2::Http2::new(stream, local, secure, peer, router, Bytes::new()))) } } else { HttpChannel { proto: Some(HttpProtocol::H1( - h1::Http1::new(stream, addr, router))) } + h1::Http1::new(stream, local, secure, peer, router))) } } } } @@ -105,8 +110,9 @@ impl Future for HttpChannel let proto = self.proto.take().unwrap(); match proto { HttpProtocol::H1(h1) => { - let (stream, addr, router, buf) = h1.into_inner(); - self.proto = Some(HttpProtocol::H2(h2::Http2::new(stream, addr, router, buf))); + let (stream, local, secure, addr, router, buf) = h1.into_inner(); + self.proto = Some(HttpProtocol::H2( + h2::Http2::new(stream, local, secure, addr, router, buf))); self.poll() } _ => unreachable!() diff --git a/src/h1.rs b/src/h1.rs index b7fed38c7..54217610b 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -29,6 +29,24 @@ const MAX_HEADERS: usize = 100; const MAX_PIPELINED_MESSAGES: usize = 16; const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; +bitflags! { + struct Flags: u8 { + const SECURE = 0b0000_0001; + const ERROR = 0b0000_0010; + const KEEPALIVE = 0b0000_0100; + const H2 = 0b0000_1000; + } +} + +bitflags! { + struct EntryFlags: u8 { + const EOF = 0b0000_0001; + const ERROR = 0b0000_0010; + const FINISHED = 0b0000_0100; + } +} + + pub(crate) enum Http1Result { Done, Switch, @@ -41,44 +59,44 @@ enum Item { } pub(crate) struct Http1 { + flags: Flags, router: Rc>, + local: SocketAddr, addr: Option, stream: H1Writer, reader: Reader, read_buf: BytesMut, - error: bool, tasks: VecDeque, - keepalive: bool, keepalive_timer: Option, - h2: bool, } struct Entry { pipe: Pipeline, - eof: bool, - error: bool, - finished: bool, + flags: EntryFlags, } impl Http1 where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static { - pub fn new(stream: T, addr: Option, router: Rc>) -> Self { + pub fn new(stream: T, local: SocketAddr, secure: bool, + addr: Option, router: Rc>) -> Self { Http1{ router: router, + local: local, + flags: if secure { Flags::SECURE | Flags::KEEPALIVE } else { Flags::KEEPALIVE }, addr: addr, stream: H1Writer::new(stream), reader: Reader::new(), read_buf: BytesMut::new(), - error: false, tasks: VecDeque::new(), - keepalive: true, - keepalive_timer: None, - h2: false } + keepalive_timer: None } } - pub fn into_inner(mut self) -> (T, Option, Rc>, Bytes) { - (self.stream.unwrap(), self.addr, self.router, self.read_buf.freeze()) + pub fn into_inner(mut self) -> (T, SocketAddr, bool, + Option, Rc>, Bytes) { + (self.stream.unwrap(), self.local, + self.flags.contains(Flags::SECURE), + self.addr, self.router, self.read_buf.freeze()) } pub fn poll(&mut self) -> Poll { @@ -103,8 +121,8 @@ impl Http1 while idx < self.tasks.len() { let item = &mut self.tasks[idx]; - if !io && !item.eof { - if item.error { + if !io && !item.flags.contains(EntryFlags::EOF) { + if item.flags.contains(EntryFlags::ERROR) { return Err(()) } @@ -113,14 +131,16 @@ impl Http1 not_ready = false; // overide keep-alive state - if self.keepalive { - self.keepalive = self.stream.keepalive(); + if self.stream.keepalive() { + self.flags.insert(Flags::KEEPALIVE); + } else { + self.flags.remove(Flags::KEEPALIVE); } self.stream = H1Writer::new(self.stream.unwrap()); - item.eof = true; + item.flags.insert(EntryFlags::EOF); if ready { - item.finished = true; + item.flags.insert(EntryFlags::FINISHED); } }, Ok(Async::NotReady) => { @@ -134,15 +154,15 @@ impl Http1 return Err(()) } } - } else if !item.finished { + } else if !item.flags.contains(EntryFlags::FINISHED) { match item.pipe.poll() { Ok(Async::NotReady) => (), Ok(Async::Ready(_)) => { not_ready = false; - item.finished = true; + item.flags.insert(EntryFlags::FINISHED); }, Err(err) => { - item.error = true; + item.flags.insert(EntryFlags::ERROR); error!("Unhandled error: {}", err); } } @@ -152,7 +172,9 @@ impl Http1 // cleanup finished tasks while !self.tasks.is_empty() { - if self.tasks[0].eof && self.tasks[0].finished { + if self.tasks[0].flags.contains(EntryFlags::EOF) && + self.tasks[0].flags.contains(EntryFlags::FINISHED) + { self.tasks.pop_front(); } else { break @@ -160,8 +182,8 @@ impl Http1 } // no keep-alive - if !self.keepalive && self.tasks.is_empty() { - if self.h2 { + if !self.flags.contains(Flags::KEEPALIVE) && self.tasks.is_empty() { + if self.flags.contains(Flags::H2) { return Ok(Async::Ready(Http1Result::Switch)) } else { return Ok(Async::Ready(Http1Result::Done)) @@ -169,7 +191,8 @@ impl Http1 } // read incoming data - while !self.error && !self.h2 && self.tasks.len() < MAX_PIPELINED_MESSAGES { + while !self.flags.contains(Flags::ERROR) && !self.flags.contains(Flags::H2) && + self.tasks.len() < MAX_PIPELINED_MESSAGES { match self.reader.parse(self.stream.get_mut(), &mut self.read_buf) { Ok(Async::Ready(Item::Http1(mut req))) => { not_ready = false; @@ -194,16 +217,14 @@ impl Http1 self.tasks.push_back( Entry {pipe: pipe.unwrap_or_else(|| Pipeline::error(HTTPNotFound)), - eof: false, - error: false, - finished: false}); + flags: EntryFlags::empty()}); } Ok(Async::Ready(Item::Http2)) => { - self.h2 = true; + self.flags.insert(Flags::H2); } Err(ReaderError::Disconnect) => { not_ready = false; - self.error = true; + self.flags.insert(Flags::ERROR); self.stream.disconnected(); for entry in &mut self.tasks { entry.pipe.disconnected() @@ -218,26 +239,24 @@ impl Http1 } // kill keepalive - self.keepalive = false; + self.flags.remove(Flags::KEEPALIVE); self.keepalive_timer.take(); // on parse error, stop reading stream but tasks need to be completed - self.error = true; + self.flags.insert(Flags::ERROR); if self.tasks.is_empty() { if let ReaderError::Error(err) = err { self.tasks.push_back( Entry {pipe: Pipeline::error(err.error_response()), - eof: false, - error: false, - finished: false}); + flags: EntryFlags::empty()}); } } } Ok(Async::NotReady) => { // start keep-alive timer, this is also slow request timeout if self.tasks.is_empty() { - if self.keepalive { + if self.flags.contains(Flags::KEEPALIVE) { if self.keepalive_timer.is_none() { trace!("Start keep-alive timer"); let mut timeout = Timeout::new( @@ -259,10 +278,10 @@ impl Http1 // check for parse error if self.tasks.is_empty() { - if self.h2 { + if self.flags.contains(Flags::H2) { return Ok(Async::Ready(Http1Result::Switch)) } - if self.error || self.keepalive_timer.is_none() { + if self.flags.contains(Flags::ERROR) || self.keepalive_timer.is_none() { return Ok(Async::Ready(Http1Result::Done)) } } diff --git a/src/h1writer.rs b/src/h1writer.rs index 447758cf6..8776b4f4f 100644 --- a/src/h1writer.rs +++ b/src/h1writer.rs @@ -35,28 +35,30 @@ pub(crate) trait Writer { fn poll_complete(&mut self) -> Poll<(), io::Error>; } +bitflags! { + struct Flags: u8 { + const STARTED = 0b0000_0001; + const UPGRADE = 0b0000_0010; + const KEEPALIVE = 0b0000_0100; + const DISCONNECTED = 0b0000_1000; + } +} pub(crate) struct H1Writer { + flags: Flags, stream: Option, - started: bool, encoder: PayloadEncoder, - upgrade: bool, - keepalive: bool, - disconnected: bool, written: u64, - headers_size: u64, + headers_size: u32, } impl H1Writer { pub fn new(stream: T) -> H1Writer { H1Writer { + flags: Flags::empty(), stream: Some(stream), - started: false, encoder: PayloadEncoder::default(), - upgrade: false, - keepalive: false, - disconnected: false, written: 0, headers_size: 0, } @@ -75,7 +77,7 @@ impl H1Writer { } pub fn keepalive(&self) -> bool { - self.keepalive && !self.upgrade + self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE) } fn write_to_stream(&mut self) -> Result { @@ -105,9 +107,10 @@ impl H1Writer { impl Writer for H1Writer { + #[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))] fn written(&self) -> u64 { - if self.written > self.headers_size { - self.written - self.headers_size + if self.written > self.headers_size as u64 { + self.written - self.headers_size as u64 } else { 0 } @@ -119,9 +122,11 @@ impl Writer for H1Writer { trace!("Prepare response with status: {:?}", msg.status()); // prepare task - self.started = true; + self.flags.insert(Flags::STARTED); self.encoder = PayloadEncoder::new(req, msg); - self.keepalive = msg.keep_alive().unwrap_or_else(|| req.keep_alive()); + if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { + self.flags.insert(Flags::KEEPALIVE); + } // Connection upgrade let version = msg.version().unwrap_or_else(|| req.version()); @@ -129,7 +134,7 @@ impl Writer for H1Writer { msg.headers_mut().insert(CONNECTION, HeaderValue::from_static("upgrade")); } // keep-alive - else if self.keepalive { + else if self.flags.contains(Flags::KEEPALIVE) { if version < Version::HTTP_11 { msg.headers_mut().insert(CONNECTION, HeaderValue::from_static("keep-alive")); } @@ -177,7 +182,7 @@ impl Writer for H1Writer { // msg eof buffer.extend(b"\r\n"); - self.headers_size = buffer.len() as u64; + self.headers_size = buffer.len() as u32; } trace!("Response: {:?}", msg); @@ -193,8 +198,8 @@ impl Writer for H1Writer { } fn write(&mut self, payload: &[u8]) -> Result { - if !self.disconnected { - if self.started { + if !self.flags.contains(Flags::DISCONNECTED) { + if self.flags.contains(Flags::STARTED) { // TODO: add warning, write after EOF self.encoder.write(payload)?; } else { diff --git a/src/h2.rs b/src/h2.rs index cf89a719c..74f033ff8 100644 --- a/src/h2.rs +++ b/src/h2.rs @@ -25,13 +25,22 @@ use payload::{Payload, PayloadWriter}; const KEEPALIVE_PERIOD: u64 = 15; // seconds +bitflags! { + struct Flags: u8 { + const SECURE = 0b0000_0001; + const DISCONNECTED = 0b0000_0010; + } +} + +/// HTTP/2 Transport pub(crate) struct Http2 where T: AsyncRead + AsyncWrite + 'static, H: 'static { + flags: Flags, router: Rc>, + local: SocketAddr, addr: Option, state: State>, - disconnected: bool, tasks: VecDeque, keepalive_timer: Option, } @@ -46,10 +55,12 @@ impl Http2 where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static { - pub fn new(stream: T, addr: Option, router: Rc>, buf: Bytes) -> Self { - Http2{ router: router, + pub fn new(stream: T, local: SocketAddr, secure: bool, + addr: Option, router: Rc>, buf: Bytes) -> Self { + Http2{ flags: if secure { Flags::SECURE } else { Flags::empty() }, + router: router, + local: local, addr: addr, - disconnected: false, tasks: VecDeque::new(), state: State::Handshake( Server::handshake(IoWrapper{unread: Some(buf), inner: stream})), @@ -80,33 +91,33 @@ impl Http2 // read payload item.poll_payload(); - if !item.eof { + if !item.flags.contains(EntryFlags::EOF) { match item.task.poll_io(&mut item.stream) { Ok(Async::Ready(ready)) => { - item.eof = true; + item.flags.insert(EntryFlags::EOF); if ready { - item.finished = true; + item.flags.insert(EntryFlags::FINISHED); } not_ready = false; }, Ok(Async::NotReady) => (), Err(err) => { error!("Unhandled error: {}", err); - item.eof = true; - item.error = true; + item.flags.insert(EntryFlags::EOF); + item.flags.insert(EntryFlags::ERROR); item.stream.reset(Reason::INTERNAL_ERROR); } } - } else if !item.finished { + } else if !item.flags.contains(EntryFlags::FINISHED) { match item.task.poll() { Ok(Async::NotReady) => (), Ok(Async::Ready(_)) => { not_ready = false; - item.finished = true; + item.flags.insert(EntryFlags::FINISHED); }, Err(err) => { - item.error = true; - item.finished = true; + item.flags.insert(EntryFlags::ERROR); + item.flags.insert(EntryFlags::FINISHED); error!("Unhandled error: {}", err); } } @@ -115,7 +126,10 @@ impl Http2 // cleanup finished tasks while !self.tasks.is_empty() { - if self.tasks[0].eof && self.tasks[0].finished || self.tasks[0].error { + if self.tasks[0].flags.contains(EntryFlags::EOF) && + self.tasks[0].flags.contains(EntryFlags::FINISHED) || + self.tasks[0].flags.contains(EntryFlags::ERROR) + { self.tasks.pop_front(); } else { break @@ -123,11 +137,11 @@ impl Http2 } // get request - if !self.disconnected { + if !self.flags.contains(Flags::DISCONNECTED) { match server.poll() { Ok(Async::Ready(None)) => { not_ready = false; - self.disconnected = true; + self.flags.insert(Flags::DISCONNECTED); for entry in &mut self.tasks { entry.task.disconnected() } @@ -156,7 +170,7 @@ impl Http2 } Err(err) => { trace!("Connection error: {}", err); - self.disconnected = true; + self.flags.insert(Flags::DISCONNECTED); for entry in &mut self.tasks { entry.task.disconnected() } @@ -166,7 +180,7 @@ impl Http2 } if not_ready { - if self.tasks.is_empty() && self.disconnected { + if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED) { return Ok(Async::Ready(())) } else { return Ok(Async::NotReady) @@ -196,16 +210,22 @@ impl Http2 } } +bitflags! { + struct EntryFlags: u8 { + const EOF = 0b0000_0001; + const REOF = 0b0000_0010; + const ERROR = 0b0000_0100; + const FINISHED = 0b0000_1000; + } +} + struct Entry { task: Pipeline, payload: PayloadType, recv: RecvStream, stream: H2Writer, - eof: bool, - error: bool, - finished: bool, - reof: bool, capacity: usize, + flags: EntryFlags, } impl Entry { @@ -244,22 +264,19 @@ impl Entry { payload: psender, recv: recv, stream: H2Writer::new(resp), - eof: false, - error: false, - finished: false, - reof: false, + flags: EntryFlags::empty(), capacity: 0, } } fn poll_payload(&mut self) { - if !self.reof { + if !self.flags.contains(EntryFlags::REOF) { match self.recv.poll() { Ok(Async::Ready(Some(chunk))) => { self.payload.feed_data(chunk); }, Ok(Async::Ready(None)) => { - self.reof = true; + self.flags.insert(EntryFlags::REOF); }, Ok(Async::NotReady) => (), Err(err) => { diff --git a/src/h2writer.rs b/src/h2writer.rs index 82a1b96e0..062c69e4e 100644 --- a/src/h2writer.rs +++ b/src/h2writer.rs @@ -16,14 +16,19 @@ use h1writer::{Writer, WriterState}; const CHUNK_SIZE: usize = 16_384; const MAX_WRITE_BUFFER_SIZE: usize = 65_536; // max buffer size 64k +bitflags! { + struct Flags: u8 { + const STARTED = 0b0000_0001; + const DISCONNECTED = 0b0000_0010; + const EOF = 0b0000_0100; + } +} pub(crate) struct H2Writer { respond: Respond, stream: Option>, - started: bool, encoder: PayloadEncoder, - disconnected: bool, - eof: bool, + flags: Flags, written: u64, } @@ -33,10 +38,8 @@ impl H2Writer { H2Writer { respond: respond, stream: None, - started: false, encoder: PayloadEncoder::default(), - disconnected: false, - eof: true, + flags: Flags::empty(), written: 0, } } @@ -48,7 +51,7 @@ impl H2Writer { } fn write_to_stream(&mut self) -> Result { - if !self.started { + if !self.flags.contains(Flags::STARTED) { return Ok(WriterState::Done) } @@ -56,7 +59,7 @@ impl H2Writer { let buffer = self.encoder.get_mut(); if buffer.is_empty() { - if self.eof { + if self.flags.contains(Flags::EOF) { let _ = stream.send_data(Bytes::new(), true); } return Ok(WriterState::Done) @@ -77,7 +80,7 @@ impl H2Writer { Ok(Async::Ready(Some(cap))) => { let len = buffer.len(); let bytes = buffer.split_to(cmp::min(cap, len)); - let eof = buffer.is_empty() && self.eof; + let eof = buffer.is_empty() && self.flags.contains(Flags::EOF); self.written += bytes.len() as u64; if let Err(err) = stream.send_data(bytes.freeze(), eof) { @@ -111,9 +114,11 @@ impl Writer for H2Writer { trace!("Prepare response with status: {:?}", msg.status()); // prepare response - self.started = true; + self.flags.insert(Flags::STARTED); self.encoder = PayloadEncoder::new(req, msg); - self.eof = if let Body::Empty = *msg.body() { true } else { false }; + if let Body::Empty = *msg.body() { + self.flags.insert(Flags::EOF); + } // http2 specific msg.headers_mut().remove(CONNECTION); @@ -140,7 +145,7 @@ impl Writer for H2Writer { resp.headers_mut().insert(key, value.clone()); } - match self.respond.send_response(resp, self.eof) { + match self.respond.send_response(resp, self.flags.contains(Flags::EOF)) { Ok(stream) => self.stream = Some(stream), Err(_) => @@ -151,7 +156,7 @@ impl Writer for H2Writer { if msg.body().is_binary() { if let Body::Binary(bytes) = msg.replace_body(Body::Empty) { - self.eof = true; + self.flags.insert(Flags::EOF); self.encoder.write(bytes.as_ref())?; if let Some(ref mut stream) = self.stream { stream.reserve_capacity(cmp::min(self.encoder.len(), CHUNK_SIZE)); @@ -164,8 +169,8 @@ impl Writer for H2Writer { } fn write(&mut self, payload: &[u8]) -> Result { - if !self.disconnected { - if self.started { + if !self.flags.contains(Flags::DISCONNECTED) { + if self.flags.contains(Flags::STARTED) { // TODO: add warning, write after EOF self.encoder.write(payload)?; } else { @@ -184,7 +189,7 @@ impl Writer for H2Writer { fn write_eof(&mut self) -> Result { self.encoder.write_eof()?; - self.eof = true; + self.flags.insert(Flags::EOF); if !self.encoder.is_eof() { Err(io::Error::new(io::ErrorKind::Other, "Last payload item, but eof is not reached")) diff --git a/src/lib.rs b/src/lib.rs index b772ec9fb..a2d9d6830 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,8 @@ extern crate bytes; extern crate sha1; extern crate regex; #[macro_use] +extern crate bitflags; +#[macro_use] extern crate futures; extern crate tokio_io; extern crate tokio_core; @@ -61,7 +63,6 @@ mod route; mod router; mod param; mod resource; -// mod recognizer; mod handler; mod pipeline; mod server; diff --git a/src/server.rs b/src/server.rs index 0a58449ff..635147b51 100644 --- a/src/server.rs +++ b/src/server.rs @@ -26,7 +26,6 @@ use tokio_openssl::{SslStream, SslAcceptorExt}; use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; - /// An HTTP Server /// /// `T` - async stream, anything that implements `AsyncRead` + `AsyncWrite`. @@ -64,12 +63,15 @@ impl HttpServer H: HttpHandler, { /// Start listening for incomming connections from stream. - pub fn serve_incoming(self, stream: S) -> io::Result + pub fn serve_incoming(self, stream: S, secure: bool) -> io::Result where Self: ActorAddress, S: Stream + 'static { Ok(HttpServer::create(move |ctx| { - ctx.add_stream(stream.map(|(t, _)| IoStream(t, None, false))); + let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + ctx.add_stream(stream.map( + move |(t, _)| IoStream{io: t, srv: addr, + peer: None, http2: false, secure: secure})); self })) } @@ -114,7 +116,9 @@ impl HttpServer { Ok(HttpServer::create(move |ctx| { for (addr, tcp) in addrs { info!("Starting http server on {}", addr); - ctx.add_stream(tcp.incoming().map(|(t, a)| IoStream(t, Some(a), false))); + ctx.add_stream(tcp.incoming().map( + move |(t, a)| IoStream{io: t, srv: addr, + peer: Some(a), http2: false, secure: false})); } self })) @@ -144,15 +148,15 @@ impl HttpServer, net::SocketAddr, H> { }; Ok(HttpServer::create(move |ctx| { - for (addr, tcp) in addrs { - info!("Starting tls http server on {}", addr); + for (srv, tcp) in addrs { + info!("Starting tls http server on {}", srv); let acc = acceptor.clone(); ctx.add_stream(tcp.incoming().and_then(move |(stream, addr)| { TlsAcceptorExt::accept_async(acc.as_ref(), stream) - .map(move |t| { - IoStream(t, Some(addr), false) - }) + .map(move |t| + IoStream{io: t, srv: srv.clone(), + peer: Some(addr), http2: false, secure: true}) .map_err(|err| { trace!("Error during handling tls connection: {}", err); io::Error::new(io::ErrorKind::Other, err) @@ -191,8 +195,8 @@ impl HttpServer, net::SocketAddr, H> { }; Ok(HttpServer::create(move |ctx| { - for (addr, tcp) in addrs { - info!("Starting tls http server on {}", addr); + for (srv, tcp) in addrs { + info!("Starting tls http server on {}", srv); let acc = acceptor.clone(); ctx.add_stream(tcp.incoming().and_then(move |(stream, addr)| { @@ -205,7 +209,8 @@ impl HttpServer, net::SocketAddr, H> { } else { false }; - IoStream(stream, Some(addr), http2) + IoStream{io: stream, srv: srv.clone(), + peer: Some(addr), http2: http2, secure: true} }) .map_err(|err| { trace!("Error during handling tls connection: {}", err); @@ -218,7 +223,13 @@ impl HttpServer, net::SocketAddr, H> { } } -struct IoStream(T, Option, bool); +struct IoStream { + io: T, + srv: SocketAddr, + peer: Option, + http2: bool, + secure: bool, +} impl ResponseType for IoStream where T: AsyncRead + AsyncWrite + 'static @@ -245,7 +256,8 @@ impl Handler, io::Error> for HttpServer -> Response> { Arbiter::handle().spawn( - HttpChannel::new(msg.0, msg.1, Rc::clone(&self.h), msg.2)); + HttpChannel::new(msg.io, msg.srv, msg.secure, + msg.peer, Rc::clone(&self.h), msg.http2)); Self::empty() } } diff --git a/tests/test_server.rs b/tests/test_server.rs index 7704c3e35..b1715f252 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -39,7 +39,7 @@ fn test_serve_incoming() { Application::new("/") .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))); let tcp = TcpListener::from_listener(tcp, &addr2, Arbiter::handle()).unwrap(); - srv.serve_incoming::<_, ()>(tcp.incoming()).unwrap(); + srv.serve_incoming::<_, ()>(tcp.incoming(), false).unwrap(); sys.run(); }); From 2192d14eff50d1bae6ac9536e69e5ec4f3eaa6d5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Dec 2017 22:54:44 -0800 Subject: [PATCH 055/279] added ServerSettings --- examples/basic.rs | 20 ++++---- src/channel.rs | 20 +++----- src/h1.rs | 27 +++++----- src/h2.rs | 20 ++++---- src/lib.rs | 2 +- src/server.rs | 127 +++++++++++++++++++++++++++++++++++----------- 6 files changed, 138 insertions(+), 78 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index 164ca7a18..e6fe48227 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -72,15 +72,6 @@ fn main() { .resource("/user/{name}/", |r| r.method(Method::GET).f(with_param)) // async handler .resource("/async/{name}", |r| r.method(Method::GET).a(index_async)) - // redirect - .resource("/", |r| r.method(Method::GET).f(|req| { - println!("{:?}", req); - - httpcodes::HTTPFound - .build() - .header("LOCATION", "/index.html") - .body(Body::Empty) - })) .resource("/test", |r| r.f(|req| { match *req.method() { Method::GET => httpcodes::HTTPOk, @@ -89,7 +80,16 @@ fn main() { } })) // static files - .resource("/static", |r| r.h(fs::StaticFiles::new("examples/static/", true)))) + .resource("/static", |r| r.h(fs::StaticFiles::new("examples/static/", true))) + // redirect + .resource("/", |r| r.method(Method::GET).f(|req| { + println!("{:?}", req); + + httpcodes::HTTPFound + .build() + .header("LOCATION", "/index.html") + .body(Body::Empty) + }))) .serve::<_, ()>("127.0.0.1:8080").unwrap(); println!("Started http server: 127.0.0.1:8080"); diff --git a/src/channel.rs b/src/channel.rs index 3a253862f..e266d4d48 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -1,4 +1,3 @@ -use std::rc::Rc; use std::net::SocketAddr; use actix::dev::*; @@ -10,6 +9,7 @@ use h1; use h2; use pipeline::Pipeline; use httprequest::HttpRequest; +use server::ServerSettings; /// Low level http request handler pub trait HttpHandler: 'static { @@ -51,21 +51,17 @@ pub struct HttpChannel impl HttpChannel where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static { - pub fn new(stream: T, - local: SocketAddr, - secure: bool, - peer: Option, - router: Rc>, - http2: bool) -> HttpChannel + pub fn new(settings: ServerSettings, + stream: T, peer: Option, http2: bool) -> HttpChannel { if http2 { HttpChannel { proto: Some(HttpProtocol::H2( - h2::Http2::new(stream, local, secure, peer, router, Bytes::new()))) } + h2::Http2::new(settings, stream, peer, Bytes::new()))) } } else { HttpChannel { proto: Some(HttpProtocol::H1( - h1::Http1::new(stream, local, secure, peer, router))) } + h1::Http1::new(settings, stream, peer))) } } } } @@ -110,9 +106,9 @@ impl Future for HttpChannel let proto = self.proto.take().unwrap(); match proto { HttpProtocol::H1(h1) => { - let (stream, local, secure, addr, router, buf) = h1.into_inner(); - self.proto = Some(HttpProtocol::H2( - h2::Http2::new(stream, local, secure, addr, router, buf))); + let (settings, stream, addr, buf) = h1.into_inner(); + self.proto = Some( + HttpProtocol::H2(h2::Http2::new(settings, stream, addr, buf))); self.poll() } _ => unreachable!() diff --git a/src/h1.rs b/src/h1.rs index 54217610b..cebdfa190 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -1,5 +1,4 @@ use std::{self, io, ptr}; -use std::rc::Rc; use std::net::SocketAddr; use std::time::Duration; use std::collections::VecDeque; @@ -21,6 +20,7 @@ use httpcodes::HTTPNotFound; use httprequest::HttpRequest; use error::{ParseError, PayloadError, ResponseError}; use payload::{Payload, PayloadWriter, DEFAULT_BUFFER_SIZE}; +use server::ServerSettings; const KEEPALIVE_PERIOD: u64 = 15; // seconds const INIT_BUFFER_SIZE: usize = 8192; @@ -60,8 +60,7 @@ enum Item { pub(crate) struct Http1 { flags: Flags, - router: Rc>, - local: SocketAddr, + settings: ServerSettings, addr: Option, stream: H1Writer, reader: Reader, @@ -79,11 +78,14 @@ impl Http1 where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static { - pub fn new(stream: T, local: SocketAddr, secure: bool, - addr: Option, router: Rc>) -> Self { - Http1{ router: router, - local: local, - flags: if secure { Flags::SECURE | Flags::KEEPALIVE } else { Flags::KEEPALIVE }, + pub fn new(settings: ServerSettings, stream: T, addr: Option) -> Self { + let flags = if settings.secure() { + Flags::SECURE | Flags::KEEPALIVE + } else { + Flags::KEEPALIVE + }; + Http1{ flags: flags, + settings: settings, addr: addr, stream: H1Writer::new(stream), reader: Reader::new(), @@ -92,11 +94,8 @@ impl Http1 keepalive_timer: None } } - pub fn into_inner(mut self) -> (T, SocketAddr, bool, - Option, Rc>, Bytes) { - (self.stream.unwrap(), self.local, - self.flags.contains(Flags::SECURE), - self.addr, self.router, self.read_buf.freeze()) + pub fn into_inner(mut self) -> (ServerSettings, T, Option, Bytes) { + (self.settings, self.stream.unwrap(), self.addr, self.read_buf.freeze()) } pub fn poll(&mut self) -> Poll { @@ -205,7 +204,7 @@ impl Http1 // start request processing let mut pipe = None; - for h in self.router.iter() { + for h in self.settings.handlers() { req = match h.handle(req) { Ok(t) => { pipe = Some(t); diff --git a/src/h2.rs b/src/h2.rs index 74f033ff8..5a77e1943 100644 --- a/src/h2.rs +++ b/src/h2.rs @@ -1,5 +1,4 @@ use std::{io, cmp, mem}; -use std::rc::Rc; use std::io::{Read, Write}; use std::time::Duration; use std::net::SocketAddr; @@ -22,6 +21,7 @@ use encoding::PayloadType; use httpcodes::HTTPNotFound; use httprequest::HttpRequest; use payload::{Payload, PayloadWriter}; +use server::ServerSettings; const KEEPALIVE_PERIOD: u64 = 15; // seconds @@ -37,8 +37,7 @@ pub(crate) struct Http2 where T: AsyncRead + AsyncWrite + 'static, H: 'static { flags: Flags, - router: Rc>, - local: SocketAddr, + settings: ServerSettings, addr: Option, state: State>, tasks: VecDeque, @@ -55,11 +54,10 @@ impl Http2 where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static { - pub fn new(stream: T, local: SocketAddr, secure: bool, - addr: Option, router: Rc>, buf: Bytes) -> Self { - Http2{ flags: if secure { Flags::SECURE } else { Flags::empty() }, - router: router, - local: local, + pub fn new(settings: ServerSettings, stream: T, addr: Option, buf: Bytes) -> Self + { + Http2{ flags: if settings.secure() { Flags::SECURE } else { Flags::empty() }, + settings: settings, addr: addr, tasks: VecDeque::new(), state: State::Handshake( @@ -154,7 +152,7 @@ impl Http2 self.keepalive_timer.take(); self.tasks.push_back( - Entry::new(parts, body, resp, self.addr, &self.router)); + Entry::new(parts, body, resp, self.addr, &self.settings)); } Ok(Async::NotReady) => { // start keep-alive timer @@ -233,7 +231,7 @@ impl Entry { recv: RecvStream, resp: Respond, addr: Option, - router: &Rc>) -> Entry + settings: &ServerSettings) -> Entry where H: HttpHandler + 'static { // Payload and Content-Encoding @@ -250,7 +248,7 @@ impl Entry { // start request processing let mut task = None; - for h in router.iter() { + for h in settings.handlers() { req = match h.handle(req) { Ok(t) => { task = Some(t); diff --git a/src/lib.rs b/src/lib.rs index a2d9d6830..78aa9c577 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,7 +131,7 @@ pub mod dev { pub use pipeline::Pipeline; pub use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; pub use param::{FromParam, Params}; - + pub use server::ServerSettings; pub use httprequest::UrlEncoded; pub use httpresponse::HttpResponseBuilder; } diff --git a/src/server.rs b/src/server.rs index 635147b51..674aca697 100644 --- a/src/server.rs +++ b/src/server.rs @@ -26,6 +26,53 @@ use tokio_openssl::{SslStream, SslAcceptorExt}; use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; +/// Various server settings +pub struct ServerSettings (Rc>); + +struct InnerServerSettings { + h: Vec, + addr: Option, + secure: bool, + sethost: bool, +} + +impl Clone for ServerSettings { + fn clone(&self) -> Self { + ServerSettings(Rc::clone(&self.0)) + } +} + +impl ServerSettings { + /// Crate server settings instance + fn new(h: Vec, addr: Option, secure: bool, sethost: bool) -> Self { + ServerSettings( + Rc::new(InnerServerSettings { + h: h, + addr: addr, + secure: secure, + sethost: sethost })) + } + + /// Returns list of http handlers + pub fn handlers(&self) -> &Vec { + &self.0.h + } + /// Returns the socket address of the local half of this TCP connection + pub fn local_addr(&self) -> Option { + self.0.addr + } + + /// Returns true if connection is secure(https) + pub fn secure(&self) -> bool { + self.0.secure + } + + /// Should http channel set *HOST* header + pub fn set_host_header(&self) -> bool { + self.0.sethost + } +} + /// An HTTP Server /// /// `T` - async stream, anything that implements `AsyncRead` + `AsyncWrite`. @@ -34,9 +81,10 @@ use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; /// /// `H` - request handler pub struct HttpServer { - h: Rc>, + h: Option>, io: PhantomData, addr: PhantomData, + sethost: bool, } impl Actor for HttpServer { @@ -51,9 +99,16 @@ impl HttpServer where H: HttpHandler { let apps: Vec<_> = handler.into_iter().map(|h| h.into_handler()).collect(); - HttpServer {h: Rc::new(apps), + HttpServer {h: Some(apps), io: PhantomData, - addr: PhantomData} + addr: PhantomData, + sethost: false} + } + + /// Set *HOST* header if not set + pub fn set_host_header(mut self) -> Self { + self.sethost = true; + self } } @@ -63,15 +118,18 @@ impl HttpServer H: HttpHandler, { /// Start listening for incomming connections from stream. - pub fn serve_incoming(self, stream: S, secure: bool) -> io::Result + pub fn serve_incoming(mut self, stream: S, secure: bool) -> io::Result where Self: ActorAddress, S: Stream + 'static { + let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let settings = ServerSettings::new( + self.h.take().unwrap(), Some(addr), secure, self.sethost); + Ok(HttpServer::create(move |ctx| { - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); ctx.add_stream(stream.map( - move |(t, _)| IoStream{io: t, srv: addr, - peer: None, http2: false, secure: secure})); + move |(t, _)| IoStream{settings: settings.clone(), + io: t, peer: None, http2: false})); self })) } @@ -107,18 +165,21 @@ impl HttpServer { /// /// This methods converts address to list of `SocketAddr` /// then binds to all available addresses. - pub fn serve(self, addr: S) -> io::Result + pub fn serve(mut self, addr: S) -> io::Result where Self: ActorAddress, S: net::ToSocketAddrs, { let addrs = self.bind(addr)?; + let settings = ServerSettings::new( + self.h.take().unwrap(), Some(addrs[0].0), false, self.sethost); Ok(HttpServer::create(move |ctx| { for (addr, tcp) in addrs { info!("Starting http server on {}", addr); + let s = settings.clone(); ctx.add_stream(tcp.incoming().map( - move |(t, a)| IoStream{io: t, srv: addr, - peer: Some(a), http2: false, secure: false})); + move |(t, a)| IoStream{settings: s.clone(), + io: t, peer: Some(a), http2: false})); } self })) @@ -132,11 +193,14 @@ impl HttpServer, net::SocketAddr, H> { /// /// This methods converts address to list of `SocketAddr` /// then binds to all available addresses. - pub fn serve_tls(self, addr: S, pkcs12: ::Pkcs12) -> io::Result + pub fn serve_tls(mut self, addr: S, pkcs12: ::Pkcs12) -> io::Result where Self: ActorAddress, S: net::ToSocketAddrs, { let addrs = self.bind(addr)?; + let settings = ServerSettings::new( + self.h.take().unwrap(), Some(addrs[0].0.clone()), true, self.sethost); + let acceptor = match TlsAcceptor::builder(pkcs12) { Ok(builder) => { match builder.build() { @@ -151,12 +215,14 @@ impl HttpServer, net::SocketAddr, H> { for (srv, tcp) in addrs { info!("Starting tls http server on {}", srv); + let st = settings.clone(); let acc = acceptor.clone(); ctx.add_stream(tcp.incoming().and_then(move |(stream, addr)| { + let st2 = st.clone(); TlsAcceptorExt::accept_async(acc.as_ref(), stream) .map(move |t| - IoStream{io: t, srv: srv.clone(), - peer: Some(addr), http2: false, secure: true}) + IoStream{settings: st2.clone(), + io: t, peer: Some(addr), http2: false}) .map_err(|err| { trace!("Error during handling tls connection: {}", err); io::Error::new(io::ErrorKind::Other, err) @@ -175,15 +241,16 @@ impl HttpServer, net::SocketAddr, H> { /// /// This methods converts address to list of `SocketAddr` /// then binds to all available addresses. - pub fn serve_tls(self, addr: S, identity: ParsedPkcs12) -> io::Result + pub fn serve_tls(mut self, addr: S, identity: ParsedPkcs12) -> io::Result where Self: ActorAddress, S: net::ToSocketAddrs, { let addrs = self.bind(addr)?; - let acceptor = match SslAcceptorBuilder::mozilla_intermediate(SslMethod::tls(), - &identity.pkey, - &identity.cert, - &identity.chain) + let settings = ServerSettings::new( + self.h.take().unwrap(), Some(addrs[0].0.clone()), true, self.sethost); + + let acceptor = match SslAcceptorBuilder::mozilla_intermediate( + SslMethod::tls(), &identity.pkey, &identity.cert, &identity.chain) { Ok(mut builder) => { match builder.builder_mut().set_alpn_protocols(&[b"h2", b"http/1.1"]) { @@ -198,8 +265,10 @@ impl HttpServer, net::SocketAddr, H> { for (srv, tcp) in addrs { info!("Starting tls http server on {}", srv); + let st = settings.clone(); let acc = acceptor.clone(); ctx.add_stream(tcp.incoming().and_then(move |(stream, addr)| { + let st2 = st.clone(); SslAcceptorExt::accept_async(&acc, stream) .map(move |stream| { let http2 = if let Some(p) = @@ -209,8 +278,8 @@ impl HttpServer, net::SocketAddr, H> { } else { false }; - IoStream{io: stream, srv: srv.clone(), - peer: Some(addr), http2: http2, secure: true} + IoStream{settings: st2.clone(), + io: stream, peer: Some(addr), http2: http2} }) .map_err(|err| { trace!("Error during handling tls connection: {}", err); @@ -223,27 +292,26 @@ impl HttpServer, net::SocketAddr, H> { } } -struct IoStream { +struct IoStream { io: T, - srv: SocketAddr, peer: Option, http2: bool, - secure: bool, + settings: ServerSettings, } -impl ResponseType for IoStream +impl ResponseType for IoStream where T: AsyncRead + AsyncWrite + 'static { type Item = (); type Error = (); } -impl StreamHandler, io::Error> for HttpServer +impl StreamHandler, io::Error> for HttpServer where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static, A: 'static {} -impl Handler, io::Error> for HttpServer +impl Handler, io::Error> for HttpServer where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static, A: 'static, @@ -252,12 +320,11 @@ impl Handler, io::Error> for HttpServer debug!("Error handling request: {}", err) } - fn handle(&mut self, msg: IoStream, _: &mut Context) - -> Response> + fn handle(&mut self, msg: IoStream, _: &mut Context) + -> Response> { Arbiter::handle().spawn( - HttpChannel::new(msg.io, msg.srv, msg.secure, - msg.peer, Rc::clone(&self.h), msg.http2)); + HttpChannel::new(msg.settings, msg.io, msg.peer, msg.http2)); Self::empty() } } From 129361909642cadc9e88537ba4615a7570ec4041 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 8 Dec 2017 09:24:05 -0800 Subject: [PATCH 056/279] set server settings to HttpHandler --- src/channel.rs | 19 ++++--- src/h1.rs | 22 +++----- src/h2.rs | 17 +++--- src/httprequest.rs | 3 +- src/server.rs | 132 ++++++++++++++++++++++----------------------- 5 files changed, 92 insertions(+), 101 deletions(-) diff --git a/src/channel.rs b/src/channel.rs index e266d4d48..c649d8c46 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -1,3 +1,4 @@ +use std::rc::Rc; use std::net::SocketAddr; use actix::dev::*; @@ -12,9 +13,13 @@ use httprequest::HttpRequest; use server::ServerSettings; /// Low level http request handler +#[allow(unused_variables)] pub trait HttpHandler: 'static { /// Handle request fn handle(&self, req: HttpRequest) -> Result; + + /// Set server settings + fn server_settings(&mut self, settings: ServerSettings) {} } /// Conversion helper trait @@ -51,17 +56,16 @@ pub struct HttpChannel impl HttpChannel where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static { - pub fn new(settings: ServerSettings, - stream: T, peer: Option, http2: bool) -> HttpChannel + pub fn new(h: Rc>, io: T, peer: Option, http2: bool) -> HttpChannel { if http2 { HttpChannel { proto: Some(HttpProtocol::H2( - h2::Http2::new(settings, stream, peer, Bytes::new()))) } + h2::Http2::new(h, io, peer, Bytes::new()))) } } else { HttpChannel { proto: Some(HttpProtocol::H1( - h1::Http1::new(settings, stream, peer))) } + h1::Http1::new(h, io, peer))) } } } } @@ -97,8 +101,7 @@ impl Future for HttpChannel return Err(()), } } - Some(HttpProtocol::H2(ref mut h2)) => - return h2.poll(), + Some(HttpProtocol::H2(ref mut h2)) => return h2.poll(), None => unreachable!(), } @@ -106,9 +109,9 @@ impl Future for HttpChannel let proto = self.proto.take().unwrap(); match proto { HttpProtocol::H1(h1) => { - let (settings, stream, addr, buf) = h1.into_inner(); + let (h, io, addr, buf) = h1.into_inner(); self.proto = Some( - HttpProtocol::H2(h2::Http2::new(settings, stream, addr, buf))); + HttpProtocol::H2(h2::Http2::new(h, io, addr, buf))); self.poll() } _ => unreachable!() diff --git a/src/h1.rs b/src/h1.rs index cebdfa190..9b4587d87 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -1,4 +1,5 @@ use std::{self, io, ptr}; +use std::rc::Rc; use std::net::SocketAddr; use std::time::Duration; use std::collections::VecDeque; @@ -20,7 +21,6 @@ use httpcodes::HTTPNotFound; use httprequest::HttpRequest; use error::{ParseError, PayloadError, ResponseError}; use payload::{Payload, PayloadWriter, DEFAULT_BUFFER_SIZE}; -use server::ServerSettings; const KEEPALIVE_PERIOD: u64 = 15; // seconds const INIT_BUFFER_SIZE: usize = 8192; @@ -31,7 +31,6 @@ const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; bitflags! { struct Flags: u8 { - const SECURE = 0b0000_0001; const ERROR = 0b0000_0010; const KEEPALIVE = 0b0000_0100; const H2 = 0b0000_1000; @@ -60,7 +59,7 @@ enum Item { pub(crate) struct Http1 { flags: Flags, - settings: ServerSettings, + handlers: Rc>, addr: Option, stream: H1Writer, reader: Reader, @@ -78,14 +77,9 @@ impl Http1 where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static { - pub fn new(settings: ServerSettings, stream: T, addr: Option) -> Self { - let flags = if settings.secure() { - Flags::SECURE | Flags::KEEPALIVE - } else { - Flags::KEEPALIVE - }; - Http1{ flags: flags, - settings: settings, + pub fn new(h: Rc>, stream: T, addr: Option) -> Self { + Http1{ flags: Flags::KEEPALIVE, + handlers: h, addr: addr, stream: H1Writer::new(stream), reader: Reader::new(), @@ -94,8 +88,8 @@ impl Http1 keepalive_timer: None } } - pub fn into_inner(mut self) -> (ServerSettings, T, Option, Bytes) { - (self.settings, self.stream.unwrap(), self.addr, self.read_buf.freeze()) + pub fn into_inner(mut self) -> (Rc>, T, Option, Bytes) { + (self.handlers, self.stream.unwrap(), self.addr, self.read_buf.freeze()) } pub fn poll(&mut self) -> Poll { @@ -204,7 +198,7 @@ impl Http1 // start request processing let mut pipe = None; - for h in self.settings.handlers() { + for h in self.handlers.iter() { req = match h.handle(req) { Ok(t) => { pipe = Some(t); diff --git a/src/h2.rs b/src/h2.rs index 5a77e1943..264fb4629 100644 --- a/src/h2.rs +++ b/src/h2.rs @@ -1,4 +1,5 @@ use std::{io, cmp, mem}; +use std::rc::Rc; use std::io::{Read, Write}; use std::time::Duration; use std::net::SocketAddr; @@ -21,13 +22,11 @@ use encoding::PayloadType; use httpcodes::HTTPNotFound; use httprequest::HttpRequest; use payload::{Payload, PayloadWriter}; -use server::ServerSettings; const KEEPALIVE_PERIOD: u64 = 15; // seconds bitflags! { struct Flags: u8 { - const SECURE = 0b0000_0001; const DISCONNECTED = 0b0000_0010; } } @@ -37,7 +36,7 @@ pub(crate) struct Http2 where T: AsyncRead + AsyncWrite + 'static, H: 'static { flags: Flags, - settings: ServerSettings, + handlers: Rc>, addr: Option, state: State>, tasks: VecDeque, @@ -54,10 +53,10 @@ impl Http2 where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static { - pub fn new(settings: ServerSettings, stream: T, addr: Option, buf: Bytes) -> Self + pub fn new(h: Rc>, stream: T, addr: Option, buf: Bytes) -> Self { - Http2{ flags: if settings.secure() { Flags::SECURE } else { Flags::empty() }, - settings: settings, + Http2{ flags: Flags::empty(), + handlers: h, addr: addr, tasks: VecDeque::new(), state: State::Handshake( @@ -152,7 +151,7 @@ impl Http2 self.keepalive_timer.take(); self.tasks.push_back( - Entry::new(parts, body, resp, self.addr, &self.settings)); + Entry::new(parts, body, resp, self.addr, &self.handlers)); } Ok(Async::NotReady) => { // start keep-alive timer @@ -231,7 +230,7 @@ impl Entry { recv: RecvStream, resp: Respond, addr: Option, - settings: &ServerSettings) -> Entry + handlers: &Rc>) -> Entry where H: HttpHandler + 'static { // Payload and Content-Encoding @@ -248,7 +247,7 @@ impl Entry { // start request processing let mut task = None; - for h in settings.handlers() { + for h in handlers.iter() { req = match h.handle(req) { Ok(t) => { task = Some(t); diff --git a/src/httprequest.rs b/src/httprequest.rs index 44e13f875..d6d0f43e4 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -142,7 +142,8 @@ impl HttpRequest { &self.0.headers } - #[cfg(test)] + #[doc(hidden)] + #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { &mut self.as_mut().headers } diff --git a/src/server.rs b/src/server.rs index 674aca697..8b8656182 100644 --- a/src/server.rs +++ b/src/server.rs @@ -5,6 +5,8 @@ use std::marker::PhantomData; use actix::dev::*; use futures::Stream; +use http::HttpTryFrom; +use http::header::HeaderValue; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_core::net::{TcpListener, TcpStream}; @@ -27,49 +29,41 @@ use tokio_openssl::{SslStream, SslAcceptorExt}; use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; /// Various server settings -pub struct ServerSettings (Rc>); - -struct InnerServerSettings { - h: Vec, +#[derive(Debug, Clone)] +pub struct ServerSettings { addr: Option, secure: bool, - sethost: bool, + host: Option, } -impl Clone for ServerSettings { - fn clone(&self) -> Self { - ServerSettings(Rc::clone(&self.0)) - } -} - -impl ServerSettings { +impl ServerSettings { /// Crate server settings instance - fn new(h: Vec, addr: Option, secure: bool, sethost: bool) -> Self { - ServerSettings( - Rc::new(InnerServerSettings { - h: h, - addr: addr, - secure: secure, - sethost: sethost })) + fn new(addr: Option, secure: bool) -> Self { + let host = if let Some(ref addr) = addr { + HeaderValue::try_from(format!("{}", addr).as_str()).ok() + } else { + None + }; + ServerSettings { + addr: addr, + secure: secure, + host: host, + } } - /// Returns list of http handlers - pub fn handlers(&self) -> &Vec { - &self.0.h - } /// Returns the socket address of the local half of this TCP connection pub fn local_addr(&self) -> Option { - self.0.addr + self.addr } /// Returns true if connection is secure(https) pub fn secure(&self) -> bool { - self.0.secure + self.secure } - /// Should http channel set *HOST* header - pub fn set_host_header(&self) -> bool { - self.0.sethost + /// Returns host header value + pub fn host(&self) -> Option<&HeaderValue> { + self.host.as_ref() } } @@ -81,10 +75,9 @@ impl ServerSettings { /// /// `H` - request handler pub struct HttpServer { - h: Option>, + h: Rc>, io: PhantomData, addr: PhantomData, - sethost: bool, } impl Actor for HttpServer { @@ -99,16 +92,9 @@ impl HttpServer where H: HttpHandler { let apps: Vec<_> = handler.into_iter().map(|h| h.into_handler()).collect(); - HttpServer {h: Some(apps), + HttpServer{ h: Rc::new(apps), io: PhantomData, - addr: PhantomData, - sethost: false} - } - - /// Set *HOST* header if not set - pub fn set_host_header(mut self) -> Self { - self.sethost = true; - self + addr: PhantomData } } } @@ -122,14 +108,17 @@ impl HttpServer where Self: ActorAddress, S: Stream + 'static { + // set server settings let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); - let settings = ServerSettings::new( - self.h.take().unwrap(), Some(addr), secure, self.sethost); + let settings = ServerSettings::new(Some(addr), secure); + for h in Rc::get_mut(&mut self.h).unwrap().iter_mut() { + h.server_settings(settings.clone()); + } + // start server Ok(HttpServer::create(move |ctx| { ctx.add_stream(stream.map( - move |(t, _)| IoStream{settings: settings.clone(), - io: t, peer: None, http2: false})); + move |(t, _)| IoStream{io: t, peer: None, http2: false})); self })) } @@ -170,16 +159,20 @@ impl HttpServer { S: net::ToSocketAddrs, { let addrs = self.bind(addr)?; - let settings = ServerSettings::new( - self.h.take().unwrap(), Some(addrs[0].0), false, self.sethost); + // set server settings + let settings = ServerSettings::new(Some(addrs[0].0), false); + for h in Rc::get_mut(&mut self.h).unwrap().iter_mut() { + h.server_settings(settings.clone()); + } + + // start server Ok(HttpServer::create(move |ctx| { for (addr, tcp) in addrs { info!("Starting http server on {}", addr); - let s = settings.clone(); + ctx.add_stream(tcp.incoming().map( - move |(t, a)| IoStream{settings: s.clone(), - io: t, peer: Some(a), http2: false})); + move |(t, a)| IoStream{io: t, peer: Some(a), http2: false})); } self })) @@ -198,8 +191,12 @@ impl HttpServer, net::SocketAddr, H> { S: net::ToSocketAddrs, { let addrs = self.bind(addr)?; - let settings = ServerSettings::new( - self.h.take().unwrap(), Some(addrs[0].0.clone()), true, self.sethost); + + // set server settings + let settings = ServerSettings::new(Some(addrs[0].0), true); + for h in Rc::get_mut(&mut self.h).unwrap().iter_mut() { + h.server_settings(settings.clone()); + } let acceptor = match TlsAcceptor::builder(pkcs12) { Ok(builder) => { @@ -211,18 +208,15 @@ impl HttpServer, net::SocketAddr, H> { Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err)) }; + // start server Ok(HttpServer::create(move |ctx| { for (srv, tcp) in addrs { info!("Starting tls http server on {}", srv); - let st = settings.clone(); let acc = acceptor.clone(); ctx.add_stream(tcp.incoming().and_then(move |(stream, addr)| { - let st2 = st.clone(); TlsAcceptorExt::accept_async(acc.as_ref(), stream) - .map(move |t| - IoStream{settings: st2.clone(), - io: t, peer: Some(addr), http2: false}) + .map(move |t| IoStream{io: t, peer: Some(addr), http2: false}) .map_err(|err| { trace!("Error during handling tls connection: {}", err); io::Error::new(io::ErrorKind::Other, err) @@ -246,8 +240,12 @@ impl HttpServer, net::SocketAddr, H> { S: net::ToSocketAddrs, { let addrs = self.bind(addr)?; - let settings = ServerSettings::new( - self.h.take().unwrap(), Some(addrs[0].0.clone()), true, self.sethost); + + // set server settings + let settings = ServerSettings::new(Some(addrs[0].0), true); + for h in Rc::get_mut(&mut self.h).unwrap().iter_mut() { + h.server_settings(settings.clone()); + } let acceptor = match SslAcceptorBuilder::mozilla_intermediate( SslMethod::tls(), &identity.pkey, &identity.cert, &identity.chain) @@ -265,10 +263,8 @@ impl HttpServer, net::SocketAddr, H> { for (srv, tcp) in addrs { info!("Starting tls http server on {}", srv); - let st = settings.clone(); let acc = acceptor.clone(); ctx.add_stream(tcp.incoming().and_then(move |(stream, addr)| { - let st2 = st.clone(); SslAcceptorExt::accept_async(&acc, stream) .map(move |stream| { let http2 = if let Some(p) = @@ -278,8 +274,7 @@ impl HttpServer, net::SocketAddr, H> { } else { false }; - IoStream{settings: st2.clone(), - io: stream, peer: Some(addr), http2: http2} + IoStream{io: stream, peer: Some(addr), http2: http2} }) .map_err(|err| { trace!("Error during handling tls connection: {}", err); @@ -292,26 +287,25 @@ impl HttpServer, net::SocketAddr, H> { } } -struct IoStream { +struct IoStream { io: T, peer: Option, http2: bool, - settings: ServerSettings, } -impl ResponseType for IoStream +impl ResponseType for IoStream where T: AsyncRead + AsyncWrite + 'static { type Item = (); type Error = (); } -impl StreamHandler, io::Error> for HttpServer +impl StreamHandler, io::Error> for HttpServer where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static, A: 'static {} -impl Handler, io::Error> for HttpServer +impl Handler, io::Error> for HttpServer where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static, A: 'static, @@ -320,11 +314,11 @@ impl Handler, io::Error> for HttpServer debug!("Error handling request: {}", err) } - fn handle(&mut self, msg: IoStream, _: &mut Context) - -> Response> + fn handle(&mut self, msg: IoStream, _: &mut Context) + -> Response> { Arbiter::handle().spawn( - HttpChannel::new(msg.settings, msg.io, msg.peer, msg.http2)); + HttpChannel::new(Rc::clone(&self.h), msg.io, msg.peer, msg.http2)); Self::empty() } } From 774bfc0a86bbd8f303e7e05cea0104bce7aa59ed Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 8 Dec 2017 09:48:53 -0800 Subject: [PATCH 057/279] use server settings for scheme and host values --- src/application.rs | 5 +++++ src/info.rs | 14 +++++++++++++- src/router.rs | 15 ++++++++++++++- src/server.rs | 22 +++++++++++++++------- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/application.rs b/src/application.rs index 0c2b376c3..899cd453b 100644 --- a/src/application.rs +++ b/src/application.rs @@ -8,6 +8,7 @@ use httprequest::HttpRequest; use channel::{HttpHandler, IntoHttpHandler}; use pipeline::Pipeline; use middlewares::Middleware; +use server::ServerSettings; /// Application pub struct HttpApplication { @@ -41,6 +42,10 @@ impl HttpHandler for HttpApplication { Err(req) } } + + fn server_settings(&mut self, settings: ServerSettings) { + self.router.set_server_settings(settings); + } } struct ApplicationParts { diff --git a/src/info.rs b/src/info.rs index a02ffc6ad..a5a01c539 100644 --- a/src/info.rs +++ b/src/info.rs @@ -64,6 +64,13 @@ impl<'a> ConnectionInfo<'a> { } if scheme.is_none() { scheme = req.uri().scheme_part().map(|a| a.as_str()); + if scheme.is_none() { + if let Some(ref router) = req.router() { + if router.server_settings().secure() { + scheme = Some("https") + } + } + } } } @@ -79,7 +86,12 @@ impl<'a> ConnectionInfo<'a> { host = h.to_str().ok(); } if host.is_none() { - host = req.uri().authority_part().map(|a| a.as_str()) + host = req.uri().authority_part().map(|a| a.as_str()); + if host.is_none() { + if let Some(ref router) = req.router() { + host = Some(router.server_settings().host()); + } + } } } } diff --git a/src/router.rs b/src/router.rs index 3d03e11a0..b6e1313dc 100644 --- a/src/router.rs +++ b/src/router.rs @@ -8,6 +8,7 @@ use regex::{Regex, RegexSet}; use error::UrlGenerationError; use resource::Resource; use httprequest::HttpRequest; +use server::ServerSettings; /// Interface for application router. @@ -19,6 +20,7 @@ struct Inner { named: HashMap, patterns: Vec, resources: Vec>, + srv: ServerSettings, } impl Router { @@ -49,7 +51,12 @@ impl Router { regset: RegexSet::new(&paths).unwrap(), named: named, patterns: patterns, - resources: resources })) + resources: resources, + srv: ServerSettings::default() })) + } + + pub(crate) fn set_server_settings(&mut self, settings: ServerSettings) { + Rc::get_mut(&mut self.0).unwrap().srv = settings; } /// Router prefix @@ -58,6 +65,12 @@ impl Router { &self.0.prefix } + /// Server settings + #[inline] + pub fn server_settings(&self) -> &ServerSettings { + &self.0.srv + } + /// Query for matched resource pub fn recognize(&self, req: &mut HttpRequest) -> Option<&Resource> { let mut idx = None; diff --git a/src/server.rs b/src/server.rs index 8b8656182..98e602428 100644 --- a/src/server.rs +++ b/src/server.rs @@ -5,8 +5,6 @@ use std::marker::PhantomData; use actix::dev::*; use futures::Stream; -use http::HttpTryFrom; -use http::header::HeaderValue; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_core::net::{TcpListener, TcpStream}; @@ -33,16 +31,26 @@ use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; pub struct ServerSettings { addr: Option, secure: bool, - host: Option, + host: String, +} + +impl Default for ServerSettings { + fn default() -> Self { + ServerSettings { + addr: None, + secure: false, + host: "localhost:8080".to_owned(), + } + } } impl ServerSettings { /// Crate server settings instance fn new(addr: Option, secure: bool) -> Self { let host = if let Some(ref addr) = addr { - HeaderValue::try_from(format!("{}", addr).as_str()).ok() + format!("{}", addr) } else { - None + "unknown".to_owned() }; ServerSettings { addr: addr, @@ -62,8 +70,8 @@ impl ServerSettings { } /// Returns host header value - pub fn host(&self) -> Option<&HeaderValue> { - self.host.as_ref() + pub fn host(&self) -> &str { + &self.host } } From 3e91b062412413df5ecf9fa19388949697057f4e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 8 Dec 2017 12:29:28 -0800 Subject: [PATCH 058/279] fix static files --- README.md | 12 +++++++----- examples/basic.rs | 3 ++- examples/websocket.rs | 3 ++- guide/src/qs_12.md | 9 ++++++--- src/fs.rs | 24 ++++++++++++++++++------ 5 files changed, 35 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 9502c6501..2f0a8355e 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,16 @@ Actix web is a small, fast, down-to-earth, open source rust web framework. ```rust,ignore use actix_web::*; -fn index(req: HttpRequest) -> String { - format!("Hello {}!", &req.match_info()["name"]) +fn index(req: HttpRequest) -> String +{ + format!("Hello {}!", + &req.match_info()["name"]) } fn main() { - HttpServer::new( - Application::new("/") - .resource("/{name}", |r| r.method(Method::GET).f(index))) + HttpServer::new(Application::new("/") + .resource("/{name}", + |r| r.method(Method::GET).f(index))) .serve::<_, ()>("127.0.0.1:8080"); } ``` diff --git a/examples/basic.rs b/examples/basic.rs index e6fe48227..c77d4adf6 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -80,7 +80,8 @@ fn main() { } })) // static files - .resource("/static", |r| r.h(fs::StaticFiles::new("examples/static/", true))) + .resource("/static/{tail:.*}", + |r| r.h(fs::StaticFiles::new("tail", "examples/static/", true))) // redirect .resource("/", |r| r.method(Method::GET).f(|req| { println!("{:?}", req); diff --git a/examples/websocket.rs b/examples/websocket.rs index 93b407464..124e4526e 100644 --- a/examples/websocket.rs +++ b/examples/websocket.rs @@ -67,7 +67,8 @@ fn main() { // websocket route .resource("/ws/", |r| r.method(Method::GET).f(ws_index)) // static files - .resource("/", |r| r.h(fs::StaticFiles::new("examples/static/", true)))) + .resource("/{tail:.*}", + |r| r.h(fs::StaticFiles::new("tail", "examples/static/", true)))) // start http server on 127.0.0.1:8080 .serve::<_, ()>("127.0.0.1:8080").unwrap(); diff --git a/guide/src/qs_12.md b/guide/src/qs_12.md index 7cabe7449..4d415baf5 100644 --- a/guide/src/qs_12.md +++ b/guide/src/qs_12.md @@ -25,7 +25,9 @@ fn main() { ## Directory To serve files from specific directory and sub-directories `StaticFiles` could be used. -`StaticFiles` could be registered with `Application::route` method. +`StaticFiles` could be registered with `Application::resource` method. +`StaticFiles` requires tail named path expression for resource registration. +And this name has to be used in `StaticFile` constructor. ```rust # extern crate actix_web; @@ -33,11 +35,12 @@ use actix_web::*; fn main() { Application::new("/") - .resource("/static", |r| r.h(fs::StaticFiles::new(".", true))) + .resource("/static/{tail:.*}", |r| r.h(fs::StaticFiles::new("tail", ".", true))) .finish(); } ``` -First parameter is a base directory. Second parameter is *show_index*, if it is set to *true* +First parameter is a name of path pattern. Second parameter is a base directory. +Third parameter is *show_index*, if it is set to *true* directory listing would be returned for directories, if it is set to *false* then *404 Not Found* would be returned instead of directory listing. diff --git a/src/fs.rs b/src/fs.rs index a5a015d1a..963cf2fc8 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -191,7 +191,8 @@ impl FromRequest for FilesystemElement { /// Static files handling /// -/// Can be registered with `Application::route_handler()`. +/// Can be registered with `Application::resource()`. Resource path has to contain +/// tail named pattern and this name has to be used in `StaticFile` constructor. /// /// ```rust /// # extern crate actix_web; @@ -199,11 +200,12 @@ impl FromRequest for FilesystemElement { /// /// fn main() { /// let app = Application::new("/") -/// .resource("/static", |r| r.h(fs::StaticFiles::new(".", true))) +/// .resource("/static/{tail:.*}", |r| r.h(fs::StaticFiles::new("tail", ".", true))) /// .finish(); /// } /// ``` pub struct StaticFiles { + name: String, directory: PathBuf, accessible: bool, show_index: bool, @@ -217,7 +219,7 @@ impl StaticFiles { /// `dir` - base directory /// /// `index` - show index for directory - pub fn new>(dir: D, index: bool) -> StaticFiles { + pub fn new>(name: &str, dir: D, index: bool) -> StaticFiles { let dir = dir.into(); let (dir, access) = match dir.canonicalize() { @@ -236,6 +238,7 @@ impl StaticFiles { }; StaticFiles { + name: name.to_owned(), directory: dir, accessible: access, show_index: index, @@ -253,7 +256,13 @@ impl Handler for StaticFiles { if !self.accessible { Err(io::Error::new(io::ErrorKind::NotFound, "not found")) } else { - let relpath = PathBuf::from_param(&req.path()[req.prefix_len()..]) + let path = if let Some(path) = req.match_info().get(&self.name) { + path + } else { + return Err(io::Error::new(io::ErrorKind::NotFound, "not found")) + }; + + let relpath = PathBuf::from_param(path) .map_err(|_| io::Error::new(io::ErrorKind::NotFound, "not found"))?; // full filepath @@ -291,7 +300,7 @@ mod tests { #[test] fn test_static_files() { - let mut st = StaticFiles::new(".", true); + let mut st = StaticFiles::new("tail", ".", true); st.accessible = false; assert!(st.handle(HttpRequest::default()).is_err()); @@ -299,8 +308,11 @@ mod tests { st.show_index = false; assert!(st.handle(HttpRequest::default()).is_err()); + let mut req = HttpRequest::default(); + req.match_info_mut().add("tail", ""); + st.show_index = true; - let resp = st.handle(HttpRequest::default()).from_request(HttpRequest::default()).unwrap(); + let resp = st.handle(req).from_request(HttpRequest::default()).unwrap(); assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/html; charset=utf-8"); assert!(resp.body().is_binary()); assert!(format!("{:?}", resp.body()).contains("README.md")); From 9043e7286d2a6a3b3a122b1b6ba06e96d397b8a5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 8 Dec 2017 12:51:44 -0800 Subject: [PATCH 059/279] tests for default predicates --- README.md | 12 +++---- src/info.rs | 7 ++-- src/pred.rs | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 2f0a8355e..9502c6501 100644 --- a/README.md +++ b/README.md @@ -5,16 +5,14 @@ Actix web is a small, fast, down-to-earth, open source rust web framework. ```rust,ignore use actix_web::*; -fn index(req: HttpRequest) -> String -{ - format!("Hello {}!", - &req.match_info()["name"]) +fn index(req: HttpRequest) -> String { + format!("Hello {}!", &req.match_info()["name"]) } fn main() { - HttpServer::new(Application::new("/") - .resource("/{name}", - |r| r.method(Method::GET).f(index))) + HttpServer::new( + Application::new("/") + .resource("/{name}", |r| r.method(Method::GET).f(index))) .serve::<_, ()>("127.0.0.1:8080"); } ``` diff --git a/src/info.rs b/src/info.rs index a5a01c539..190ce0c5c 100644 --- a/src/info.rs +++ b/src/info.rs @@ -65,7 +65,7 @@ impl<'a> ConnectionInfo<'a> { if scheme.is_none() { scheme = req.uri().scheme_part().map(|a| a.as_str()); if scheme.is_none() { - if let Some(ref router) = req.router() { + if let Some(router) = req.router() { if router.server_settings().secure() { scheme = Some("https") } @@ -88,7 +88,7 @@ impl<'a> ConnectionInfo<'a> { if host.is_none() { host = req.uri().authority_part().map(|a| a.as_str()); if host.is_none() { - if let Some(ref router) = req.router() { + if let Some(router) = req.router() { host = Some(router.server_settings().host()); } } @@ -104,8 +104,7 @@ impl<'a> ConnectionInfo<'a> { remote = h.split(',').next().map(|v| v.trim()); } } - if remote.is_none() { - // get peeraddr from socketaddr + if remote.is_none() { // get peeraddr from socketaddr peer = req.peer_addr().map(|addr| format!("{}", addr)); } } diff --git a/src/pred.rs b/src/pred.rs index 2eebd040d..b760af280 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -146,3 +146,101 @@ impl Predicate for HeaderPredicate { false } } + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + use http::{Uri, Version, Method}; + use http::header::{self, HeaderMap}; + use payload::Payload; + + #[test] + fn test_header() { + let mut headers = HeaderMap::new(); + headers.insert(header::TRANSFER_ENCODING, + header::HeaderValue::from_static("chunked")); + let mut req = HttpRequest::new( + Method::GET, Uri::from_str("/").unwrap(), + Version::HTTP_11, headers, Payload::empty()); + + let pred = Header("transfer-encoding", "chunked"); + assert!(pred.check(&mut req)); + + let pred = Header("transfer-encoding", "other"); + assert!(!pred.check(&mut req)); + } + + #[test] + fn test_methods() { + let mut req = HttpRequest::new( + Method::GET, Uri::from_str("/").unwrap(), + Version::HTTP_11, HeaderMap::new(), Payload::empty()); + let mut req2 = HttpRequest::new( + Method::POST, Uri::from_str("/").unwrap(), + Version::HTTP_11, HeaderMap::new(), Payload::empty()); + + assert!(Get().check(&mut req)); + assert!(!Get().check(&mut req2)); + assert!(Post().check(&mut req2)); + assert!(!Post().check(&mut req)); + + let mut r = HttpRequest::new( + Method::PUT, Uri::from_str("/").unwrap(), + Version::HTTP_11, HeaderMap::new(), Payload::empty()); + assert!(Put().check(&mut r)); + assert!(!Put().check(&mut req)); + + let mut r = HttpRequest::new( + Method::DELETE, Uri::from_str("/").unwrap(), + Version::HTTP_11, HeaderMap::new(), Payload::empty()); + assert!(Delete().check(&mut r)); + assert!(!Delete().check(&mut req)); + + let mut r = HttpRequest::new( + Method::HEAD, Uri::from_str("/").unwrap(), + Version::HTTP_11, HeaderMap::new(), Payload::empty()); + assert!(Head().check(&mut r)); + assert!(!Head().check(&mut req)); + + let mut r = HttpRequest::new( + Method::OPTIONS, Uri::from_str("/").unwrap(), + Version::HTTP_11, HeaderMap::new(), Payload::empty()); + assert!(Options().check(&mut r)); + assert!(!Options().check(&mut req)); + + let mut r = HttpRequest::new( + Method::CONNECT, Uri::from_str("/").unwrap(), + Version::HTTP_11, HeaderMap::new(), Payload::empty()); + assert!(Connect().check(&mut r)); + assert!(!Connect().check(&mut req)); + + let mut r = HttpRequest::new( + Method::PATCH, Uri::from_str("/").unwrap(), + Version::HTTP_11, HeaderMap::new(), Payload::empty()); + assert!(Patch().check(&mut r)); + assert!(!Patch().check(&mut req)); + + let mut r = HttpRequest::new( + Method::TRACE, Uri::from_str("/").unwrap(), + Version::HTTP_11, HeaderMap::new(), Payload::empty()); + assert!(Trace().check(&mut r)); + assert!(!Trace().check(&mut req)); + } + + #[test] + fn test_preds() { + let mut r = HttpRequest::new( + Method::TRACE, Uri::from_str("/").unwrap(), + Version::HTTP_11, HeaderMap::new(), Payload::empty()); + + assert!(Not(Get()).check(&mut r)); + assert!(!Not(Trace()).check(&mut r)); + + assert!(All(vec![Trace(), Trace()]).check(&mut r)); + assert!(!All(vec![Get(), Trace()]).check(&mut r)); + + assert!(Any(vec![Get(), Trace()]).check(&mut r)); + assert!(!Any(vec![Get(), Get()]).check(&mut r)); + } +} From a44f71d8c27b88e57a9d4573b657c16009ca8a9a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 8 Dec 2017 15:25:37 -0800 Subject: [PATCH 060/279] make ErrorBadRequest type useful --- build.rs | 1 + guide/src/SUMMARY.md | 1 + guide/src/qs_2.md | 4 +- guide/src/qs_4.md | 36 ++++++------ guide/src/qs_4_5.md | 135 +++++++++++++++++++++++++++++++++++++++++++ src/error.rs | 44 ++++++++++++++ src/lib.rs | 2 +- src/param.rs | 30 +--------- src/pred.rs | 3 + 9 files changed, 208 insertions(+), 48 deletions(-) create mode 100644 guide/src/qs_4_5.md diff --git a/build.rs b/build.rs index 6a8a3bd01..3b916a95f 100644 --- a/build.rs +++ b/build.rs @@ -16,6 +16,7 @@ fn main() { "guide/src/qs_2.md", "guide/src/qs_3.md", "guide/src/qs_4.md", + "guide/src/qs_4_5.md", "guide/src/qs_5.md", "guide/src/qs_6.md", "guide/src/qs_7.md", diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 85332cf43..17ce202e9 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -4,6 +4,7 @@ - [Getting Started](./qs_2.md) - [Application](./qs_3.md) - [Handler](./qs_4.md) +- [Errors](./qs_4_5.md) - [State](./qs_6.md) - [Resources and Routes](./qs_5.md) - [Request & Response](./qs_7.md) diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md index f8187c70d..cd32a87be 100644 --- a/guide/src/qs_2.md +++ b/guide/src/qs_2.md @@ -84,9 +84,7 @@ fn main() { .serve::<_, ()>("127.0.0.1:8088").unwrap(); println!("Started http server: 127.0.0.1:8088"); - // do not copy this line - actix::Arbiter::system().send(actix::msgs::SystemExit(0)); - +# actix::Arbiter::system().send(actix::msgs::SystemExit(0)); let _ = sys.run(); } ``` diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index ae5ae19c2..acab60d71 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -1,21 +1,19 @@ # Handler A request handler can by any object that implements -[`Handler` trait](../actix_web/struct.HttpResponse.html#implementations). +[`Handler` trait](../actix_web/dev/trait.Handler.html#implementors). +Request handling happen in two stages. First handler object get called. +Handle can return any object that implements +[`FromRequest` trait](../actix_web/trait.FromRequest.html#foreign-impls). +Then `from_request()` get called on returned object. And finally +result of the `from_request()` call get converted to `Reply` object. -By default actix provdes several `Handler` implementations: - -* Simple function that accepts `HttpRequest` and returns any object that - implements `FromRequest` trait -* Function that accepts `HttpRequest` and returns `Result>` object. -* Function that accepts `HttpRequest` and return actor that has `HttpContext`as a context. - -Actix provides response `FromRequest` implementation for some standard types, +By default actix provides several `FromRequest` implementations for some standard types, like `&'static str`, `String`, etc. For complete list of implementations check [FromRequest documentation](../actix_web/trait.FromRequest.html#foreign-impls). -Examples: +Examples of valid handlers: ```rust,ignore fn index(req: HttpRequest) -> &'static str { @@ -41,9 +39,11 @@ fn index(req: HttpRequest) -> Box> { } ``` -## Custom conversion +## Response with custom type -Let's create response for custom type that serializes to `application/json` response: +To return custom type directly from handler function `FromResponse` trait should be +implemented for this type. Let's create response for custom type that +serializes to `application/json` response: ```rust # extern crate actix; @@ -55,7 +55,7 @@ use actix_web::*; #[derive(Serialize)] struct MyObj { - name: String, + name: &'static str, } /// we have to convert Error into HttpResponse as well @@ -73,18 +73,20 @@ impl FromRequest for MyObj { } } +fn index(req: HttpRequest) -> MyObj { + MyObj{name: "user"} +} + fn main() { let sys = actix::System::new("example"); HttpServer::new( Application::new("/") - .resource("/", |r| r.method( - Method::GET).f(|req| {MyObj{name: "user".to_owned()}}))) + .resource("/", |r| r.method(Method::GET).f(index))) .serve::<_, ()>("127.0.0.1:8088").unwrap(); println!("Started http server: 127.0.0.1:8088"); - actix::Arbiter::system().send(actix::msgs::SystemExit(0)); // <- remove this line, this code stops system during testing - +# actix::Arbiter::system().send(actix::msgs::SystemExit(0)); let _ = sys.run(); } ``` diff --git a/guide/src/qs_4_5.md b/guide/src/qs_4_5.md new file mode 100644 index 000000000..ad8154366 --- /dev/null +++ b/guide/src/qs_4_5.md @@ -0,0 +1,135 @@ +# Errors + +Actix uses [`Error` type](../actix_web/error/struct.Error.html) +and [`ResponseError` trait](../actix_web/error/trait.ResponseError.html) +for handling handler's errors. +Any error that implements `ResponseError` trait can be returned as error value. +*Handler* can return *Result* object, actix by default provides +`FromRequest` implemenation for compatible result object. Here is implementation +definition: + +```rust,ignore +impl> FromRequest for Result +``` + +And any error that implements `ResponseError` can be converted into `Error` object. +For example if *handler* function returns `io::Error`, it would be converted +into `HTTPInternalServerError` response. Implementation for `io::Error` is provided +by default. + +```rust +# extern crate actix_web; +# use actix_web::*; +use std::io; + +fn index(req: HttpRequest) -> io::Result { + Ok(fs::NamedFile::open("static/index.html")?) +} +# +# fn main() { +# Application::new("/") +# .resource(r"/a/index.html", |r| r.f(index)) +# .finish(); +# } +``` + +## Custom error response + +To add support for custom errors all we need to do just implement `ResponseError` trait. +`ResponseError` trait has default implementation for `error_response()` method, it +generates *500* response. + +```rust +# extern crate actix_web; +#[macro_use] extern crate failure; +use actix_web::*; + +#[derive(Fail, Debug)] +#[fail(display="my error")] +struct MyError { + name: &'static str +} + +impl error::ResponseError for MyError {} + +fn index(req: HttpRequest) -> Result<&'static str, MyError> { + Err(MyError{name: "test"}) +} +# +# fn main() { +# Application::new("/") +# .resource(r"/a/index.html", |r| r.f(index)) +# .finish(); +# } +``` + +In this example *index* handler will always return *500* response. But it is easy +to return different responses. + +```rust +# extern crate actix_web; +#[macro_use] extern crate failure; +use actix_web::*; + +#[derive(Fail, Debug)] +enum MyError { + #[fail(display="internal error")] + InternalError, + #[fail(display="bad request")] + BadClientData, + #[fail(display="timeout")] + Timeout, +} + +impl error::ResponseError for MyError { + fn error_response(&self) -> HttpResponse { + match *self { + MyError::InternalError => HttpResponse::new( + StatusCode::INTERNAL_SERVER_ERROR, Body::Empty), + MyError::BadClientData => HttpResponse::new( + StatusCode::BAD_REQUEST, Body::Empty), + MyError::Timeout => HttpResponse::new( + StatusCode::GATEWAY_TIMEOUT, Body::Empty), + } + } +} + +fn index(req: HttpRequest) -> Result<&'static str, MyError> { + Err(MyError::BadClientData) +} +# +# fn main() { +# Application::new("/") +# .resource(r"/a/index.html", |r| r.f(index)) +# .finish(); +# } +``` + +## Error helpers + +Actix provides set of error helper types. It is possible to use them to generate +specific error response. We can use helper types for first example with custom error. + +```rust +# extern crate actix_web; +#[macro_use] extern crate failure; +use actix_web::*; + +#[derive(Debug)] +struct MyError { + name: &'static str +} + +fn index(req: HttpRequest) -> Result<&'static str> { + let result: Result<&'static str, MyError> = Err(MyError{name: "test"}); + + Ok(result.map_err(error::ErrorBadRequest)?) +} +# fn main() { +# Application::new("/") +# .resource(r"/a/index.html", |r| r.f(index)) +# .finish(); +# } +``` + +In this example *BAD REQUEST* response get generated for `MYError` error. diff --git a/src/error.rs b/src/error.rs index d2434b6db..a45b6b6f0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -426,6 +426,50 @@ impl From for UrlGenerationError { } } + +/// Helper type that can wrap any error and generate *BAD REQUEST* response. +/// +/// In following example any `io::Error` will be converted into "BAD REQUEST" response +/// as oposite to *INNTERNAL SERVER ERROR* which is defined by default. +/// +/// ```rust +/// # extern crate actix_web; +/// # use actix_web::*; +/// use actix_web::fs::NamedFile; +/// +/// fn index(req: HttpRequest) -> Result { +/// let f = NamedFile::open("test.txt").map_err(error::ErrorBadRequest)?; +/// Ok(f) +/// } +/// # fn main() {} +/// ``` +#[derive(Debug)] +pub struct ErrorBadRequest(pub T); + +unsafe impl Sync for ErrorBadRequest {} +unsafe impl Send for ErrorBadRequest {} + +impl ErrorBadRequest { + pub fn cause(&self) -> &T { + &self.0 + } +} + +impl Fail for ErrorBadRequest {} +impl fmt::Display for ErrorBadRequest { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "BadRequest({:?})", self.0) + } +} + +impl ResponseError for ErrorBadRequest + where T: Send + Sync + fmt::Debug + 'static, +{ + fn error_response(&self) -> HttpResponse { + HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) + } +} + #[cfg(test)] mod tests { use std::error::Error as StdError; diff --git a/src/lib.rs b/src/lib.rs index 78aa9c577..d1b9feb8e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,7 +81,7 @@ pub mod httpcodes; pub mod multipart; pub mod middlewares; pub mod pred; -pub use error::{Error, Result}; +pub use error::{Error, Result, ResponseError}; pub use body::{Body, Binary}; pub use application::Application; pub use httprequest::HttpRequest; diff --git a/src/param.rs b/src/param.rs index 3981a81bf..63c37f13b 100644 --- a/src/param.rs +++ b/src/param.rs @@ -2,14 +2,9 @@ use std; use std::ops::Index; use std::path::PathBuf; use std::str::FromStr; - -use failure::Fail; -use http::{StatusCode}; use smallvec::SmallVec; -use body::Body; -use httpresponse::HttpResponse; -use error::{ResponseError, UriSegmentError}; +use error::{ResponseError, UriSegmentError, ErrorBadRequest}; /// A trait to abstract the idea of creating a new instance of a type from a path parameter. @@ -132,32 +127,13 @@ impl FromParam for PathBuf { } } -#[derive(Fail, Debug)] -#[fail(display="Error")] -pub struct BadRequest(T); - -impl BadRequest { - pub fn cause(&self) -> &T { - &self.0 - } -} - -impl ResponseError for BadRequest - where T: Send + Sync + std::fmt::Debug +std::fmt::Display + 'static, -BadRequest: Fail -{ - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) - } -} - macro_rules! FROM_STR { ($type:ty) => { impl FromParam for $type { - type Err = BadRequest<<$type as FromStr>::Err>; + type Err = ErrorBadRequest<<$type as FromStr>::Err>; fn from_param(val: &str) -> Result { - <$type as FromStr>::from_str(val).map_err(BadRequest) + <$type as FromStr>::from_str(val).map_err(ErrorBadRequest) } } } diff --git a/src/pred.rs b/src/pred.rs index b760af280..b3fc6f882 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -169,6 +169,9 @@ mod tests { let pred = Header("transfer-encoding", "other"); assert!(!pred.check(&mut req)); + + let pred = Header("content-tye", "other"); + assert!(!pred.check(&mut req)); } #[test] From 4a40b026a4d7010816cdd177258743781547a6f4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 8 Dec 2017 15:52:46 -0800 Subject: [PATCH 061/279] more error wrappers --- src/error.rs | 95 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 20 deletions(-) diff --git a/src/error.rs b/src/error.rs index a45b6b6f0..33cb25bab 100644 --- a/src/error.rs +++ b/src/error.rs @@ -426,6 +426,34 @@ impl From for UrlGenerationError { } } +macro_rules! ERROR_WRAP { + ($type:ty, $status:expr) => { + unsafe impl Sync for $type {} + unsafe impl Send for $type {} + + impl $type { + pub fn cause(&self) -> &T { + &self.0 + } + } + + impl Fail for $type {} + impl fmt::Display for $type { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.0) + } + } + + impl ResponseError for $type + where T: Send + Sync + fmt::Debug + 'static, + { + fn error_response(&self) -> HttpResponse { + HttpResponse::new($status, Body::Empty) + } + } + + } +} /// Helper type that can wrap any error and generate *BAD REQUEST* response. /// @@ -445,30 +473,57 @@ impl From for UrlGenerationError { /// ``` #[derive(Debug)] pub struct ErrorBadRequest(pub T); +ERROR_WRAP!(ErrorBadRequest, StatusCode::BAD_REQUEST); -unsafe impl Sync for ErrorBadRequest {} -unsafe impl Send for ErrorBadRequest {} +#[derive(Debug)] +/// Helper type that can wrap any error and generate *UNAUTHORIZED* response. +pub struct ErrorUnauthorized(pub T); +ERROR_WRAP!(ErrorUnauthorized, StatusCode::UNAUTHORIZED); -impl ErrorBadRequest { - pub fn cause(&self) -> &T { - &self.0 - } -} +#[derive(Debug)] +/// Helper type that can wrap any error and generate *FORBIDDEN* response. +pub struct ErrorForbidden(pub T); +ERROR_WRAP!(ErrorForbidden, StatusCode::FORBIDDEN); -impl Fail for ErrorBadRequest {} -impl fmt::Display for ErrorBadRequest { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "BadRequest({:?})", self.0) - } -} +#[derive(Debug)] +/// Helper type that can wrap any error and generate *NOT FOUND* response. +pub struct ErrorNotFound(pub T); +ERROR_WRAP!(ErrorNotFound, StatusCode::NOT_FOUND); -impl ResponseError for ErrorBadRequest - where T: Send + Sync + fmt::Debug + 'static, -{ - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) - } -} +#[derive(Debug)] +/// Helper type that can wrap any error and generate *METHOD_NOT_ALLOWED* response. +pub struct ErrorMethodNotAllowed(pub T); +ERROR_WRAP!(ErrorMethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED); + +#[derive(Debug)] +/// Helper type that can wrap any error and generate *REQUEST_TIMEOUT* response. +pub struct ErrorRequestTimeout(pub T); +ERROR_WRAP!(ErrorRequestTimeout, StatusCode::REQUEST_TIMEOUT); + +#[derive(Debug)] +/// Helper type that can wrap any error and generate *CONFLICT* response. +pub struct ErrorConflict(pub T); +ERROR_WRAP!(ErrorConflict, StatusCode::CONFLICT); + +#[derive(Debug)] +/// Helper type that can wrap any error and generate *GONE* response. +pub struct ErrorGone(pub T); +ERROR_WRAP!(ErrorGone, StatusCode::GONE); + +#[derive(Debug)] +/// Helper type that can wrap any error and generate *PRECONDITION_FAILED* response. +pub struct ErrorPreconditionFailed(pub T); +ERROR_WRAP!(ErrorPreconditionFailed, StatusCode::PRECONDITION_FAILED); + +#[derive(Debug)] +/// Helper type that can wrap any error and generate *EXPECTATION_FAILED* response. +pub struct ErrorExpectationFailed(pub T); +ERROR_WRAP!(ErrorExpectationFailed, StatusCode::EXPECTATION_FAILED); + +#[derive(Debug)] +/// Helper type that can wrap any error and generate *INTERNAL_SERVER_ERROR* response. +pub struct ErrorInternalServerError(pub T); +ERROR_WRAP!(ErrorInternalServerError, StatusCode::INTERNAL_SERVER_ERROR); #[cfg(test)] mod tests { From b98ab2eebeab584769db6c2a1b679bc856429965 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Dec 2017 04:33:40 -0800 Subject: [PATCH 062/279] use trait instead of pipeline --- src/application.rs | 23 ++--- src/channel.rs | 19 +++- src/encoding.rs | 8 +- src/error.rs | 10 +-- src/h1.rs | 4 +- src/h1writer.rs | 12 +-- src/h2.rs | 4 +- src/h2writer.rs | 4 +- src/httprequest.rs | 48 +++++++--- src/middlewares/defaultheaders.rs | 4 +- src/middlewares/logger.rs | 16 ++-- src/middlewares/mod.rs | 8 +- src/middlewares/session.rs | 25 +++--- src/pipeline.rs | 141 ++++++++++++++++-------------- tests/test_server.rs | 8 +- 15 files changed, 189 insertions(+), 145 deletions(-) diff --git a/src/application.rs b/src/application.rs index 899cd453b..291b33d8a 100644 --- a/src/application.rs +++ b/src/application.rs @@ -5,7 +5,7 @@ use handler::{Reply, RouteHandler}; use router::{Router, Pattern}; use resource::Resource; use httprequest::HttpRequest; -use channel::{HttpHandler, IntoHttpHandler}; +use channel::{HttpHandler, IntoHttpHandler, HttpHandlerTask}; use pipeline::Pipeline; use middlewares::Middleware; use server::ServerSettings; @@ -16,14 +16,12 @@ pub struct HttpApplication { prefix: String, default: Resource, router: Router, - middlewares: Rc>>, + middlewares: Rc>>>, } impl HttpApplication { - fn run(&self, req: HttpRequest) -> Reply { - let mut req = req.with_state(Rc::clone(&self.state), self.router.clone()); - + fn run(&self, mut req: HttpRequest) -> Reply { if let Some(h) = self.router.recognize(&mut req) { h.handle(req) } else { @@ -34,10 +32,12 @@ impl HttpApplication { impl HttpHandler for HttpApplication { - fn handle(&self, req: HttpRequest) -> Result { + fn handle(&self, req: HttpRequest) -> Result, HttpRequest> { if req.path().starts_with(&self.prefix) { - Ok(Pipeline::new(req, Rc::clone(&self.middlewares), - &|req: HttpRequest| self.run(req))) + let req = req.with_state(Rc::clone(&self.state), self.router.clone()); + + Ok(Box::new(Pipeline::new(req, Rc::clone(&self.middlewares), + &|req: HttpRequest| self.run(req)))) } else { Err(req) } @@ -54,7 +54,7 @@ struct ApplicationParts { default: Resource, resources: HashMap>>, external: HashMap, - middlewares: Vec>, + middlewares: Vec>>, } /// Structure that follows the builder pattern for building `Application` structs. @@ -204,7 +204,7 @@ impl Application where S: 'static { /// Register a middleware pub fn middleware(&mut self, mw: T) -> &mut Self - where T: Middleware + 'static + where T: Middleware + 'static { self.parts.as_mut().expect("Use after finish") .middlewares.push(Box::new(mw)); @@ -322,7 +322,8 @@ mod tests { let app = Application::with_state("/", 10) .resource("/", |r| r.h(httpcodes::HTTPOk)) .finish(); + let req = HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone()); assert_eq!( - app.run(HttpRequest::default()).msg().unwrap().status(), StatusCode::OK); + app.run(req).msg().unwrap().status(), StatusCode::OK); } } diff --git a/src/channel.rs b/src/channel.rs index c649d8c46..bf7e24a96 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -8,20 +8,31 @@ use tokio_io::{AsyncRead, AsyncWrite}; use h1; use h2; -use pipeline::Pipeline; +use error::Error; +use h1writer::Writer; use httprequest::HttpRequest; use server::ServerSettings; /// Low level http request handler #[allow(unused_variables)] pub trait HttpHandler: 'static { + /// Handle request - fn handle(&self, req: HttpRequest) -> Result; + fn handle(&self, req: HttpRequest) -> Result, HttpRequest>; /// Set server settings fn server_settings(&mut self, settings: ServerSettings) {} } +pub trait HttpHandlerTask { + + fn poll_io(&mut self, io: &mut Writer) -> Poll; + + fn poll(&mut self) -> Poll<(), Error>; + + fn disconnected(&mut self); +} + /// Conversion helper trait pub trait IntoHttpHandler { /// The associated type which is result of conversion. @@ -40,7 +51,7 @@ impl IntoHttpHandler for T { } enum HttpProtocol - where T: AsyncRead + AsyncWrite + 'static, H: 'static + where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static { H1(h1::Http1), H2(h2::Http2), @@ -48,7 +59,7 @@ enum HttpProtocol #[doc(hidden)] pub struct HttpChannel - where T: AsyncRead + AsyncWrite + 'static, H: 'static + where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static { proto: Option>, } diff --git a/src/encoding.rs b/src/encoding.rs index 2768dfd18..1c8c88272 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -15,7 +15,7 @@ use bytes::{Bytes, BytesMut, BufMut, Writer}; use body::{Body, Binary}; use error::PayloadError; -use httprequest::HttpRequest; +use httprequest::HttpMessage; use httpresponse::HttpResponse; use payload::{PayloadSender, PayloadWriter}; @@ -336,8 +336,8 @@ impl Default for PayloadEncoder { impl PayloadEncoder { - pub fn new(req: &HttpRequest, resp: &mut HttpResponse) -> PayloadEncoder { - let version = resp.version().unwrap_or_else(|| req.version()); + pub fn new(req: &HttpMessage, resp: &mut HttpResponse) -> PayloadEncoder { + let version = resp.version().unwrap_or_else(|| req.version); let mut body = resp.replace_body(Body::Empty); let has_body = match body { Body::Empty => false, @@ -350,7 +350,7 @@ impl PayloadEncoder { let encoding = match *resp.content_encoding() { ContentEncoding::Auto => { // negotiate content-encoding - if let Some(val) = req.headers().get(ACCEPT_ENCODING) { + if let Some(val) = req.headers.get(ACCEPT_ENCODING) { if let Ok(enc) = val.to_str() { AcceptEncoding::parse(enc) } else { diff --git a/src/error.rs b/src/error.rs index 33cb25bab..e79b12939 100644 --- a/src/error.rs +++ b/src/error.rs @@ -491,12 +491,12 @@ pub struct ErrorNotFound(pub T); ERROR_WRAP!(ErrorNotFound, StatusCode::NOT_FOUND); #[derive(Debug)] -/// Helper type that can wrap any error and generate *METHOD_NOT_ALLOWED* response. +/// Helper type that can wrap any error and generate *METHOD NOT ALLOWED* response. pub struct ErrorMethodNotAllowed(pub T); ERROR_WRAP!(ErrorMethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED); #[derive(Debug)] -/// Helper type that can wrap any error and generate *REQUEST_TIMEOUT* response. +/// Helper type that can wrap any error and generate *REQUEST TIMEOUT* response. pub struct ErrorRequestTimeout(pub T); ERROR_WRAP!(ErrorRequestTimeout, StatusCode::REQUEST_TIMEOUT); @@ -511,17 +511,17 @@ pub struct ErrorGone(pub T); ERROR_WRAP!(ErrorGone, StatusCode::GONE); #[derive(Debug)] -/// Helper type that can wrap any error and generate *PRECONDITION_FAILED* response. +/// Helper type that can wrap any error and generate *PRECONDITION FAILED* response. pub struct ErrorPreconditionFailed(pub T); ERROR_WRAP!(ErrorPreconditionFailed, StatusCode::PRECONDITION_FAILED); #[derive(Debug)] -/// Helper type that can wrap any error and generate *EXPECTATION_FAILED* response. +/// Helper type that can wrap any error and generate *EXPECTATION FAILED* response. pub struct ErrorExpectationFailed(pub T); ERROR_WRAP!(ErrorExpectationFailed, StatusCode::EXPECTATION_FAILED); #[derive(Debug)] -/// Helper type that can wrap any error and generate *INTERNAL_SERVER_ERROR* response. +/// Helper type that can wrap any error and generate *INTERNAL SERVER ERROR* response. pub struct ErrorInternalServerError(pub T); ERROR_WRAP!(ErrorInternalServerError, StatusCode::INTERNAL_SERVER_ERROR); diff --git a/src/h1.rs b/src/h1.rs index 9b4587d87..5a46a3bb4 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -15,7 +15,7 @@ use tokio_core::reactor::Timeout; use pipeline::Pipeline; use encoding::PayloadType; -use channel::HttpHandler; +use channel::{HttpHandler, HttpHandlerTask}; use h1writer::H1Writer; use httpcodes::HTTPNotFound; use httprequest::HttpRequest; @@ -69,7 +69,7 @@ pub(crate) struct Http1 { } struct Entry { - pipe: Pipeline, + pipe: Box, flags: EntryFlags, } diff --git a/src/h1writer.rs b/src/h1writer.rs index 8776b4f4f..58b6da58c 100644 --- a/src/h1writer.rs +++ b/src/h1writer.rs @@ -8,7 +8,7 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_TYPE, DATE}; use date; use body::Body; use encoding::PayloadEncoder; -use httprequest::HttpRequest; +use httprequest::HttpMessage; use httpresponse::HttpResponse; const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific @@ -16,16 +16,16 @@ const MAX_WRITE_BUFFER_SIZE: usize = 65_536; // max buffer size 64k #[derive(Debug)] -pub(crate) enum WriterState { +pub enum WriterState { Done, Pause, } /// Send stream -pub(crate) trait Writer { +pub trait Writer { fn written(&self) -> u64; - fn start(&mut self, req: &mut HttpRequest, resp: &mut HttpResponse) + fn start(&mut self, req: &mut HttpMessage, resp: &mut HttpResponse) -> Result; fn write(&mut self, payload: &[u8]) -> Result; @@ -116,7 +116,7 @@ impl Writer for H1Writer { } } - fn start(&mut self, req: &mut HttpRequest, msg: &mut HttpResponse) + fn start(&mut self, req: &mut HttpMessage, msg: &mut HttpResponse) -> Result { trace!("Prepare response with status: {:?}", msg.status()); @@ -129,7 +129,7 @@ impl Writer for H1Writer { } // Connection upgrade - let version = msg.version().unwrap_or_else(|| req.version()); + let version = msg.version().unwrap_or_else(|| req.version); if msg.upgrade() { msg.headers_mut().insert(CONNECTION, HeaderValue::from_static("upgrade")); } diff --git a/src/h2.rs b/src/h2.rs index 264fb4629..d3f357aef 100644 --- a/src/h2.rs +++ b/src/h2.rs @@ -16,7 +16,7 @@ use tokio_core::reactor::Timeout; use pipeline::Pipeline; use h2writer::H2Writer; -use channel::HttpHandler; +use channel::{HttpHandler, HttpHandlerTask}; use error::PayloadError; use encoding::PayloadType; use httpcodes::HTTPNotFound; @@ -217,7 +217,7 @@ bitflags! { } struct Entry { - task: Pipeline, + task: Box, payload: PayloadType, recv: RecvStream, stream: H2Writer, diff --git a/src/h2writer.rs b/src/h2writer.rs index 062c69e4e..f2c07d651 100644 --- a/src/h2writer.rs +++ b/src/h2writer.rs @@ -9,7 +9,7 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_TYPE, TRANSFER_ENCODING, DAT use date; use body::Body; use encoding::PayloadEncoder; -use httprequest::HttpRequest; +use httprequest::HttpMessage; use httpresponse::HttpResponse; use h1writer::{Writer, WriterState}; @@ -108,7 +108,7 @@ impl Writer for H2Writer { self.written } - fn start(&mut self, req: &mut HttpRequest, msg: &mut HttpResponse) + fn start(&mut self, req: &mut HttpMessage, msg: &mut HttpResponse) -> Result { trace!("Prepare response with status: {:?}", msg.status()); diff --git a/src/httprequest.rs b/src/httprequest.rs index d6d0f43e4..092299594 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -19,18 +19,17 @@ use error::{ParseError, PayloadError, UrlGenerationError, MultipartError, CookieParseError, HttpRangeError, UrlencodedError}; -struct HttpMessage { - version: Version, - method: Method, - uri: Uri, - headers: HeaderMap, - extensions: Extensions, - params: Params<'static>, - cookies: Option>>, - addr: Option, - payload: Payload, - info: Option>, - +pub struct HttpMessage { + pub version: Version, + pub method: Method, + pub uri: Uri, + pub headers: HeaderMap, + pub extensions: Extensions, + pub params: Params<'static>, + pub cookies: Option>>, + pub addr: Option, + pub payload: Payload, + pub info: Option>, } impl Default for HttpMessage { @@ -51,6 +50,27 @@ impl Default for HttpMessage { } } +impl HttpMessage { + + /// Checks if a connection should be kept alive. + pub fn keep_alive(&self) -> bool { + if let Some(conn) = self.headers.get(header::CONNECTION) { + if let Ok(conn) = conn.to_str() { + if self.version == Version::HTTP_10 && conn.contains("keep-alive") { + true + } else { + self.version == Version::HTTP_11 && + !(conn.contains("close") || conn.contains("upgrade")) + } + } else { + false + } + } else { + self.version != Version::HTTP_10 + } + } +} + /// An HTTP Request pub struct HttpRequest(Rc, Rc, Option>); @@ -101,6 +121,10 @@ impl HttpRequest { unsafe{mem::transmute(r)} } + pub(crate) fn get_inner(&mut self) -> &mut HttpMessage { + self.as_mut() + } + /// Shared application state #[inline] pub fn state(&self) -> &S { diff --git a/src/middlewares/defaultheaders.rs b/src/middlewares/defaultheaders.rs index 5f4dd8bcd..e1a797a23 100644 --- a/src/middlewares/defaultheaders.rs +++ b/src/middlewares/defaultheaders.rs @@ -35,9 +35,9 @@ impl DefaultHeaders { } } -impl Middleware for DefaultHeaders { +impl Middleware for DefaultHeaders { - fn response(&self, _: &mut HttpRequest, mut resp: HttpResponse) -> Response { + fn response(&self, _: &mut HttpRequest, mut resp: HttpResponse) -> Response { for (key, value) in self.0.iter() { if !resp.headers().contains_key(key) { resp.headers_mut().insert(key, value.clone()); diff --git a/src/middlewares/logger.rs b/src/middlewares/logger.rs index 5e443fce6..67e5c3ffa 100644 --- a/src/middlewares/logger.rs +++ b/src/middlewares/logger.rs @@ -86,7 +86,7 @@ struct StartTime(time::Tm); impl Logger { - fn log(&self, req: &mut HttpRequest, resp: &HttpResponse) { + fn log(&self, req: &mut HttpRequest, resp: &HttpResponse) { let entry_time = req.extensions().get::().unwrap().0; let render = |fmt: &mut Formatter| { @@ -99,14 +99,14 @@ impl Logger { } } -impl Middleware for Logger { +impl Middleware for Logger { - fn start(&self, req: &mut HttpRequest) -> Started { + fn start(&self, req: &mut HttpRequest) -> Started { req.extensions().insert(StartTime(time::now())); Started::Done } - fn finish(&self, req: &mut HttpRequest, resp: &HttpResponse) -> Finished { + fn finish(&self, req: &mut HttpRequest, resp: &HttpResponse) -> Finished { self.log(req, resp); Finished::Done } @@ -199,10 +199,10 @@ pub enum FormatText { impl FormatText { - fn render(&self, fmt: &mut Formatter, - req: &HttpRequest, - resp: &HttpResponse, - entry_time: time::Tm) -> Result<(), fmt::Error> + fn render(&self, fmt: &mut Formatter, + req: &HttpRequest, + resp: &HttpResponse, + entry_time: time::Tm) -> Result<(), fmt::Error> { match *self { FormatText::Str(ref string) => fmt.write_str(string), diff --git a/src/middlewares/mod.rs b/src/middlewares/mod.rs index ebe405319..b9798c97b 100644 --- a/src/middlewares/mod.rs +++ b/src/middlewares/mod.rs @@ -46,22 +46,22 @@ pub enum Finished { /// Middleware definition #[allow(unused_variables)] -pub trait Middleware { +pub trait Middleware { /// Method is called when request is ready. It may return /// future, which should resolve before next middleware get called. - fn start(&self, req: &mut HttpRequest) -> Started { + fn start(&self, req: &mut HttpRequest) -> Started { Started::Done } /// Method is called when handler returns response, /// but before sending http message to peer. - fn response(&self, req: &mut HttpRequest, resp: HttpResponse) -> Response { + fn response(&self, req: &mut HttpRequest, resp: HttpResponse) -> Response { Response::Done(resp) } /// Method is called after body stream get sent to peer. - fn finish(&self, req: &mut HttpRequest, resp: &HttpResponse) -> Finished { + fn finish(&self, req: &mut HttpRequest, resp: &HttpResponse) -> Finished { Finished::Done } } diff --git a/src/middlewares/session.rs b/src/middlewares/session.rs index f962171a7..a807b0c03 100644 --- a/src/middlewares/session.rs +++ b/src/middlewares/session.rs @@ -3,6 +3,7 @@ use std::any::Any; use std::rc::Rc; use std::sync::Arc; +use std::marker::PhantomData; use std::collections::HashMap; use serde_json; @@ -79,18 +80,18 @@ unsafe impl Send for SessionImplBox {} unsafe impl Sync for SessionImplBox {} /// Session storage middleware -pub struct SessionStorage(T); +pub struct SessionStorage(T, PhantomData); -impl SessionStorage { +impl> SessionStorage { /// Create session storage - pub fn new(backend: T) -> SessionStorage { - SessionStorage(backend) + pub fn new(backend: T) -> SessionStorage { + SessionStorage(backend, PhantomData) } } -impl Middleware for SessionStorage { +impl> Middleware for SessionStorage { - fn start(&self, req: &mut HttpRequest) -> Started { + fn start(&self, req: &mut HttpRequest) -> Started { let mut req = req.clone(); let fut = self.0.from_request(&mut req) @@ -106,7 +107,7 @@ impl Middleware for SessionStorage { Started::Future(Box::new(fut)) } - fn response(&self, req: &mut HttpRequest, resp: HttpResponse) -> Response { + fn response(&self, req: &mut HttpRequest, resp: HttpResponse) -> Response { if let Some(s_box) = req.extensions().remove::>() { s_box.0.write(resp) } else { @@ -133,12 +134,12 @@ pub trait SessionImpl: 'static { /// Session's storage backend trait definition. #[doc(hidden)] -pub trait SessionBackend: Sized + 'static { +pub trait SessionBackend: Sized + 'static { type Session: SessionImpl; type ReadFuture: Future; /// Parse the session from request and load data from a storage backend. - fn from_request(&self, request: &mut HttpRequest) -> Self::ReadFuture; + fn from_request(&self, request: &mut HttpRequest) -> Self::ReadFuture; } /// Dummy session impl, does not do anything @@ -258,7 +259,7 @@ impl CookieSessionInner { Ok(()) } - fn load(&self, req: &mut HttpRequest) -> HashMap { + fn load(&self, req: &mut HttpRequest) -> HashMap { if let Ok(cookies) = req.cookies() { for cookie in cookies { if cookie.name() == self.name { @@ -316,12 +317,12 @@ impl CookieSessionBackend { } } -impl SessionBackend for CookieSessionBackend { +impl SessionBackend for CookieSessionBackend { type Session = CookieSession; type ReadFuture = FutureResult; - fn from_request(&self, req: &mut HttpRequest) -> Self::ReadFuture { + fn from_request(&self, req: &mut HttpRequest) -> Self::ReadFuture { let state = self.0.load(req); FutOk( CookieSession { diff --git a/src/pipeline.rs b/src/pipeline.rs index 4c9c79369..06f1e8f01 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -5,6 +5,7 @@ use std::cell::RefCell; use futures::{Async, Poll, Future, Stream}; use futures::task::{Task as FutureTask, current as current_task}; +use channel::HttpHandlerTask; use body::{Body, BodyStream}; use context::{Frame, IoContext}; use error::{Error, UnexpectedTaskFrame}; @@ -14,23 +15,23 @@ use httprequest::HttpRequest; use httpresponse::HttpResponse; use middlewares::{Middleware, Finished, Started, Response}; -type Handler = Fn(HttpRequest) -> Reply; -pub(crate) type PipelineHandler<'a> = &'a Fn(HttpRequest) -> Reply; +type Handler = Fn(HttpRequest) -> Reply; +pub(crate) type PipelineHandler<'a, S> = &'a Fn(HttpRequest) -> Reply; -pub struct Pipeline(PipelineState); +pub struct Pipeline(PipelineState); -enum PipelineState { +enum PipelineState { None, Error, - Starting(StartMiddlewares), - Handler(WaitingResponse), - RunMiddlewares(RunMiddlewares), - Response(ProcessResponse), - Finishing(FinishingMiddlewares), - Completed(Completed), + Starting(StartMiddlewares), + Handler(WaitingResponse), + RunMiddlewares(RunMiddlewares), + Response(ProcessResponse), + Finishing(FinishingMiddlewares), + Completed(Completed), } -impl PipelineState { +impl PipelineState { fn is_done(&self) -> bool { match *self { @@ -71,16 +72,16 @@ impl PipelineState { } } -struct PipelineInfo { - req: HttpRequest, +struct PipelineInfo { + req: HttpRequest, count: usize, - mws: Rc>>, + mws: Rc>>>, context: Option>, error: Option, } -impl PipelineInfo { - fn new(req: HttpRequest) -> PipelineInfo { +impl PipelineInfo { + fn new(req: HttpRequest) -> PipelineInfo { PipelineInfo { req: req, count: 0, @@ -91,7 +92,7 @@ impl PipelineInfo { } #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))] - fn req_mut(&self) -> &mut HttpRequest { + fn req_mut(&self) -> &mut HttpRequest { #[allow(mutable_transmutes)] unsafe{mem::transmute(&self.req)} } @@ -158,25 +159,30 @@ impl Future for DrainFut { } -impl Pipeline { +impl Pipeline { - pub fn new(req: HttpRequest, - mw: Rc>>, - handler: PipelineHandler) -> Pipeline + pub fn new(req: HttpRequest, + mw: Rc>>>, + handler: PipelineHandler) -> Pipeline { Pipeline(StartMiddlewares::init(mw, req, handler)) } +} - pub fn error>(err: R) -> Self { - Pipeline(ProcessResponse::init( - Box::new(PipelineInfo::new(HttpRequest::default())), err.into())) +impl Pipeline<()> { + pub fn error>(err: R) -> Box { + Box::new(Pipeline(ProcessResponse::init( + PipelineInfo::new(HttpRequest::default()), err.into()))) } +} - pub(crate) fn disconnected(&mut self) { +impl HttpHandlerTask for Pipeline { + + fn disconnected(&mut self) { self.0.disconnect() } - pub(crate) fn poll_io(&mut self, io: &mut T) -> Poll { + fn poll_io(&mut self, io: &mut Writer) -> Poll { loop { let state = mem::replace(&mut self.0, PipelineState::None); match state { @@ -256,7 +262,7 @@ impl Pipeline { } } - pub(crate) fn poll(&mut self) -> Poll<(), Error> { + fn poll(&mut self) -> Poll<(), Error> { loop { let state = mem::replace(&mut self.0, PipelineState::None); match state { @@ -327,16 +333,16 @@ impl Pipeline { type Fut = Box, Error=Error>>; /// Middlewares start executor -struct StartMiddlewares { - hnd: *mut Handler, +struct StartMiddlewares { + hnd: *mut Handler, fut: Option, - info: Box, + info: PipelineInfo, } -impl StartMiddlewares { +impl StartMiddlewares { - fn init(mws: Rc>>, - req: HttpRequest, handler: PipelineHandler) -> PipelineState { + fn init(mws: Rc>>>, + req: HttpRequest, handler: PipelineHandler) -> PipelineState { let mut info = PipelineInfo { req: req, count: 0, @@ -351,37 +357,37 @@ impl StartMiddlewares { loop { if info.count == len { let reply = (&*handler)(info.req.clone()); - return WaitingResponse::init(Box::new(info), reply) + return WaitingResponse::init(info, reply) } else { match info.mws[info.count].start(&mut info.req) { Started::Done => info.count += 1, Started::Response(resp) => - return RunMiddlewares::init(Box::new(info), resp), + return RunMiddlewares::init(info, resp), Started::Future(mut fut) => match fut.poll() { Ok(Async::NotReady) => return PipelineState::Starting(StartMiddlewares { hnd: handler as *const _ as *mut _, fut: Some(fut), - info: Box::new(info)}), + info: info}), Ok(Async::Ready(resp)) => { if let Some(resp) = resp { - return RunMiddlewares::init(Box::new(info), resp); + return RunMiddlewares::init(info, resp); } info.count += 1; } Err(err) => - return ProcessResponse::init(Box::new(info), err.into()), + return ProcessResponse::init(info, err.into()), }, Started::Err(err) => - return ProcessResponse::init(Box::new(info), err.into()), + return ProcessResponse::init(info, err.into()), } } } } - fn poll(mut self) -> Result { + fn poll(mut self) -> Result, PipelineState> { let len = self.info.mws.len(); 'outer: loop { match self.fut.as_mut().unwrap().poll() { @@ -421,14 +427,14 @@ impl StartMiddlewares { } // waiting for response -struct WaitingResponse { - info: Box, +struct WaitingResponse { + info: PipelineInfo, stream: PipelineResponse, } -impl WaitingResponse { +impl WaitingResponse { - fn init(info: Box, reply: Reply) -> PipelineState + fn init(info: PipelineInfo, reply: Reply) -> PipelineState { let stream = match reply.into() { ReplyItem::Message(resp) => @@ -443,7 +449,7 @@ impl WaitingResponse { WaitingResponse { info: info, stream: stream }) } - fn poll(mut self) -> Result { + fn poll(mut self) -> Result, PipelineState> { let stream = mem::replace(&mut self.stream, PipelineResponse::None); match stream { @@ -494,15 +500,15 @@ impl WaitingResponse { } /// Middlewares response executor -pub(crate) struct RunMiddlewares { - info: Box, +struct RunMiddlewares { + info: PipelineInfo, curr: usize, fut: Option>>, } -impl RunMiddlewares { +impl RunMiddlewares { - fn init(mut info: Box, mut resp: HttpResponse) -> PipelineState + fn init(mut info: PipelineInfo, mut resp: HttpResponse) -> PipelineState { if info.count == 0 { return ProcessResponse::init(info, resp); @@ -532,7 +538,7 @@ impl RunMiddlewares { } } - fn poll(mut self) -> Result { + fn poll(mut self) -> Result, PipelineState> { let len = self.info.mws.len(); loop { @@ -570,12 +576,12 @@ impl RunMiddlewares { } } -struct ProcessResponse { +struct ProcessResponse { resp: HttpResponse, iostate: IOState, running: RunningState, drain: DrainVec, - info: Box, + info: PipelineInfo, } #[derive(PartialEq)] @@ -625,9 +631,9 @@ impl Drop for DrainVec { } } -impl ProcessResponse { +impl ProcessResponse { - fn init(info: Box, resp: HttpResponse) -> PipelineState + fn init(info: PipelineInfo, resp: HttpResponse) -> PipelineState { PipelineState::Response( ProcessResponse{ resp: resp, @@ -637,14 +643,15 @@ impl ProcessResponse { info: info}) } - fn poll_io(mut self, io: &mut T) -> Result { + fn poll_io(mut self, io: &mut Writer) -> Result, PipelineState> { if self.drain.0.is_empty() && self.running != RunningState::Paused { // if task is paused, write buffer is probably full loop { let result = match mem::replace(&mut self.iostate, IOState::Done) { IOState::Response => { - let result = match io.start(self.info.req_mut(), &mut self.resp) { + let result = match io.start(self.info.req_mut().get_inner(), + &mut self.resp) { Ok(res) => res, Err(err) => { self.info.error = Some(err.into()); @@ -804,15 +811,15 @@ impl ProcessResponse { } /// Middlewares start executor -struct FinishingMiddlewares { - info: Box, +struct FinishingMiddlewares { + info: PipelineInfo, resp: HttpResponse, fut: Option>>, } -impl FinishingMiddlewares { +impl FinishingMiddlewares { - fn init(info: Box, resp: HttpResponse) -> PipelineState { + fn init(info: PipelineInfo, resp: HttpResponse) -> PipelineState { if info.count == 0 { Completed::init(info) } else { @@ -822,7 +829,7 @@ impl FinishingMiddlewares { } } - fn poll(mut self) -> Result { + fn poll(mut self) -> Result, PipelineState> { loop { // poll latest fut let not_ready = if let Some(ref mut fut) = self.fut { @@ -861,11 +868,11 @@ impl FinishingMiddlewares { } } -struct Completed(Box); +struct Completed(PipelineInfo); -impl Completed { +impl Completed { - fn init(info: Box) -> PipelineState { + fn init(info: PipelineInfo) -> PipelineState { if info.context.is_none() { PipelineState::None } else { @@ -873,7 +880,7 @@ impl Completed { } } - fn poll(mut self) -> Result { + fn poll(mut self) -> Result, PipelineState> { match self.0.poll_context() { Ok(Async::NotReady) => Ok(PipelineState::Completed(self)), Ok(Async::Ready(())) => Ok(PipelineState::None), @@ -890,11 +897,11 @@ mod tests { use tokio_core::reactor::Core; use futures::future::{lazy, result}; - impl PipelineState { + impl PipelineState { fn is_none(&self) -> Option { if let PipelineState::None = *self { Some(true) } else { None } } - fn completed(self) -> Option { + fn completed(self) -> Option> { if let PipelineState::Completed(c) = self { Some(c) } else { None } } } diff --git a/tests/test_server.rs b/tests/test_server.rs index b1715f252..1cc955867 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -53,18 +53,18 @@ struct MiddlewareTest { finish: Arc, } -impl middlewares::Middleware for MiddlewareTest { - fn start(&self, _: &mut HttpRequest) -> middlewares::Started { +impl middlewares::Middleware for MiddlewareTest { + fn start(&self, _: &mut HttpRequest) -> middlewares::Started { self.start.store(self.start.load(Ordering::Relaxed) + 1, Ordering::Relaxed); middlewares::Started::Done } - fn response(&self, _: &mut HttpRequest, resp: HttpResponse) -> middlewares::Response { + fn response(&self, _: &mut HttpRequest, resp: HttpResponse) -> middlewares::Response { self.response.store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed); middlewares::Response::Done(resp) } - fn finish(&self, _: &mut HttpRequest, _: &HttpResponse) -> middlewares::Finished { + fn finish(&self, _: &mut HttpRequest, _: &HttpResponse) -> middlewares::Finished { self.finish.store(self.finish.load(Ordering::Relaxed) + 1, Ordering::Relaxed); middlewares::Finished::Done } From 273de2260dc596ff27216066de088b5a4b94a0e7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Dec 2017 05:54:04 -0800 Subject: [PATCH 063/279] refactor pipeline --- src/pipeline.rs | 346 ++++++++++++++++++++++-------------------------- 1 file changed, 161 insertions(+), 185 deletions(-) diff --git a/src/pipeline.rs b/src/pipeline.rs index 06f1e8f01..cea38fa9e 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -1,6 +1,7 @@ use std::{io, mem}; use std::rc::Rc; use std::cell::RefCell; +use std::marker::PhantomData; use futures::{Async, Poll, Future, Stream}; use futures::task::{Task as FutureTask, current as current_task}; @@ -18,7 +19,7 @@ use middlewares::{Middleware, Finished, Started, Response}; type Handler = Fn(HttpRequest) -> Reply; pub(crate) type PipelineHandler<'a, S> = &'a Fn(HttpRequest) -> Reply; -pub struct Pipeline(PipelineState); +pub struct Pipeline(PipelineInfo, PipelineState); enum PipelineState { None, @@ -31,47 +32,6 @@ enum PipelineState { Completed(Completed), } -impl PipelineState { - - fn is_done(&self) -> bool { - match *self { - PipelineState::None | PipelineState::Error - | PipelineState::Starting(_) | PipelineState::Handler(_) - | PipelineState::RunMiddlewares(_) | PipelineState::Response(_) => true, - PipelineState::Finishing(ref st) => st.info.context.is_none(), - PipelineState::Completed(_) => false, - } - } - - fn disconnect(&mut self) { - let info = match *self { - PipelineState::None | PipelineState::Error => return, - PipelineState::Starting(ref mut st) => &mut st.info, - PipelineState::Handler(ref mut st) => &mut st.info, - PipelineState::RunMiddlewares(ref mut st) => &mut st.info, - PipelineState::Response(ref mut st) => &mut st.info, - PipelineState::Finishing(ref mut st) => &mut st.info, - PipelineState::Completed(ref mut st) => &mut st.0, - }; - if let Some(ref mut context) = info.context { - context.disconnected(); - } - } - - fn error(&mut self) -> Option { - let info = match *self { - PipelineState::None | PipelineState::Error => return None, - PipelineState::Starting(ref mut st) => &mut st.info, - PipelineState::Handler(ref mut st) => &mut st.info, - PipelineState::RunMiddlewares(ref mut st) => &mut st.info, - PipelineState::Response(ref mut st) => &mut st.info, - PipelineState::Finishing(ref mut st) => &mut st.info, - PipelineState::Completed(ref mut st) => &mut st.0, - }; - info.error.take() - } -} - struct PipelineInfo { req: HttpRequest, count: usize, @@ -162,98 +122,122 @@ impl Future for DrainFut { impl Pipeline { pub fn new(req: HttpRequest, - mw: Rc>>>, + mws: Rc>>>, handler: PipelineHandler) -> Pipeline { - Pipeline(StartMiddlewares::init(mw, req, handler)) + let mut info = PipelineInfo { + req: req, + count: 0, + mws: mws, + error: None, + context: None, + }; + let state = StartMiddlewares::init(&mut info, handler); + + Pipeline(info, state) } } impl Pipeline<()> { pub fn error>(err: R) -> Box { - Box::new(Pipeline(ProcessResponse::init( - PipelineInfo::new(HttpRequest::default()), err.into()))) + Box::new(Pipeline( + PipelineInfo::new(HttpRequest::default()), ProcessResponse::init(err.into()))) + } +} + +impl Pipeline { + + fn is_done(&self) -> bool { + match self.1 { + PipelineState::None | PipelineState::Error + | PipelineState::Starting(_) | PipelineState::Handler(_) + | PipelineState::RunMiddlewares(_) | PipelineState::Response(_) => true, + PipelineState::Finishing(_) => self.0.context.is_none(), + PipelineState::Completed(_) => false, + } } } impl HttpHandlerTask for Pipeline { fn disconnected(&mut self) { - self.0.disconnect() + if let Some(ref mut context) = self.0.context { + context.disconnected(); + } } fn poll_io(&mut self, io: &mut Writer) -> Poll { loop { - let state = mem::replace(&mut self.0, PipelineState::None); + let state = mem::replace(&mut self.1, PipelineState::None); match state { PipelineState::None => return Ok(Async::Ready(true)), PipelineState::Error => return Err(io::Error::new(io::ErrorKind::Other, "Internal error").into()), PipelineState::Starting(st) => { - match st.poll() { + match st.poll(&mut self.0) { Ok(state) => - self.0 = state, + self.1 = state, Err(state) => { - self.0 = state; + self.1 = state; return Ok(Async::NotReady) } } } PipelineState::Handler(st) => { - match st.poll() { + match st.poll(&mut self.0) { Ok(state) => - self.0 = state, + self.1 = state, Err(state) => { - self.0 = state; + self.1 = state; return Ok(Async::NotReady) } } } PipelineState::RunMiddlewares(st) => { - match st.poll() { + match st.poll(&mut self.0) { Ok(state) => - self.0 = state, + self.1 = state, Err(state) => { - self.0 = state; + self.1 = state; return Ok(Async::NotReady) } } } PipelineState::Response(st) => { - match st.poll_io(io) { + match st.poll_io(io, &mut self.0) { Ok(state) => { - self.0 = state; - if let Some(error) = self.0.error() { + self.1 = state; + if let Some(error) = self.0.error.take() { return Err(error) } else { - return Ok(Async::Ready(self.0.is_done())) + return Ok(Async::Ready(self.is_done())) } } Err(state) => { - self.0 = state; + self.1 = state; return Ok(Async::NotReady) } } } PipelineState::Finishing(st) => { - match st.poll() { + match st.poll(&mut self.0) { Ok(state) => - self.0 = state, + self.1 = state, Err(state) => { - self.0 = state; + self.1 = state; return Ok(Async::NotReady) } } } PipelineState::Completed(st) => { - match st.poll() { + match st.poll(&mut self.0) { Ok(state) => { - self.0 = state; + self.1 = state; return Ok(Async::Ready(true)); } Err(state) => { - self.0 = state; + self.1 = state; return Ok(Async::NotReady) } } @@ -264,63 +248,63 @@ impl HttpHandlerTask for Pipeline { fn poll(&mut self) -> Poll<(), Error> { loop { - let state = mem::replace(&mut self.0, PipelineState::None); + let state = mem::replace(&mut self.1, PipelineState::None); match state { PipelineState::None | PipelineState::Error => { return Ok(Async::Ready(())) } PipelineState::Starting(st) => { - match st.poll() { + match st.poll(&mut self.0) { Ok(state) => - self.0 = state, + self.1 = state, Err(state) => { - self.0 = state; + self.1 = state; return Ok(Async::NotReady) } } } PipelineState::Handler(st) => { - match st.poll() { + match st.poll(&mut self.0) { Ok(state) => - self.0 = state, + self.1 = state, Err(state) => { - self.0 = state; + self.1 = state; return Ok(Async::NotReady) } } } PipelineState::RunMiddlewares(st) => { - match st.poll() { + match st.poll(&mut self.0) { Ok(state) => - self.0 = state, + self.1 = state, Err(state) => { - self.0 = state; + self.1 = state; return Ok(Async::NotReady) } } } PipelineState::Response(_) => { - self.0 = state; + self.1 = state; return Ok(Async::NotReady); } PipelineState::Finishing(st) => { - match st.poll() { + match st.poll(&mut self.0) { Ok(state) => - self.0 = state, + self.1 = state, Err(state) => { - self.0 = state; + self.1 = state; return Ok(Async::NotReady) } } } PipelineState::Completed(st) => { - match st.poll() { + match st.poll(&mut self.0) { Ok(state) => { - self.0 = state; + self.1 = state; return Ok(Async::Ready(())); } Err(state) => { - self.0 = state; + self.1 = state; return Ok(Async::NotReady) } } @@ -336,21 +320,11 @@ type Fut = Box, Error=Error>>; struct StartMiddlewares { hnd: *mut Handler, fut: Option, - info: PipelineInfo, } impl StartMiddlewares { - fn init(mws: Rc>>>, - req: HttpRequest, handler: PipelineHandler) -> PipelineState { - let mut info = PipelineInfo { - req: req, - count: 0, - mws: mws, - error: None, - context: None, - }; - + fn init(info: &mut PipelineInfo, handler: PipelineHandler) -> PipelineState { // execute middlewares, we need this stage because middlewares could be non-async // and we can move to next state immidietly let len = info.mws.len(); @@ -369,8 +343,7 @@ impl StartMiddlewares { Ok(Async::NotReady) => return PipelineState::Starting(StartMiddlewares { hnd: handler as *const _ as *mut _, - fut: Some(fut), - info: info}), + fut: Some(fut)}), Ok(Async::Ready(resp)) => { if let Some(resp) = resp { return RunMiddlewares::init(info, resp); @@ -378,49 +351,49 @@ impl StartMiddlewares { info.count += 1; } Err(err) => - return ProcessResponse::init(info, err.into()), + return ProcessResponse::init(err.into()), }, Started::Err(err) => - return ProcessResponse::init(info, err.into()), + return ProcessResponse::init(err.into()), } } } } - fn poll(mut self) -> Result, PipelineState> { - let len = self.info.mws.len(); + fn poll(mut self, info: &mut PipelineInfo) -> Result, PipelineState> { + let len = info.mws.len(); 'outer: loop { match self.fut.as_mut().unwrap().poll() { Ok(Async::NotReady) => return Err(PipelineState::Starting(self)), Ok(Async::Ready(resp)) => { - self.info.count += 1; + info.count += 1; if let Some(resp) = resp { - return Ok(RunMiddlewares::init(self.info, resp)); + return Ok(RunMiddlewares::init(info, resp)); } - if self.info.count == len { - let reply = (unsafe{&*self.hnd})(self.info.req.clone()); - return Ok(WaitingResponse::init(self.info, reply)); + if info.count == len { + let reply = (unsafe{&*self.hnd})(info.req.clone()); + return Ok(WaitingResponse::init(info, reply)); } else { loop { - match self.info.mws[self.info.count].start(self.info.req_mut()) { + match info.mws[info.count].start(info.req_mut()) { Started::Done => - self.info.count += 1, + info.count += 1, Started::Response(resp) => { - return Ok(RunMiddlewares::init(self.info, resp)); + return Ok(RunMiddlewares::init(info, resp)); }, Started::Future(fut) => { self.fut = Some(fut); continue 'outer }, Started::Err(err) => - return Ok(ProcessResponse::init(self.info, err.into())) + return Ok(ProcessResponse::init(err.into())) } } } } Err(err) => - return Ok(ProcessResponse::init(self.info, err.into())) + return Ok(ProcessResponse::init(err.into())) } } } @@ -428,13 +401,13 @@ impl StartMiddlewares { // waiting for response struct WaitingResponse { - info: PipelineInfo, stream: PipelineResponse, + _s: PhantomData, } impl WaitingResponse { - fn init(info: PipelineInfo, reply: Reply) -> PipelineState + fn init(info: &mut PipelineInfo, reply: Reply) -> PipelineState { let stream = match reply.into() { ReplyItem::Message(resp) => @@ -446,10 +419,10 @@ impl WaitingResponse { }; PipelineState::Handler( - WaitingResponse { info: info, stream: stream }) + WaitingResponse { stream: stream, _s: PhantomData }) } - fn poll(mut self) -> Result, PipelineState> { + fn poll(mut self, info: &mut PipelineInfo) -> Result, PipelineState> { let stream = mem::replace(&mut self.stream, PipelineResponse::None); match stream { @@ -459,8 +432,8 @@ impl WaitingResponse { Ok(Async::Ready(Some(frame))) => { match frame { Frame::Message(resp) => { - self.info.context = Some(context); - return Ok(RunMiddlewares::init(self.info, resp)) + info.context = Some(context); + return Ok(RunMiddlewares::init(info, resp)) } Frame::Payload(_) | Frame::Drain(_) => (), } @@ -468,14 +441,14 @@ impl WaitingResponse { Ok(Async::Ready(None)) => { error!("Unexpected eof"); let err: Error = UnexpectedTaskFrame.into(); - return Ok(ProcessResponse::init(self.info, err.into())) + return Ok(ProcessResponse::init(err.into())) }, Ok(Async::NotReady) => { self.stream = PipelineResponse::Context(context); return Err(PipelineState::Handler(self)) }, Err(err) => - return Ok(ProcessResponse::init(self.info, err.into())) + return Ok(ProcessResponse::init(err.into())) } } }, @@ -486,9 +459,9 @@ impl WaitingResponse { Err(PipelineState::Handler(self)) } Ok(Async::Ready(response)) => - Ok(RunMiddlewares::init(self.info, response)), + Ok(RunMiddlewares::init(info, response)), Err(err) => - Ok(ProcessResponse::init(self.info, err.into())), + Ok(ProcessResponse::init(err.into())), } } PipelineResponse::None => { @@ -501,17 +474,17 @@ impl WaitingResponse { /// Middlewares response executor struct RunMiddlewares { - info: PipelineInfo, curr: usize, fut: Option>>, + _s: PhantomData, } impl RunMiddlewares { - fn init(mut info: PipelineInfo, mut resp: HttpResponse) -> PipelineState + fn init(info: &mut PipelineInfo, mut resp: HttpResponse) -> PipelineState { if info.count == 0 { - return ProcessResponse::init(info, resp); + return ProcessResponse::init(resp); } let mut curr = 0; let len = info.mws.len(); @@ -520,26 +493,26 @@ impl RunMiddlewares { resp = match info.mws[curr].response(info.req_mut(), resp) { Response::Err(err) => { info.count = curr + 1; - return ProcessResponse::init(info, err.into()) + return ProcessResponse::init(err.into()) } Response::Done(r) => { curr += 1; if curr == len { - return ProcessResponse::init(info, r) + return ProcessResponse::init(r) } else { r } }, Response::Future(fut) => { return PipelineState::RunMiddlewares( - RunMiddlewares { info: info, curr: curr, fut: Some(fut) }) + RunMiddlewares { curr: curr, fut: Some(fut), _s: PhantomData }) }, }; } } - fn poll(mut self) -> Result, PipelineState> { - let len = self.info.mws.len(); + fn poll(mut self, info: &mut PipelineInfo) -> Result, PipelineState> { + let len = info.mws.len(); loop { // poll latest fut @@ -551,16 +524,16 @@ impl RunMiddlewares { resp } Err(err) => - return Ok(ProcessResponse::init(self.info, err.into())), + return Ok(ProcessResponse::init(err.into())), }; loop { if self.curr == len { - return Ok(ProcessResponse::init(self.info, resp)); + return Ok(ProcessResponse::init(resp)); } else { - match self.info.mws[self.curr].response(self.info.req_mut(), resp) { + match info.mws[self.curr].response(info.req_mut(), resp) { Response::Err(err) => - return Ok(ProcessResponse::init(self.info, err.into())), + return Ok(ProcessResponse::init(err.into())), Response::Done(r) => { self.curr += 1; resp = r @@ -581,7 +554,7 @@ struct ProcessResponse { iostate: IOState, running: RunningState, drain: DrainVec, - info: PipelineInfo, + _s: PhantomData, } #[derive(PartialEq)] @@ -633,29 +606,31 @@ impl Drop for DrainVec { impl ProcessResponse { - fn init(info: PipelineInfo, resp: HttpResponse) -> PipelineState + fn init(resp: HttpResponse) -> PipelineState { PipelineState::Response( ProcessResponse{ resp: resp, iostate: IOState::Response, running: RunningState::Running, drain: DrainVec(Vec::new()), - info: info}) + _s: PhantomData}) } - fn poll_io(mut self, io: &mut Writer) -> Result, PipelineState> { + fn poll_io(mut self, io: &mut Writer, info: &mut PipelineInfo) + -> Result, PipelineState> + { if self.drain.0.is_empty() && self.running != RunningState::Paused { // if task is paused, write buffer is probably full loop { let result = match mem::replace(&mut self.iostate, IOState::Done) { IOState::Response => { - let result = match io.start(self.info.req_mut().get_inner(), + let result = match io.start(info.req_mut().get_inner(), &mut self.resp) { Ok(res) => res, Err(err) => { - self.info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init(self.info, self.resp)) + info.error = Some(err.into()); + return Ok(FinishingMiddlewares::init(info, self.resp)) } }; @@ -672,13 +647,13 @@ impl ProcessResponse { IOState::Payload(mut body) => { // always poll context if self.running == RunningState::Running { - match self.info.poll_context() { + match info.poll_context() { Ok(Async::NotReady) => (), Ok(Async::Ready(_)) => self.running = RunningState::Done, Err(err) => { - self.info.error = Some(err); - return Ok(FinishingMiddlewares::init(self.info, self.resp)) + info.error = Some(err); + return Ok(FinishingMiddlewares::init(info, self.resp)) } } } @@ -687,8 +662,8 @@ impl ProcessResponse { Ok(Async::Ready(None)) => { self.iostate = IOState::Done; if let Err(err) = io.write_eof() { - self.info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init(self.info, self.resp)) + info.error = Some(err.into()); + return Ok(FinishingMiddlewares::init(info, self.resp)) } break }, @@ -696,9 +671,8 @@ impl ProcessResponse { self.iostate = IOState::Payload(body); match io.write(chunk.as_ref()) { Err(err) => { - self.info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - self.info, self.resp)) + info.error = Some(err.into()); + return Ok(FinishingMiddlewares::init(info, self.resp)) }, Ok(result) => result } @@ -708,27 +682,27 @@ impl ProcessResponse { break }, Err(err) => { - self.info.error = Some(err); - return Ok(FinishingMiddlewares::init(self.info, self.resp)) + info.error = Some(err); + return Ok(FinishingMiddlewares::init(info, self.resp)) } } }, IOState::Context => { - match self.info.context.as_mut().unwrap().poll() { + match info.context.as_mut().unwrap().poll() { Ok(Async::Ready(Some(frame))) => { match frame { Frame::Message(msg) => { error!("Unexpected message frame {:?}", msg); - self.info.error = Some(UnexpectedTaskFrame.into()); + info.error = Some(UnexpectedTaskFrame.into()); return Ok( - FinishingMiddlewares::init(self.info, self.resp)) + FinishingMiddlewares::init(info, self.resp)) }, Frame::Payload(None) => { self.iostate = IOState::Done; if let Err(err) = io.write_eof() { - self.info.error = Some(err.into()); + info.error = Some(err.into()); return Ok( - FinishingMiddlewares::init(self.info, self.resp)) + FinishingMiddlewares::init(info, self.resp)) } break }, @@ -736,9 +710,9 @@ impl ProcessResponse { self.iostate = IOState::Context; match io.write(chunk.as_ref()) { Err(err) => { - self.info.error = Some(err.into()); + info.error = Some(err.into()); return Ok(FinishingMiddlewares::init( - self.info, self.resp)) + info, self.resp)) }, Ok(result) => result } @@ -751,7 +725,7 @@ impl ProcessResponse { }, Ok(Async::Ready(None)) => { self.iostate = IOState::Done; - self.info.context.take(); + info.context.take(); break } Ok(Async::NotReady) => { @@ -759,8 +733,8 @@ impl ProcessResponse { break } Err(err) => { - self.info.error = Some(err); - return Ok(FinishingMiddlewares::init(self.info, self.resp)) + info.error = Some(err); + return Ok(FinishingMiddlewares::init(info, self.resp)) } } } @@ -787,8 +761,8 @@ impl ProcessResponse { return Err(PipelineState::Response(self)), Err(err) => { debug!("Error sending data: {}", err); - self.info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init(self.info, self.resp)) + info.error = Some(err.into()); + return Ok(FinishingMiddlewares::init(info, self.resp)) } } @@ -803,7 +777,7 @@ impl ProcessResponse { // response is completed if self.iostate.is_done() { self.resp.set_response_size(io.written()); - Ok(FinishingMiddlewares::init(self.info, self.resp)) + Ok(FinishingMiddlewares::init(info, self.resp)) } else { Err(PipelineState::Response(self)) } @@ -812,24 +786,24 @@ impl ProcessResponse { /// Middlewares start executor struct FinishingMiddlewares { - info: PipelineInfo, resp: HttpResponse, fut: Option>>, + _s: PhantomData, } impl FinishingMiddlewares { - fn init(info: PipelineInfo, resp: HttpResponse) -> PipelineState { + fn init(info: &mut PipelineInfo, resp: HttpResponse) -> PipelineState { if info.count == 0 { Completed::init(info) } else { - match (FinishingMiddlewares{info: info, resp: resp, fut: None}).poll() { + match (FinishingMiddlewares{resp: resp, fut: None, _s: PhantomData}).poll(info) { Ok(st) | Err(st) => st, } } } - fn poll(mut self) -> Result, PipelineState> { + fn poll(mut self, info: &mut PipelineInfo) -> Result, PipelineState> { loop { // poll latest fut let not_ready = if let Some(ref mut fut) = self.fut { @@ -852,12 +826,12 @@ impl FinishingMiddlewares { return Ok(PipelineState::Finishing(self)) } self.fut = None; - self.info.count -= 1; + info.count -= 1; - match self.info.mws[self.info.count].finish(self.info.req_mut(), &self.resp) { + match info.mws[info.count].finish(info.req_mut(), &self.resp) { Finished::Done => { - if self.info.count == 0 { - return Ok(Completed::init(self.info)) + if info.count == 0 { + return Ok(Completed::init(info)) } } Finished::Future(fut) => { @@ -868,21 +842,21 @@ impl FinishingMiddlewares { } } -struct Completed(PipelineInfo); +struct Completed(PhantomData); impl Completed { - fn init(info: PipelineInfo) -> PipelineState { + fn init(info: &mut PipelineInfo) -> PipelineState { if info.context.is_none() { PipelineState::None } else { - PipelineState::Completed(Completed(info)) + PipelineState::Completed(Completed(PhantomData)) } } - fn poll(mut self) -> Result, PipelineState> { - match self.0.poll_context() { - Ok(Async::NotReady) => Ok(PipelineState::Completed(self)), + fn poll(self, info: &mut PipelineInfo) -> Result, PipelineState> { + match info.poll_context() { + Ok(Async::NotReady) => Ok(PipelineState::Completed(Completed(PhantomData))), Ok(Async::Ready(())) => Ok(PipelineState::None), Err(_) => Ok(PipelineState::Error), } @@ -914,23 +888,25 @@ mod tests { #[test] fn test_completed() { Core::new().unwrap().run(lazy(|| { - let info = Box::new(PipelineInfo::new(HttpRequest::default())); - Completed::init(info).is_none().unwrap(); + let mut info = PipelineInfo::new(HttpRequest::default()); + Completed::init(&mut info).is_none().unwrap(); let req = HttpRequest::default(); let mut ctx = HttpContext::new(req.clone(), MyActor); let addr: Address<_> = ctx.address(); - let mut info = Box::new(PipelineInfo::new(req)); + let mut info = PipelineInfo::new(req); info.context = Some(Box::new(ctx)); - let mut state = Completed::init(info).completed().unwrap(); + let mut state = Completed::init(&mut info).completed().unwrap(); - let st = state.poll().ok().unwrap(); - assert!(!st.is_done()); + let st = state.poll(&mut info).ok().unwrap(); + let pp = Pipeline(info, st); + assert!(!pp.is_done()); + let Pipeline(mut info, st) = pp; state = st.completed().unwrap(); drop(addr); - state.poll().ok().unwrap().is_none().unwrap(); + state.poll(&mut info).ok().unwrap().is_none().unwrap(); result(Ok::<_, ()>(())) })).unwrap() From 7addd2800da930c182fa75dcad7c500aa15e37f4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Dec 2017 11:39:13 -0800 Subject: [PATCH 064/279] add NormalizePath handler --- .travis.yml | 2 +- src/handler.rs | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 +- 3 files changed, 93 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 883a9d40c..cc0574091 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,7 +50,7 @@ after_success: - | if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) - cargo tarpaulin --out Xml + USE_SKEPTIC=1 cargo tarpaulin --out Xml bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi diff --git a/src/handler.rs b/src/handler.rs index 91d70bced..37963f39f 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -4,7 +4,10 @@ use actix::Actor; use futures::Future; use serde_json; use serde::Serialize; +use regex::Regex; +use http::{header, StatusCode, Error as HttpError}; +use body::Body; use error::Error; use context::{HttpContext, IoContext}; use httprequest::HttpRequest; @@ -263,6 +266,94 @@ impl FromRequest for Json { } } +/// Handler that normalizes the path of a request. By normalizing it means: +/// +/// - Add a trailing slash to the path. +/// - Double slashes are replaced by one. +/// +/// The handler returns as soon as it finds a path that resolves +/// correctly. The order if all enable is 1) merge, 2) append +/// and 3) both merge and append. If the path resolves with +/// at least one of those conditions, it will redirect to the new path. +/// +/// If *append* is *true* append slash when needed. If a resource is +/// defined with trailing slash and the request comes without it, it will +/// append it automatically. +/// +/// If *merge* is *true*, merge multiple consecutive slashes in the path into one. +/// +/// This handler designed to be use as a handler for application's *default resource*. +pub struct NormalizePath { + append: bool, + merge: bool, + re_merge: Regex, + redirect: StatusCode, + not_found: StatusCode, +} + +impl Default for NormalizePath { + fn default() -> NormalizePath { + NormalizePath { + append: true, + merge: true, + re_merge: Regex::new("//+").unwrap(), + redirect: StatusCode::MOVED_PERMANENTLY, + not_found: StatusCode::NOT_FOUND, + } + } +} + +impl NormalizePath { + pub fn new(append: bool, merge: bool, redirect: StatusCode) -> NormalizePath { + NormalizePath { + append: append, + merge: merge, + re_merge: Regex::new("//+").unwrap(), + redirect: redirect, + not_found: StatusCode::NOT_FOUND, + } + } +} + +impl Handler for NormalizePath { + type Result = Result; + + fn handle(&self, req: HttpRequest) -> Self::Result { + if let Some(router) = req.router() { + if self.merge { + // merge slashes + let p = self.re_merge.replace_all(req.path(), "/"); + if p.len() != req.path().len() { + if router.has_route(p.as_ref()) { + return HttpResponse::build(self.redirect) + .header(header::LOCATION, p.as_ref()) + .body(Body::Empty); + } + // merge slashes and append trailing slash + if self.append && !p.ends_with('/') { + let p = p.as_ref().to_owned() + "/"; + if router.has_route(&p) { + return HttpResponse::build(self.redirect) + .header(header::LOCATION, p.as_str()) + .body(Body::Empty); + } + } + } + } + // append trailing slash + if self.append && !req.path().ends_with('/') { + let p = req.path().to_owned() + "/"; + if router.has_route(&p) { + return HttpResponse::build(self.redirect) + .header(header::LOCATION, p.as_str()) + .body(Body::Empty); + } + } + } + Ok(HttpResponse::new(self.not_found, Body::Empty)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/lib.rs b/src/lib.rs index d1b9feb8e..8d1ba6bec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,7 +87,7 @@ pub use application::Application; pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; pub use payload::{Payload, PayloadItem}; -pub use handler::{Reply, Json, FromRequest}; +pub use handler::{Reply, FromRequest, Json, NormalizePath}; pub use route::Route; pub use resource::Resource; pub use server::HttpServer; From 71bbe2a5dd3bf1e5d9a8fc194bccdf113b2a011e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Dec 2017 11:55:55 -0800 Subject: [PATCH 065/279] update doc string for NormalizePath --- src/handler.rs | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 37963f39f..16c066f5d 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -266,14 +266,16 @@ impl FromRequest for Json { } } -/// Handler that normalizes the path of a request. By normalizing it means: +/// Path normalization helper +/// +/// By normalizing it means: /// /// - Add a trailing slash to the path. /// - Double slashes are replaced by one. /// /// The handler returns as soon as it finds a path that resolves -/// correctly. The order if all enable is 1) merge, 2) append -/// and 3) both merge and append. If the path resolves with +/// correctly. The order if all enable is 1) merge, 3) both merge and append +/// and 3) append. If the path resolves with /// at least one of those conditions, it will redirect to the new path. /// /// If *append* is *true* append slash when needed. If a resource is @@ -283,6 +285,23 @@ impl FromRequest for Json { /// If *merge* is *true*, merge multiple consecutive slashes in the path into one. /// /// This handler designed to be use as a handler for application's *default resource*. +/// +/// ```rust +/// # extern crate actix_web; +/// # #[macro_use] extern crate serde_derive; +/// # use actix_web::*; +/// # +/// # fn index(req: HttpRequest) -> httpcodes::StaticResponse { +/// # httpcodes::HTTPOk +/// # } +/// fn main() { +/// let app = Application::new("/") +/// .resource("/test/", |r| r.f(index)) +/// .default_resource(|r| r.h(NormalizePath::default())) +/// .finish(); +/// } +/// ``` +/// In this example `/test`, `/test///` will be redirected to `/test/` url. pub struct NormalizePath { append: bool, merge: bool, @@ -292,6 +311,8 @@ pub struct NormalizePath { } impl Default for NormalizePath { + /// Create default `NormalizePath` instance, *append* is set to *true*, + /// *merge* is set to *true* and *redirect* is set to `StatusCode::MOVED_PERMANENTLY` fn default() -> NormalizePath { NormalizePath { append: true, @@ -304,6 +325,7 @@ impl Default for NormalizePath { } impl NormalizePath { + /// Create new `NoramlizePath` instance pub fn new(append: bool, merge: bool, redirect: StatusCode) -> NormalizePath { NormalizePath { append: append, From 0388a464ba77c23e9cae1f2aa9e7a1a048cc3889 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Dec 2017 13:25:06 -0800 Subject: [PATCH 066/279] tests for NormalizePath --- src/application.rs | 36 ++++------ src/handler.rs | 172 ++++++++++++++++++++++++++++++++++++++++++++- src/httprequest.rs | 27 ++++++- 3 files changed, 211 insertions(+), 24 deletions(-) diff --git a/src/application.rs b/src/application.rs index 291b33d8a..4d18cb6fa 100644 --- a/src/application.rs +++ b/src/application.rs @@ -21,7 +21,11 @@ pub struct HttpApplication { impl HttpApplication { - fn run(&self, mut req: HttpRequest) -> Reply { + pub(crate) fn prepare_request(&self, req: HttpRequest) -> HttpRequest { + req.with_state(Rc::clone(&self.state), self.router.clone()) + } + + pub(crate) fn run(&self, mut req: HttpRequest) -> Reply { if let Some(h) = self.router.recognize(&mut req) { h.handle(req) } else { @@ -34,8 +38,7 @@ impl HttpHandler for HttpApplication { fn handle(&self, req: HttpRequest) -> Result, HttpRequest> { if req.path().starts_with(&self.prefix) { - let req = req.with_state(Rc::clone(&self.state), self.router.clone()); - + let req = self.prepare_request(req); Ok(Box::new(Pipeline::new(req, Rc::clone(&self.middlewares), &|req: HttpRequest| self.run(req)))) } else { @@ -266,21 +269,10 @@ mod tests { use std::str::FromStr; use http::{Method, Version, Uri, HeaderMap, StatusCode}; use super::*; - use handler::ReplyItem; use httprequest::HttpRequest; - use httpresponse::HttpResponse; use payload::Payload; use httpcodes; - impl Reply { - fn msg(self) -> Option { - match self.into() { - ReplyItem::Message(resp) => Some(resp), - _ => None, - } - } - } - #[test] fn test_default_resource() { let app = Application::new("/") @@ -290,14 +282,14 @@ mod tests { let req = HttpRequest::new( Method::GET, Uri::from_str("/test").unwrap(), Version::HTTP_11, HeaderMap::new(), Payload::empty()); - let resp = app.run(req).msg().unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); let req = HttpRequest::new( Method::GET, Uri::from_str("/blah").unwrap(), Version::HTTP_11, HeaderMap::new(), Payload::empty()); - let resp = app.run(req).msg().unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); let app = Application::new("/") .default_resource(|r| r.h(httpcodes::HTTPMethodNotAllowed)) @@ -305,8 +297,8 @@ mod tests { let req = HttpRequest::new( Method::GET, Uri::from_str("/blah").unwrap(), Version::HTTP_11, HeaderMap::new(), Payload::empty()); - let resp = app.run(req).msg().unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::METHOD_NOT_ALLOWED); } #[test] @@ -323,7 +315,7 @@ mod tests { .resource("/", |r| r.h(httpcodes::HTTPOk)) .finish(); let req = HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone()); - assert_eq!( - app.run(req).msg().unwrap().status(), StatusCode::OK); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); } } diff --git a/src/handler.rs b/src/handler.rs index 16c066f5d..24cb15a7c 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -79,6 +79,14 @@ impl Reply { pub(crate) fn into(self) -> ReplyItem { self.0 } + + #[cfg(test)] + pub(crate) fn as_response(&self) -> Option<&HttpResponse> { + match self.0 { + ReplyItem::Message(ref resp) => Some(resp), + _ => None, + } + } } impl FromRequest for Reply { @@ -342,11 +350,13 @@ impl Handler for NormalizePath { fn handle(&self, req: HttpRequest) -> Self::Result { if let Some(router) = req.router() { + let query = req.query_string(); if self.merge { // merge slashes let p = self.re_merge.replace_all(req.path(), "/"); if p.len() != req.path().len() { if router.has_route(p.as_ref()) { + let p = if !query.is_empty() { p + "?" + query } else { p }; return HttpResponse::build(self.redirect) .header(header::LOCATION, p.as_ref()) .body(Body::Empty); @@ -355,6 +365,7 @@ impl Handler for NormalizePath { if self.append && !p.ends_with('/') { let p = p.as_ref().to_owned() + "/"; if router.has_route(&p) { + let p = if !query.is_empty() { p + "?" + query } else { p }; return HttpResponse::build(self.redirect) .header(header::LOCATION, p.as_str()) .body(Body::Empty); @@ -366,6 +377,7 @@ impl Handler for NormalizePath { if self.append && !req.path().ends_with('/') { let p = req.path().to_owned() + "/"; if router.has_route(&p) { + let p = if !query.is_empty() { p + "?" + query } else { p }; return HttpResponse::build(self.redirect) .header(header::LOCATION, p.as_str()) .body(Body::Empty); @@ -379,7 +391,8 @@ impl Handler for NormalizePath { #[cfg(test)] mod tests { use super::*; - use http::header; + use http::{header, Method}; + use application::Application; #[derive(Serialize)] struct MyObj { @@ -392,4 +405,161 @@ mod tests { let resp = json.from_request(HttpRequest::default()).unwrap(); assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "application/json"); } + + fn index(_req: HttpRequest) -> HttpResponse { + HttpResponse::new(StatusCode::OK, Body::Empty) + } + + #[test] + fn test_normalize_path_trailing_slashes() { + let app = Application::new("/") + .resource("/resource1", |r| r.method(Method::GET).f(index)) + .resource("/resource2/", |r| r.method(Method::GET).f(index)) + .default_resource(|r| r.h(NormalizePath::default())) + .finish(); + + // trailing slashes + let params = vec![("/resource1", "", StatusCode::OK), + ("/resource1/", "", StatusCode::NOT_FOUND), + ("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY), + ("/resource2/", "", StatusCode::OK), + ("/resource1?p1=1&p2=2", "", StatusCode::OK), + ("/resource1/?p1=1&p2=2", "", StatusCode::NOT_FOUND), + ("/resource2?p1=1&p2=2", "/resource2/?p1=1&p2=2", + StatusCode::MOVED_PERMANENTLY), + ("/resource2/?p1=1&p2=2", "", StatusCode::OK) + ]; + for (path, target, code) in params { + let req = app.prepare_request(HttpRequest::from_path(path)); + let resp = app.run(req); + let r = resp.as_response().unwrap(); + assert_eq!(r.status(), code); + if !target.is_empty() { + assert_eq!( + target, + r.headers().get(header::LOCATION).unwrap().to_str().unwrap()); + } + } + } + + #[test] + fn test_normalize_path_trailing_slashes_disabled() { + let app = Application::new("/") + .resource("/resource1", |r| r.method(Method::GET).f(index)) + .resource("/resource2/", |r| r.method(Method::GET).f(index)) + .default_resource(|r| r.h( + NormalizePath::new(false, true, StatusCode::MOVED_PERMANENTLY))) + .finish(); + + // trailing slashes + let params = vec![("/resource1", StatusCode::OK), + ("/resource1/", StatusCode::NOT_FOUND), + ("/resource2", StatusCode::NOT_FOUND), + ("/resource2/", StatusCode::OK), + ("/resource1?p1=1&p2=2", StatusCode::OK), + ("/resource1/?p1=1&p2=2", StatusCode::NOT_FOUND), + ("/resource2?p1=1&p2=2", StatusCode::NOT_FOUND), + ("/resource2/?p1=1&p2=2", StatusCode::OK) + ]; + for (path, code) in params { + let req = app.prepare_request(HttpRequest::from_path(path)); + let resp = app.run(req); + let r = resp.as_response().unwrap(); + assert_eq!(r.status(), code); + } + } + + #[test] + fn test_normalize_path_merge_slashes() { + let app = Application::new("/") + .resource("/resource1", |r| r.method(Method::GET).f(index)) + .resource("/resource1/a/b", |r| r.method(Method::GET).f(index)) + .default_resource(|r| r.h(NormalizePath::default())) + .finish(); + + // trailing slashes + let params = vec![ + ("/resource1/a/b", "", StatusCode::OK), + ("//resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("//resource1//a//b/", "", StatusCode::NOT_FOUND), + ("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("/////resource1/a//b/", "", StatusCode::NOT_FOUND), + ("/resource1/a/b?p=1", "", StatusCode::OK), + ("//resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), + ("//resource1//a//b/?p=1", "", StatusCode::NOT_FOUND), + ("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), + ("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), + ("/////resource1/a//b/?p=1", "", StatusCode::NOT_FOUND), + ]; + for (path, target, code) in params { + let req = app.prepare_request(HttpRequest::from_path(path)); + let resp = app.run(req); + let r = resp.as_response().unwrap(); + assert_eq!(r.status(), code); + if !target.is_empty() { + assert_eq!( + target, + r.headers().get(header::LOCATION).unwrap().to_str().unwrap()); + } + } + } + + #[test] + fn test_normalize_path_merge_and_append_slashes() { + let app = Application::new("/") + .resource("/resource1", |r| r.method(Method::GET).f(index)) + .resource("/resource2/", |r| r.method(Method::GET).f(index)) + .resource("/resource1/a/b", |r| r.method(Method::GET).f(index)) + .resource("/resource2/a/b/", |r| r.method(Method::GET).f(index)) + .default_resource(|r| r.h(NormalizePath::default())) + .finish(); + + // trailing slashes + let params = vec![ + ("/resource1/a/b", "", StatusCode::OK), + ("/resource1/a/b/", "", StatusCode::NOT_FOUND), + ("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), + ("//resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), + ("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("///resource1//a//b/", "", StatusCode::NOT_FOUND), + ("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("/////resource1/a///b/", "", StatusCode::NOT_FOUND), + ("/resource2/a/b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), + ("/resource2/a/b/", "", StatusCode::OK), + ("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), + ("//resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), + ("///resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), + ("///resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), + ("/////resource2/a///b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), + ("/////resource2/a///b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), + ("/resource1/a/b?p=1", "", StatusCode::OK), + ("/resource1/a/b/?p=1", "", StatusCode::NOT_FOUND), + ("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), + ("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), + ("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), + ("///resource1//a//b/?p=1", "", StatusCode::NOT_FOUND), + ("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), + ("/////resource1/a///b/?p=1", "", StatusCode::NOT_FOUND), + ("/resource2/a/b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), + ("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), + ("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), + ("///resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), + ("///resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), + ("/////resource2/a///b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), + ("/////resource2/a///b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), + ]; + for (path, target, code) in params { + let req = app.prepare_request(HttpRequest::from_path(path)); + let resp = app.run(req); + let r = resp.as_response().unwrap(); + assert_eq!(r.status(), code); + if !target.is_empty() { + assert_eq!( + target, r.headers().get(header::LOCATION).unwrap().to_str().unwrap()); + } + } + } + + } diff --git a/src/httprequest.rs b/src/httprequest.rs index 092299594..3752cdf36 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -5,9 +5,9 @@ use std::net::SocketAddr; use std::collections::HashMap; use bytes::BytesMut; use futures::{Async, Future, Stream, Poll}; -use url::{Url, form_urlencoded}; use cookie::Cookie; use http_range::HttpRange; +use url::{Url, form_urlencoded}; use http::{header, Uri, Method, Version, HeaderMap, Extensions}; use info::ConnectionInfo; @@ -98,6 +98,31 @@ impl HttpRequest<()> { ) } + /// Construct a new Request. + #[inline] + #[cfg(test)] + pub fn from_path(path: &str) -> HttpRequest + { + use std::str::FromStr; + + HttpRequest( + Rc::new(HttpMessage { + method: Method::GET, + uri: Uri::from_str(path).unwrap(), + version: Version::HTTP_11, + headers: HeaderMap::new(), + params: Params::default(), + cookies: None, + addr: None, + payload: Payload::empty(), + extensions: Extensions::new(), + info: None, + }), + Rc::new(()), + None, + ) + } + /// Construct new http request with state. pub fn with_state(self, state: Rc, router: Router) -> HttpRequest { HttpRequest(self.0, state, Some(router)) From c5490a851c099cb6be102551800e6de12915f615 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Dec 2017 13:58:24 -0800 Subject: [PATCH 067/279] add guid for path normalization --- guide/src/qs_5.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index bd046ddac..0495f9b45 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -184,3 +184,41 @@ fn main() { .finish(); } ``` + +### Path normalization + +By normalizing it means: + + - Add a trailing slash to the path. + - Double slashes are replaced by one. + +The handler returns as soon as it finds a path that resolves +correctly. The order if all enable is 1) merge, 3) both merge and append +and 3) append. If the path resolves with +at least one of those conditions, it will redirect to the new path. + +If *append* is *true* append slash when needed. If a resource is +defined with trailing slash and the request comes without it, it will +append it automatically. + +If *merge* is *true*, merge multiple consecutive slashes in the path into one. + +This handler designed to be use as a handler for application's *default resource*. + +```rust +# extern crate actix_web; +# #[macro_use] extern crate serde_derive; +# use actix_web::*; +# +# fn index(req: HttpRequest) -> httpcodes::StaticResponse { +# httpcodes::HTTPOk +# } +fn main() { + let app = Application::new("/") + .resource("/resource/", |r| r.f(index)) + .default_resource(|r| r.h(NormalizePath::default())) + .finish(); +} +``` + +In this example `/resource`, `//resource///` will be redirected to `/resource/` url. From caca907c231302828763533eea1f436a65b04324 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Dec 2017 14:06:22 -0800 Subject: [PATCH 068/279] update guide --- guide/src/qs_5.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index 0495f9b45..b5a9b4d01 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -222,3 +222,26 @@ fn main() { ``` In this example `/resource`, `//resource///` will be redirected to `/resource/` url. + +In this example path normalization handler get registered for all method, +but you should not rely on this mechanism to redirect *POST* requests. The redirect of the +slash-appending *Not Found* will turn a *POST* request into a GET, losing any +*POST* data in the original request. + +It is possible to register path normalization only for *GET* requests only + +```rust +# extern crate actix_web; +# #[macro_use] extern crate serde_derive; +# use actix_web::*; +# +# fn index(req: HttpRequest) -> httpcodes::StaticResponse { +# httpcodes::HTTPOk +# } +fn main() { + let app = Application::new("/") + .resource("/resource/", |r| r.f(index)) + .default_resource(|r| r.method(Method::GET).h(NormalizePath::default())) + .finish(); +} +``` From 0f75d066f2412dc8ce2a643a85481d7d050f2673 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Dec 2017 14:16:29 -0800 Subject: [PATCH 069/279] simplify Application creation; update url dispatch guide section --- examples/basic.rs | 2 +- examples/state.rs | 2 +- examples/websocket.rs | 2 +- guide/src/SUMMARY.md | 2 +- guide/src/qs_10.md | 4 +- guide/src/qs_12.md | 4 +- guide/src/qs_2.md | 4 +- guide/src/qs_3.md | 21 +- guide/src/qs_4.md | 6 +- guide/src/qs_4_5.md | 8 +- guide/src/qs_5.md | 525 +++++++++++++++++++++++------- guide/src/qs_6.md | 2 +- guide/src/qs_7.md | 4 +- src/application.rs | 65 +++- src/fs.rs | 2 +- src/handler.rs | 10 +- src/info.rs | 1 + src/middlewares/defaultheaders.rs | 2 +- src/middlewares/logger.rs | 2 +- src/pred.rs | 2 +- src/resource.rs | 24 +- src/route.rs | 17 +- src/ws.rs | 2 +- tests/test_server.rs | 6 +- 24 files changed, 512 insertions(+), 207 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index c77d4adf6..53dda877d 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -57,7 +57,7 @@ fn main() { let sys = actix::System::new("ws-example"); HttpServer::new( - Application::new("/") + Application::new() // enable logger .middleware(middlewares::Logger::default()) // cookie session middleware diff --git a/examples/state.rs b/examples/state.rs index c199dd5e5..8d489dcf5 100644 --- a/examples/state.rs +++ b/examples/state.rs @@ -60,7 +60,7 @@ fn main() { let sys = actix::System::new("ws-example"); HttpServer::new( - Application::with_state("/", AppState{counter: Cell::new(0)}) + Application::with_state(AppState{counter: Cell::new(0)}) // enable logger .middleware(middlewares::Logger::default()) // websocket route diff --git a/examples/websocket.rs b/examples/websocket.rs index 124e4526e..cb644753c 100644 --- a/examples/websocket.rs +++ b/examples/websocket.rs @@ -61,7 +61,7 @@ fn main() { let sys = actix::System::new("ws-example"); HttpServer::new( - Application::new("/") + Application::new() // enable logger .middleware(middlewares::Logger::default()) // websocket route diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 17ce202e9..a9befac89 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -6,7 +6,7 @@ - [Handler](./qs_4.md) - [Errors](./qs_4_5.md) - [State](./qs_6.md) -- [Resources and Routes](./qs_5.md) +- [URL Dispatch](./qs_5.md) - [Request & Response](./qs_7.md) - [WebSockets](./qs_9.md) - [Middlewares](./qs_10.md) diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index 664106f99..951051c4d 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -20,7 +20,7 @@ use actix_web::Application; use actix_web::middlewares::Logger; fn main() { - Application::new("/") + Application::new() .middleware(Logger::default()) .middleware(Logger::new("%a %{User-Agent}i")) .finish(); @@ -71,7 +71,7 @@ Tto set default response headers `DefaultHeaders` middleware could be used. use actix_web::*; fn main() { - let app = Application::new("/") + let app = Application::new() .middleware( middlewares::DefaultHeaders::build() .header("X-Version", "0.2") diff --git a/guide/src/qs_12.md b/guide/src/qs_12.md index 4d415baf5..5900e4172 100644 --- a/guide/src/qs_12.md +++ b/guide/src/qs_12.md @@ -16,7 +16,7 @@ fn index(req: HttpRequest) -> Result { } fn main() { - Application::new("/") + Application::new() .resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index)) .finish(); } @@ -34,7 +34,7 @@ And this name has to be used in `StaticFile` constructor. use actix_web::*; fn main() { - Application::new("/") + Application::new() .resource("/static/{tail:.*}", |r| r.h(fs::StaticFiles::new("tail", ".", true))) .finish(); } diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md index cd32a87be..719f24cc0 100644 --- a/guide/src/qs_2.md +++ b/guide/src/qs_2.md @@ -48,7 +48,7 @@ request handler with the application's `resource` on a particular *HTTP method* # "Hello world!" # } # fn main() { - let app = Application::new("/") + let app = Application::new() .resource("/", |r| r.method(Method::GET).f(index)) .finish(); # } @@ -79,7 +79,7 @@ fn main() { let sys = actix::System::new("example"); HttpServer::new( - Application::new("/") + Application::new() .resource("/", |r| r.f(index))) .serve::<_, ()>("127.0.0.1:8088").unwrap(); diff --git a/guide/src/qs_3.md b/guide/src/qs_3.md index 2b954045e..d826998c1 100644 --- a/guide/src/qs_3.md +++ b/guide/src/qs_3.md @@ -5,8 +5,8 @@ It provides routing, middlewares, pre-processing of requests, and post-processin websocket protcol handling, multipart streams, etc. All actix web server is built around `Application` instance. -It is used for registering handlers for routes and resources, middlewares. -Also it stores applicationspecific state that is shared accross all handlers +It is used for registering routes for resources, middlewares. +Also it stores application specific state that is shared accross all handlers within same application. Application acts as namespace for all routes, i.e all routes for specific application @@ -20,7 +20,8 @@ has same url path prefix: # "Hello world!" # } # fn main() { - let app = Application::new("/prefix") + let app = Application::new() + .prefix("/prefix") .resource("/index.html", |r| r.method(Method::GET).f(index)) .finish() # } @@ -28,23 +29,27 @@ has same url path prefix: In this example application with `/prefix` prefix and `index.html` resource get created. This resource is available as on `/prefix/index.html` url. +For more information check +[*URL Matching*](./qs_5.html#using-a-application-prefix-to-compose-applications) section. Multiple applications could be served with one server: ```rust # extern crate actix_web; # extern crate tokio_core; -use std::net::SocketAddr; +# use tokio_core::net::TcpStream; +# use std::net::SocketAddr; use actix_web::*; -use tokio_core::net::TcpStream; fn main() { HttpServer::::new(vec![ - Application::new("/app1") + Application::new() + .prefix("/app1") .resource("/", |r| r.f(|r| httpcodes::HTTPOk)), - Application::new("/app2") + Application::new() + .prefix("/app2") .resource("/", |r| r.f(|r| httpcodes::HTTPOk)), - Application::new("/") + Application::new() .resource("/", |r| r.f(|r| httpcodes::HTTPOk)), ]); } diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index acab60d71..354cac122 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -81,7 +81,7 @@ fn main() { let sys = actix::System::new("example"); HttpServer::new( - Application::new("/") + Application::new() .resource("/", |r| r.method(Method::GET).f(index))) .serve::<_, ()>("127.0.0.1:8088").unwrap(); @@ -115,7 +115,7 @@ fn index(req: HttpRequest) -> FutureResult { } fn main() { - Application::new("/") + Application::new() .resource("/async", |r| r.route().a(index)) .finish(); } @@ -140,7 +140,7 @@ fn index(req: HttpRequest) -> HttpResponse { } fn main() { - Application::new("/") + Application::new() .resource("/async", |r| r.f(index)) .finish(); } diff --git a/guide/src/qs_4_5.md b/guide/src/qs_4_5.md index ad8154366..6c2863e4c 100644 --- a/guide/src/qs_4_5.md +++ b/guide/src/qs_4_5.md @@ -27,7 +27,7 @@ fn index(req: HttpRequest) -> io::Result { } # # fn main() { -# Application::new("/") +# Application::new() # .resource(r"/a/index.html", |r| r.f(index)) # .finish(); # } @@ -57,7 +57,7 @@ fn index(req: HttpRequest) -> Result<&'static str, MyError> { } # # fn main() { -# Application::new("/") +# Application::new() # .resource(r"/a/index.html", |r| r.f(index)) # .finish(); # } @@ -99,7 +99,7 @@ fn index(req: HttpRequest) -> Result<&'static str, MyError> { } # # fn main() { -# Application::new("/") +# Application::new() # .resource(r"/a/index.html", |r| r.f(index)) # .finish(); # } @@ -126,7 +126,7 @@ fn index(req: HttpRequest) -> Result<&'static str> { Ok(result.map_err(error::ErrorBadRequest)?) } # fn main() { -# Application::new("/") +# Application::new() # .resource(r"/a/index.html", |r| r.f(index)) # .finish(); # } diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index b5a9b4d01..33b107099 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -1,115 +1,251 @@ -# Resources and Routes +# URL Dispatch -All resources and routes register for specific application. -Application routes incoming requests based on route criteria which is defined during -resource registration or path prefix for simple handlers. -Internally *router* is a list of *resources*. Resource is an entry in *route table* -which corresponds to requested URL. +URL dispatch provides a simple way to map URLs to `Handler` code using a simple pattern matching +language. *Regex* crate and it's +[*RegexSet*](https://doc.rust-lang.org/regex/regex/struct.RegexSet.html) is beeing used for +pattern matching. If one of the patterns matches the path information associated with a request, +a particular handler object is invoked. A handler is a specific object that implements +`Handler` trait, defined in your application, that receives the request and returns +a response object. More informatin is available in [handler section](../qs_4.html). -Prefix handler: +## Resource configuration + +Resource configuraiton is the act of adding a new resource to an application. +A resource has a name, which acts as an identifier to be used for URL generation. +The name also allows developers to add routes to existing resources. +A resource also has a pattern, meant to match against the *PATH* portion of a *URL* +(the portion following the scheme and port, e.g., */foo/bar* in the +*URL* *http://localhost:8080/foo/bar*). + +The [Application::resource](../actix_web/struct.Application.html#method.resource) methods +add a single resource to application routing table. This method accepts *path pattern* +and resource configuration funnction. ```rust # extern crate actix_web; # use actix_web::*; +# use actix_web::httpcodes::*; # -fn index(req: HttpRequest) -> HttpResponse { - unimplemented!() -} - -fn main() { - Application::new("/") - .resource("/prefix", |r| r.f(index)) - .finish(); -} -``` - -In this example `index` get called for any url which starts with `/prefix`. - -Application prefix combines with handler prefix i.e - -```rust -# extern crate actix_web; -# use actix_web::*; -# -fn index(req: HttpRequest) -> HttpResponse { - unimplemented!() -} - -fn main() { - Application::new("/app") - .resource("/prefix", |r| r.f(index)) - .finish(); -} -``` - -In this example `index` get called for any url which starts with`/app/prefix`. - -Resource contains set of route for same endpoint. Route corresponds to handling -*HTTP method* by calling *web handler*. Resource select route based on *http method*, -if no route could be matched default response `HTTPMethodNotAllowed` get resturned. - -```rust -# extern crate actix_web; -# use actix_web::*; -# -fn main() { - Application::new("/") - .resource("/prefix", |r| { - r.method(Method::GET).h(httpcodes::HTTPOk); - r.method(Method::POST).h(httpcodes::HTTPForbidden); - }) - .finish(); -} -``` - -[`ApplicationBuilder::resource()` method](../actix_web/dev/struct.ApplicationBuilder.html#method.resource) -accepts configuration function, resource could be configured at once. -Check [`Resource`](../actix-web/target/doc/actix_web/struct.Resource.html) documentation -for more information. - -## Variable resources - -Resource may have *variable path*also. For instance, a resource with the -path '/a/{name}/c' would match all incoming requests with paths such -as '/a/b/c', '/a/1/c', and '/a/etc/c'. - -A *variable part* is specified in the form {identifier}, where the identifier can be -used later in a request handler to access the matched value for that part. This is -done by looking up the identifier in the `HttpRequest.match_info` object: - -```rust -# extern crate actix_web; -use actix_web::*; - -fn index(req: HttpRequest) -> String { - format!("Hello, {}", &req.match_info()["name"]) -} - -fn main() { - Application::new("/") - .resource("/{name}", |r| r.method(Method::GET).f(index)) - .finish(); -} -``` - -By default, each part matches the regular expression `[^{}/]+`. - -You can also specify a custom regex in the form `{identifier:regex}`: - -```rust -# extern crate actix_web; -# use actix_web::*; -# fn index(req: HttpRequest) -> String { -# format!("Hello, {}", &req.match_info()["name"]) +# fn index(req: HttpRequest) -> HttpResponse { +# unimplemented!() # } # fn main() { - Application::new("/") - .resource(r"{name:\d+}", |r| r.method(Method::GET).f(index)) + Application::new() + .resource("/prefix", |r| r.f(index)) + .resource("/user/{name}", + |r| r.method(Method::GET).f(|req| HTTPOk)) .finish(); } ``` +*Configuraiton function* has following type: + +```rust,ignore + FnOnce(&mut Resource<_>) -> () +``` + +*Configration function* can set name and register specific routes. +If resource does not contain any route or does not have any matching routes it +returns *NOT FOUND* http resources. + +## Configuring a Route + +Resource contains set of routes. Each route in turn has set of predicates and handler. +New route could be crearted with `Resource::route()` method which returns reference +to new *Route* instance. By default *route* does not contain any predicates, so matches +all requests and default handler is `HTTPNotFound`. + +Application routes incoming requests based on route criteria which is defined during +resource registration and route registration. Resource matches all routes it contains in +the order that the routes were registered via `Resource::route()`. *Route* can contain +any number of *predicates* but only one handler. + +```rust +# extern crate actix_web; +# use actix_web::*; +# use actix_web::httpcodes::*; + +fn main() { + Application::new() + .resource("/path", |resource| + resource.route() + .p(pred::Get()) + .p(pred::Header("content-type", "text/plain")) + .f(|req| HTTPOk) + ) + .finish(); +} +``` + +In this example `index` get called for *GET* request, +if request contains `Content-Type` header and value of this header is *text/plain* +and path equals to `/test`. Resource calls handle of the first matches route. +If resource can not match any route "NOT FOUND" response get returned. + +## Route matching + +The main purpose of route configuration is to match (or not match) the request's `path` +against a URL path pattern. `path` represents the path portion of the URL that was requested. + +The way that *actix* does this is very simple. When a request enters the system, +for each resource configuration registration present in the system, actix checks +the request's path against the pattern declared. *Regex* crate and it's +[*RegexSet*](https://doc.rust-lang.org/regex/regex/struct.RegexSet.html) is beeing used for +pattern matching. If resource could not be found, *default resource* get used as matched +resource. + +When a route configuration is declared, it may contain route predicate arguments. All route +predicates associated with a route declaration must be `true` for the route configuration to +be used for a given request during a check. If any predicate in the set of route predicate +arguments provided to a route configuration returns `false` during a check, that route is +skipped and route matching continues through the ordered set of routes. + +If any route matches, the route matching process stops and the handler associated with +route get invoked. + +If no route matches after all route patterns are exhausted, *NOT FOUND* response get returned. + +## Resource pattern syntax + +The syntax of the pattern matching language used by the actix in the pattern +argument is straightforward. + +The pattern used in route configuration may start with a slash character. If the pattern +does not start with a slash character, an implicit slash will be prepended +to it at matching time. For example, the following patterns are equivalent: + +``` +{foo}/bar/baz +``` + +and: + +``` +/{foo}/bar/baz +``` + +A *variable part*(replacement marker) is specified in the form *{identifier}*, +where this means "accept any characters up to the next slash character and use this +as the name in the `HttpRequest.match_info` object". + +A replacement marker in a pattern matches the regular expression `[^{}/]+`. + +A match_info is the `Params` object representing the dynamic parts extracted from a +*URL* based on the routing pattern. It is available as *request.match_info*. For example, the +following pattern defines one literal segment (foo) and two replacement markers (baz, and bar): + +``` +foo/{baz}/{bar} +``` + +The above pattern will match these URLs, generating the following match information: + +``` +foo/1/2 -> Params {'baz':'1', 'bar':'2'} +foo/abc/def -> Params {'baz':'abc', 'bar':'def'} +``` + +It will not match the following patterns however: + +``` +foo/1/2/ -> No match (trailing slash) +bar/abc/def -> First segment literal mismatch +``` + +The match for a segment replacement marker in a segment will be done only up to +the first non-alphanumeric character in the segment in the pattern. So, for instance, +if this route pattern was used: + +``` +foo/{name}.html +``` + +The literal path */foo/biz.html* will match the above route pattern, and the match result +will be `Params{'name': 'biz'}`. However, the literal path */foo/biz* will not match, +because it does not contain a literal *.html* at the end of the segment represented +by *{name}.html* (it only contains biz, not biz.html). + +To capture both segments, two replacement markers can be used: + +``` +foo/{name}.{ext} +``` + +The literal path */foo/biz.html* will match the above route pattern, and the match +result will be *Params{'name': 'biz', 'ext': 'html'}*. This occurs because there is a +literal part of *.* (period) between the two replacement markers *{name}* and *{ext}*. + +Replacement markers can optionally specify a regular expression which will be used to decide +whether a path segment should match the marker. To specify that a replacement marker should +match only a specific set of characters as defined by a regular expression, you must use a +slightly extended form of replacement marker syntax. Within braces, the replacement marker +name must be followed by a colon, then directly thereafter, the regular expression. The default +regular expression associated with a replacement marker *[^/]+* matches one or more characters +which are not a slash. For example, under the hood, the replacement marker *{foo}* can more +verbosely be spelled as *{foo:[^/]+}*. You can change this to be an arbitrary regular expression +to match an arbitrary sequence of characters, such as *{foo:\d+}* to match only digits. + +Segments must contain at least one character in order to match a segment replacement marker. +For example, for the URL */abc/*: + +* */abc/{foo}* will not match. +* */{foo}/* will match. + +Note that path will be URL-unquoted and decoded into valid unicode string before +matching pattern and values representing matched path segments will be URL-unquoted too. +So for instance, the following pattern: + +``` +foo/{bar} +``` + +When matching the following URL: + +``` +http://example.com/foo/La%20Pe%C3%B1a +``` + +The matchdict will look like so (the value is URL-decoded): + +``` +Params{'bar': 'La Pe\xf1a'} +``` + +Literal strings in the path segment should represent the decoded value of the +path provided to actix. You don't want to use a URL-encoded value in the pattern. +For example, rather than this: + +``` +/Foo%20Bar/{baz} +``` + +You'll want to use something like this: + +``` +/Foo Bar/{baz} +``` + +It is possible to get "tail match". For this purpose custom regex has to be used. + +``` +foo/{bar}/{tail:.*} +``` + +The above pattern will match these URLs, generating the following match information: + +``` +foo/1/2/ -> Params{'bar':'1', 'tail': '2/'} +foo/abc/def/a/b/c -> Params{'bar':u'abc', 'tail': 'def/a/b/c'} +``` + +## Match information + +All values representing matched path segments are available in +[`HttpRequest::match_info`](../actix_web/struct.HttpRequest.html#method.match_info). +Specific value can be received with +[`Params::get()`](../actix_web/dev/struct.Params.html#method.get) method. + Any matched parameter can be deserialized into specific type if this type implements `FromParam` trait. For example most of standard integer types implements `FromParam` trait. i.e.: @@ -125,7 +261,7 @@ fn index(req: HttpRequest) -> Result { } fn main() { - Application::new("/") + Application::new() .resource(r"/a/{v1}/{v2}/", |r| r.f(index)) .finish(); } @@ -133,25 +269,6 @@ fn main() { For this example for path '/a/1/2/', values v1 and v2 will resolve to "1" and "2". -It is possible to match path tail with custom `.*` regex. - -```rust -# extern crate actix_web; -# use actix_web::*; -# -# fn index(req: HttpRequest) -> HttpResponse { -# unimplemented!() -# } -fn main() { - Application::new("/") - .resource(r"/test/{tail:.*}", |r| r.method(Method::GET).f(index)) - .finish(); -} -``` - -Above example would match all incoming requests with path such as -'/test/b/c', '/test/index.html', and '/test/etc/test'. - It is possible to create a `PathBuf` from a tail path parameter. The returned `PathBuf` is percent-decoded. If a segment is equal to "..", the previous segment (if any) is skipped. @@ -179,13 +296,63 @@ fn index(req: HttpRequest) -> Result { } fn main() { - Application::new("/") + Application::new() .resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index)) .finish(); } ``` -### Path normalization +List of `FromParam` implementation could be found in +[api docs](../actix_web/dev/trait.FromParam.html#foreign-impls) + +## Generating resource URLs + +Use the [HttpRequest.url_for()](../actix_web/struct.HttpRequest.html#method.url_for) +method to generate URLs based on resource patterns. For example, if you've configured a +resource with the name "foo" and the pattern "{a}/{b}/{c}", you might do this. + +```rust +# extern crate actix_web; +# use actix_web::*; +# use actix_web::httpcodes::*; +# +fn index(req: HttpRequest) -> HttpResponse { + let url = req.url_for("foo", &["1", "2", "3"]); + HTTPOk.into() +} +# fn main() {} +``` + +This would return something like the string *http://example.com/1/2/3* (at least if +the current protocol and hostname implied http://example.com). +`url_for()` method return [*Url object*](https://docs.rs/url/1.6.0/url/struct.Url.html) so you +can modify this url (add query parameters, anchor, etc). +`url_for()` could be called only for *named* resources otherwise error get returned. + +## External resources + +Resources that are valid URLs, could be registered as external resources. They are useful +for URL generation purposes only and are never considered for matching at request time. + +```rust +# extern crate actix_web; +use actix_web::*; + +fn index(mut req: HttpRequest) -> Result { + let url = req.url_for("youtube", &["oHg5SJYRHA0"])?; + assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); + Ok(httpcodes::HTTPOk.into()) +} + +fn main() { + let app = Application::new() + .resource("/index.html", |r| r.f(index)) + .external_resource("youtube", "https://youtube.com/watch/{video_id}") + .finish(); +} +``` + +## Path normalization and redirecting to slash-appended routes By normalizing it means: @@ -214,7 +381,7 @@ This handler designed to be use as a handler for application's *default resource # httpcodes::HTTPOk # } fn main() { - let app = Application::new("/") + let app = Application::new() .resource("/resource/", |r| r.f(index)) .default_resource(|r| r.h(NormalizePath::default())) .finish(); @@ -239,9 +406,123 @@ It is possible to register path normalization only for *GET* requests only # httpcodes::HTTPOk # } fn main() { - let app = Application::new("/") + let app = Application::new() .resource("/resource/", |r| r.f(index)) .default_resource(|r| r.method(Method::GET).h(NormalizePath::default())) .finish(); } ``` + +## Using a Application Prefix to Compose Applications + +The `Applicaiton::prefix()`" method allows to set specific application prefix. +If route_prefix is supplied to the include method, it must be a string. +This prefix represents a resource prefix that will be prepended to all resource patterns added +by the resource configuration. This can be used to help mount a set of routes at a different +location than the included callable's author intended while still maintaining the same +resource names. + +For example: + +```rust +# extern crate actix_web; +# use actix_web::*; +# +fn show_users(req: HttpRequest) -> HttpResponse { + unimplemented!() +} + +fn main() { + Application::new() + .prefix("/users") + .resource("/show", |r| r.f(show_users)) + .finish(); +} +``` + +In the above example, the *show_users* route will have an effective route pattern of +*/users/show* instead of */show* because the application's prefix argument will be prepended +to the pattern. The route will then only match if the URL path is /users/show, +and when the `HttpRequest.url_for()` function is called with the route name show_users, +it will generate a URL with that same path. + +## Custom route predicates + +You can think of predicate as simple function that accept *request* object reference +and returns *true* or *false*. Formally predicate is any object that implements +[`Predicate`](../actix_web/pred/trait.Predicate.html) trait. Actix provides +several predicates, you can check [functions section](../actix_web/pred/index.html#functions) +of api docs. + +Here is simple predicates that check that request contains specific *header* and predicate +usage: + +```rust +# extern crate actix_web; +# extern crate http; +# use actix_web::*; +# use actix_web::httpcodes::*; +use http::header::CONTENT_TYPE; +use actix_web::pred::Predicate; + +struct ContentTypeHeader; + +impl Predicate for ContentTypeHeader { + + fn check(&self, req: &mut HttpRequest) -> bool { + req.headers().contains_key(CONTENT_TYPE) + } +} + +fn main() { + Application::new() + .resource("/index.html", |r| + r.route() + .p(Box::new(ContentTypeHeader)) + .f(|req| HTTPOk)) + .finish(); +} +``` + +In this example *index* handler will be called only if request contains *CONTENT-TYPE* header. + +Predicates can have access to application's state via `HttpRequest::state()` method. +Also predicates can store extra information in +[requests`s extensions](../actix_web/struct.HttpRequest.html#method.extensions). + +### Modifing predicate values + +You can invert the meaning of any predicate value by wrapping it in a `Not` predicate. +For example if you want to return "METHOD NOT ALLOWED" response for all methods +except "GET": + +```rust +# extern crate actix_web; +# extern crate http; +# use actix_web::*; +# use actix_web::httpcodes::*; +use actix_web::pred; + +fn main() { + Application::new() + .resource("/index.html", |r| + r.route() + .p(pred::Not(pred::Get())) + .f(|req| HTTPMethodNotAllowed)) + .finish(); +} +``` + +`Any` predicate accept list of predicates and matches if any of the supplied +predicates match. i.e: + +```rust,ignore + pred::Any(vec![pred::Get(), pred::Post()]) +``` + +`All` predicate accept list of predicates and matches if all of the supplied +predicates match. i.e: + +```rust,ignore + pred::All(vec![pred::Get(), pred::Header("content-type", "plain/text")]) +``` diff --git a/guide/src/qs_6.md b/guide/src/qs_6.md index 73a342ca3..f7c889464 100644 --- a/guide/src/qs_6.md +++ b/guide/src/qs_6.md @@ -30,7 +30,7 @@ fn index(req: HttpRequest) -> String { } fn main() { - Application::with_state("/", AppState{counter: Cell::new(0)}) + Application::with_state(AppState{counter: Cell::new(0)}) .resource("/", |r| r.method(Method::GET).f(index)) .finish(); } diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index e65905f86..3e3fd8f7c 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -1,4 +1,4 @@ -# HttpRequest & HttpResponse +# Request & Response ## Response @@ -77,7 +77,7 @@ fn index(req: HttpRequest) -> Result> { } fn main() { - Application::new("/") + Application::new() .resource(r"/a/{name}", |r| r.method(Method::GET).f(index)) .finish(); } diff --git a/src/application.rs b/src/application.rs index 4d18cb6fa..b094292b3 100644 --- a/src/application.rs +++ b/src/application.rs @@ -69,13 +69,11 @@ impl Application<()> { /// Create application with empty state. Application can /// be configured with builder-like pattern. - /// - /// This method accepts path prefix for which it should serve requests. - pub fn new>(prefix: T) -> Application<()> { + pub fn new() -> Application<()> { Application { parts: Some(ApplicationParts { state: (), - prefix: prefix.into(), + prefix: "/".to_owned(), default: Resource::default_not_found(), resources: HashMap::new(), external: HashMap::new(), @@ -85,6 +83,12 @@ impl Application<()> { } } +impl Default for Application<()> { + fn default() -> Self { + Application::new() + } +} + impl Application where S: 'static { /// Create application with specific state. Application can be @@ -92,11 +96,11 @@ impl Application where S: 'static { /// /// State is shared with all reousrces within same application and could be /// accessed with `HttpRequest::state()` method. - pub fn with_state>(prefix: T, state: S) -> Application { + pub fn with_state(state: S) -> Application { Application { parts: Some(ApplicationParts { state: state, - prefix: prefix.into(), + prefix: "/".to_owned(), default: Resource::default_not_found(), resources: HashMap::new(), external: HashMap::new(), @@ -105,6 +109,42 @@ impl Application where S: 'static { } } + /// Set application prefix. + /// + /// Only requests that matches application's prefix get processed by this application. + /// Application prefix always contains laading "/" slash. If supplied prefix + /// does not contain leading slash, it get inserted. + /// + /// Inthe following example only requests with "/app/" path prefix + /// get handled. Request with path "/app/test/" will be handled, + /// but request with path "/other/..." will return *NOT FOUND* + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::*; + /// + /// fn main() { + /// let app = Application::new() + /// .prefix("/app") + /// .resource("/test", |r| { + /// r.method(Method::GET).f(|_| httpcodes::HTTPOk); + /// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed); + /// }) + /// .finish(); + /// } + /// ``` + pub fn prefix>(&mut self, prefix: P) -> &mut Self { + { + let parts = self.parts.as_mut().expect("Use after finish"); + let mut prefix = prefix.into(); + if !prefix.starts_with('/') { + prefix.insert(0, '/') + } + parts.prefix = prefix; + } + self + } + /// Configure resource for specific path. /// /// Resource may have variable path also. For instance, a resource with @@ -128,7 +168,7 @@ impl Application where S: 'static { /// use actix_web::*; /// /// fn main() { - /// let app = Application::new("/") + /// let app = Application::new() /// .resource("/test", |r| { /// r.method(Method::GET).f(|_| httpcodes::HTTPOk); /// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed); @@ -184,7 +224,7 @@ impl Application where S: 'static { /// } /// /// fn main() { - /// let app = Application::new("/") + /// let app = Application::new() /// .resource("/index.html", |r| r.f(index)) /// .external_resource("youtube", "https://youtube.com/watch/{video_id}") /// .finish(); @@ -275,7 +315,7 @@ mod tests { #[test] fn test_default_resource() { - let app = Application::new("/") + let app = Application::new() .resource("/test", |r| r.h(httpcodes::HTTPOk)) .finish(); @@ -291,7 +331,7 @@ mod tests { let resp = app.run(req); assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); - let app = Application::new("/") + let app = Application::new() .default_resource(|r| r.h(httpcodes::HTTPMethodNotAllowed)) .finish(); let req = HttpRequest::new( @@ -303,7 +343,8 @@ mod tests { #[test] fn test_unhandled_prefix() { - let app = Application::new("/test") + let app = Application::new() + .prefix("/test") .resource("/test", |r| r.h(httpcodes::HTTPOk)) .finish(); assert!(app.handle(HttpRequest::default()).is_err()); @@ -311,7 +352,7 @@ mod tests { #[test] fn test_state() { - let app = Application::with_state("/", 10) + let app = Application::with_state(10) .resource("/", |r| r.h(httpcodes::HTTPOk)) .finish(); let req = HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone()); diff --git a/src/fs.rs b/src/fs.rs index 963cf2fc8..736d9910e 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -199,7 +199,7 @@ impl FromRequest for FilesystemElement { /// use actix_web::{fs, Application}; /// /// fn main() { -/// let app = Application::new("/") +/// let app = Application::new() /// .resource("/static/{tail:.*}", |r| r.h(fs::StaticFiles::new("tail", ".", true))) /// .finish(); /// } diff --git a/src/handler.rs b/src/handler.rs index 24cb15a7c..241eabf3c 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -303,7 +303,7 @@ impl FromRequest for Json { /// # httpcodes::HTTPOk /// # } /// fn main() { -/// let app = Application::new("/") +/// let app = Application::new() /// .resource("/test/", |r| r.f(index)) /// .default_resource(|r| r.h(NormalizePath::default())) /// .finish(); @@ -412,7 +412,7 @@ mod tests { #[test] fn test_normalize_path_trailing_slashes() { - let app = Application::new("/") + let app = Application::new() .resource("/resource1", |r| r.method(Method::GET).f(index)) .resource("/resource2/", |r| r.method(Method::GET).f(index)) .default_resource(|r| r.h(NormalizePath::default())) @@ -444,7 +444,7 @@ mod tests { #[test] fn test_normalize_path_trailing_slashes_disabled() { - let app = Application::new("/") + let app = Application::new() .resource("/resource1", |r| r.method(Method::GET).f(index)) .resource("/resource2/", |r| r.method(Method::GET).f(index)) .default_resource(|r| r.h( @@ -471,7 +471,7 @@ mod tests { #[test] fn test_normalize_path_merge_slashes() { - let app = Application::new("/") + let app = Application::new() .resource("/resource1", |r| r.method(Method::GET).f(index)) .resource("/resource1/a/b", |r| r.method(Method::GET).f(index)) .default_resource(|r| r.h(NormalizePath::default())) @@ -507,7 +507,7 @@ mod tests { #[test] fn test_normalize_path_merge_and_append_slashes() { - let app = Application::new("/") + let app = Application::new() .resource("/resource1", |r| r.method(Method::GET).f(index)) .resource("/resource2/", |r| r.method(Method::GET).f(index)) .resource("/resource1/a/b", |r| r.method(Method::GET).f(index)) diff --git a/src/info.rs b/src/info.rs index 190ce0c5c..76cf678ea 100644 --- a/src/info.rs +++ b/src/info.rs @@ -21,6 +21,7 @@ pub struct ConnectionInfo<'a> { impl<'a> ConnectionInfo<'a> { /// Create *ConnectionInfo* instance for a request. + #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] pub fn new(req: &'a HttpRequest) -> ConnectionInfo<'a> { let mut host = None; let mut scheme = None; diff --git a/src/middlewares/defaultheaders.rs b/src/middlewares/defaultheaders.rs index e1a797a23..a0b772e90 100644 --- a/src/middlewares/defaultheaders.rs +++ b/src/middlewares/defaultheaders.rs @@ -15,7 +15,7 @@ use middlewares::{Response, Middleware}; /// use actix_web::*; /// /// fn main() { -/// let app = Application::new("/") +/// let app = Application::new() /// .middleware( /// middlewares::DefaultHeaders::build() /// .header("X-Version", "0.2") diff --git a/src/middlewares/logger.rs b/src/middlewares/logger.rs index 67e5c3ffa..57b12cf81 100644 --- a/src/middlewares/logger.rs +++ b/src/middlewares/logger.rs @@ -27,7 +27,7 @@ use middlewares::{Middleware, Started, Finished}; /// use actix_web::middlewares::Logger; /// /// fn main() { -/// let app = Application::new("/") +/// let app = Application::new() /// .middleware(Logger::default()) /// .middleware(Logger::new("%a %{User-Agent}i")) /// .finish(); diff --git a/src/pred.rs b/src/pred.rs index b3fc6f882..c907d2793 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -170,7 +170,7 @@ mod tests { let pred = Header("transfer-encoding", "other"); assert!(!pred.check(&mut req)); - let pred = Header("content-tye", "other"); + let pred = Header("content-type", "other"); assert!(!pred.check(&mut req)); } diff --git a/src/resource.rs b/src/resource.rs index f4677ad08..0053b84ed 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -2,8 +2,9 @@ use std::marker::PhantomData; use http::Method; +use pred; use route::Route; -use handler::{Reply, Handler, FromRequest, RouteHandler, WrapHandler}; +use handler::{Reply, Handler, FromRequest, RouteHandler}; use httpcodes::HTTPNotFound; use httprequest::HttpRequest; @@ -22,7 +23,7 @@ use httprequest::HttpRequest; /// use actix_web::*; /// /// fn main() { -/// let app = Application::new("/") +/// let app = Application::new() /// .resource( /// "/", |r| r.method(Method::GET).f(|r| HttpResponse::Ok())) /// .finish(); @@ -31,7 +32,6 @@ pub struct Resource { name: String, state: PhantomData, routes: Vec>, - default: Box>, } impl Default for Resource { @@ -39,8 +39,7 @@ impl Default for Resource { Resource { name: String::new(), state: PhantomData, - routes: Vec::new(), - default: Box::new(HTTPNotFound)} + routes: Vec::new() } } } @@ -50,8 +49,7 @@ impl Resource { Resource { name: String::new(), state: PhantomData, - routes: Vec::new(), - default: Box::new(HTTPNotFound)} + routes: Vec::new() } } /// Set resource name @@ -74,7 +72,7 @@ impl Resource { /// use actix_web::*; /// /// fn main() { - /// let app = Application::new("/") + /// let app = Application::new() /// .resource( /// "/", |r| r.route() /// .p(pred::Any(vec![pred::Get(), pred::Put()])) @@ -97,7 +95,7 @@ impl Resource { /// ``` pub fn method(&mut self, method: Method) -> &mut Route { self.routes.push(Route::default()); - self.routes.last_mut().unwrap().method(method) + self.routes.last_mut().unwrap().p(pred::Method(method)) } /// Register a new route and add handler object. @@ -126,12 +124,6 @@ impl Resource { self.routes.push(Route::default()); self.routes.last_mut().unwrap().f(handler) } - - /// Default handler is used if no matched route found. - /// By default `HTTPNotFound` is used. - pub fn default_handler(&mut self, handler: H) where H: Handler { - self.default = Box::new(WrapHandler::new(handler)); - } } impl RouteHandler for Resource { @@ -142,6 +134,6 @@ impl RouteHandler for Resource { return route.handle(req) } } - self.default.handle(req) + Reply::response(HTTPNotFound) } } diff --git a/src/route.rs b/src/route.rs index 64f037d8d..daea3cb32 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,8 +1,7 @@ -use http::Method; use futures::Future; use error::Error; -use pred::{self, Predicate}; +use pred::Predicate; use handler::{Reply, Handler, FromRequest, RouteHandler, AsyncHandler, WrapHandler}; use httpcodes::HTTPNotFound; use httprequest::HttpRequest; @@ -43,20 +42,6 @@ impl Route { self.handler.handle(req) } - /// Add method check to route. This method could be called multiple times. - pub fn method(&mut self, method: Method) -> &mut Self { - self.preds.push(pred::Method(method)); - self - } - - /// Add predicates to route. - pub fn predicates

    (&mut self, preds: P) -> &mut Self - where P: IntoIterator>> - { - self.preds.extend(preds.into_iter()); - self - } - /// Add match predicate to route. pub fn p(&mut self, p: Box>) -> &mut Self { self.preds.push(p); diff --git a/src/ws.rs b/src/ws.rs index 551dee622..2578cdc0d 100644 --- a/src/ws.rs +++ b/src/ws.rs @@ -42,7 +42,7 @@ //! } //! //! fn main() { -//! Application::new("/") +//! Application::new() //! .resource("/ws/", |r| r.method(Method::GET).f(ws_index)) // <- register websocket route //! .finish(); //! } diff --git a/tests/test_server.rs b/tests/test_server.rs index 1cc955867..35a3d76cc 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -16,7 +16,7 @@ fn test_serve() { thread::spawn(|| { let sys = System::new("test"); let srv = HttpServer::new( - vec![Application::new("/") + vec![Application::new() .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))]); srv.serve::<_, ()>("127.0.0.1:58902").unwrap(); sys.run(); @@ -36,7 +36,7 @@ fn test_serve_incoming() { let sys = System::new("test"); let srv = HttpServer::new( - Application::new("/") + Application::new() .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))); let tcp = TcpListener::from_listener(tcp, &addr2, Arbiter::handle()).unwrap(); srv.serve_incoming::<_, ()>(tcp.incoming(), false).unwrap(); @@ -84,7 +84,7 @@ fn test_middlewares() { let sys = System::new("test"); HttpServer::new( - vec![Application::new("/") + vec![Application::new() .middleware(MiddlewareTest{start: act_num1, response: act_num2, finish: act_num3}) From 96381f5d6aeec00b44293477656d35b37e7a893d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Dec 2017 14:27:09 -0800 Subject: [PATCH 070/279] fix doc --- guide/src/qs_10.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index 951051c4d..9af3301e1 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -63,8 +63,8 @@ INFO:actix_web::middlewares::logger: 127.0.0.1:59947 [02/Dec/2017:00:22:40 -0800 ## Default headers -Tto set default response headers `DefaultHeaders` middleware could be used. -*DefaultHeaders* middleware does not set header if response headers already contains it. +To set default response headers `DefaultHeaders` middleware could be used. +*DefaultHeaders* middleware does not set header if response headers contains header. ```rust # extern crate actix_web; From b1ae7f95cc304998de373a7cbdb0a73cd98f6ce0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Dec 2017 15:57:37 -0800 Subject: [PATCH 071/279] update readme example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9502c6501..275036489 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ fn index(req: HttpRequest) -> String { fn main() { HttpServer::new( - Application::new("/") + Application::new() .resource("/{name}", |r| r.method(Method::GET).f(index))) .serve::<_, ()>("127.0.0.1:8080"); } From 007b7ce62f1850d9e9710cb6cd721a246c57ea93 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Dec 2017 16:26:51 -0800 Subject: [PATCH 072/279] unify route not found handling --- guide/src/qs_5.md | 24 ++++++++++++++++++++++++ src/application.rs | 8 ++++---- src/resource.rs | 20 ++++++++++++-------- 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index 33b107099..1b9a707ea 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -526,3 +526,27 @@ predicates match. i.e: ```rust,ignore pred::All(vec![pred::Get(), pred::Header("content-type", "plain/text")]) ``` + +## Changing the default Not Found response + +If path pattern can not be found in routing table or resource can not find matching +route default resource is used. Default response is *NOT FOUND* response. +To override *NOT FOUND* resource use `Application::default_resource()` method. +This method accepts *configuration function* same as normal resource registration +with `Application::resource()` method. + +``rust +# extern crate actix_web; +# extern crate http; +use actix_web::*; +use actix_web::httpcodes::*; + +fn main() { + Application::new() + .default_resource(|r| + r.method(Method::GET).f(|req| HTTPNotFound); + r.route().p(pred::Not(pred::Get()).f(|req| HTTPMethodNotAllowed); + }) + .finish(); +} +``` diff --git a/src/application.rs b/src/application.rs index b094292b3..9da145f03 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,7 +1,7 @@ use std::rc::Rc; use std::collections::HashMap; -use handler::{Reply, RouteHandler}; +use handler::Reply; use router::{Router, Pattern}; use resource::Resource; use httprequest::HttpRequest; @@ -27,9 +27,9 @@ impl HttpApplication { pub(crate) fn run(&self, mut req: HttpRequest) -> Reply { if let Some(h) = self.router.recognize(&mut req) { - h.handle(req) + h.handle(req.clone(), Some(&self.default)) } else { - self.default.handle(req) + self.default.handle(req, None) } } } @@ -196,7 +196,7 @@ impl Application where S: 'static { self } - /// Default resource is used if no match route could be found. + /// Default resource is used if no matched route could be found. pub fn default_resource(&mut self, f: F) -> &mut Self where F: FnOnce(&mut Resource) + 'static { diff --git a/src/resource.rs b/src/resource.rs index 0053b84ed..523a30966 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,12 +1,13 @@ use std::marker::PhantomData; -use http::Method; +use http::{Method, StatusCode}; use pred; +use body::Body; use route::Route; -use handler::{Reply, Handler, FromRequest, RouteHandler}; -use httpcodes::HTTPNotFound; +use handler::{Reply, Handler, FromRequest}; use httprequest::HttpRequest; +use httpresponse::HttpResponse; /// *Resource* is an entry in route table which corresponds to requested URL. /// @@ -124,16 +125,19 @@ impl Resource { self.routes.push(Route::default()); self.routes.last_mut().unwrap().f(handler) } -} -impl RouteHandler for Resource { - - fn handle(&self, mut req: HttpRequest) -> Reply { + pub(crate) fn handle(&self, mut req: HttpRequest, default: Option<&Resource>) + -> Reply + { for route in &self.routes { if route.check(&mut req) { return route.handle(req) } } - Reply::response(HTTPNotFound) + if let Some(resource) = default { + resource.handle(req, None) + } else { + Reply::response(HttpResponse::new(StatusCode::NOT_FOUND, Body::Empty)) + } } } From 6e3f598c50529e264b503c8cce96a7ff4bc09769 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Dec 2017 16:50:51 -0800 Subject: [PATCH 073/279] fix guide page --- guide/src/qs_5.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index 1b9a707ea..6d30e7e19 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -535,7 +535,7 @@ To override *NOT FOUND* resource use `Application::default_resource()` method. This method accepts *configuration function* same as normal resource registration with `Application::resource()` method. -``rust +```rust # extern crate actix_web; # extern crate http; use actix_web::*; From d7efbb516d10826d050e2425354a2bb242a3f0bd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Dec 2017 19:16:45 -0800 Subject: [PATCH 074/279] fix guide tests --- guide/src/qs_5.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index 6d30e7e19..46b41edaa 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -543,9 +543,9 @@ use actix_web::httpcodes::*; fn main() { Application::new() - .default_resource(|r| + .default_resource(|r| { r.method(Method::GET).f(|req| HTTPNotFound); - r.route().p(pred::Not(pred::Get()).f(|req| HTTPMethodNotAllowed); + r.route().p(pred::Not(pred::Get())).f(|req| HTTPMethodNotAllowed); }) .finish(); } From b9da09ddf0afda189a8be05fb530f65cffb383a2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Dec 2017 19:17:37 -0800 Subject: [PATCH 075/279] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 275036489..b06b8af0c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ fn index(req: HttpRequest) -> String { fn main() { HttpServer::new( Application::new() - .resource("/{name}", |r| r.method(Method::GET).f(index))) + .resource("/{name}", |r| r.f(index))) .serve::<_, ()>("127.0.0.1:8080"); } ``` From e9aa67b75d8bb9c999032ea58fcc647c4ceccca7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 12 Dec 2017 07:40:36 -0800 Subject: [PATCH 076/279] http server accepts factory of HttpHandlers --- examples/basic.rs | 2 +- examples/state.rs | 3 ++- examples/websocket.rs | 2 +- guide/src/qs_2.md | 6 +++--- guide/src/qs_3.md | 2 +- guide/src/qs_4.md | 2 +- src/application.rs | 22 +++++++++++----------- src/server.rs | 7 ++++--- tests/test_server.rs | 19 ++++++++++--------- 9 files changed, 34 insertions(+), 31 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index 53dda877d..304eabd27 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -57,7 +57,7 @@ fn main() { let sys = actix::System::new("ws-example"); HttpServer::new( - Application::new() + || Application::new() // enable logger .middleware(middlewares::Logger::default()) // cookie session middleware diff --git a/examples/state.rs b/examples/state.rs index 8d489dcf5..aef09fc28 100644 --- a/examples/state.rs +++ b/examples/state.rs @@ -54,13 +54,14 @@ impl Handler for MyWebSocket { } } + fn main() { ::std::env::set_var("RUST_LOG", "actix_web=info"); let _ = env_logger::init(); let sys = actix::System::new("ws-example"); HttpServer::new( - Application::with_state(AppState{counter: Cell::new(0)}) + || Application::with_state(AppState{counter: Cell::new(0)}) // enable logger .middleware(middlewares::Logger::default()) // websocket route diff --git a/examples/websocket.rs b/examples/websocket.rs index cb644753c..cbcf91c11 100644 --- a/examples/websocket.rs +++ b/examples/websocket.rs @@ -61,7 +61,7 @@ fn main() { let sys = actix::System::new("ws-example"); HttpServer::new( - Application::new() + || Application::new() // enable logger .middleware(middlewares::Logger::default()) // websocket route diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md index 719f24cc0..1b28892e7 100644 --- a/guide/src/qs_2.md +++ b/guide/src/qs_2.md @@ -55,10 +55,10 @@ request handler with the application's `resource` on a particular *HTTP method* ``` After that, application instance can be used with `HttpServer` to listen for incoming -connections: +connections. Server accepts function that should return `HttpHandler` instance: ```rust,ignore - HttpServer::new(app).serve::<_, ()>("127.0.0.1:8088"); + HttpServer::new(|| app).serve::<_, ()>("127.0.0.1:8088"); ``` That's it. Now, compile and run the program with cargo run. @@ -79,7 +79,7 @@ fn main() { let sys = actix::System::new("example"); HttpServer::new( - Application::new() + || Application::new() .resource("/", |r| r.f(index))) .serve::<_, ()>("127.0.0.1:8088").unwrap(); diff --git a/guide/src/qs_3.md b/guide/src/qs_3.md index d826998c1..b6a83cf1a 100644 --- a/guide/src/qs_3.md +++ b/guide/src/qs_3.md @@ -42,7 +42,7 @@ Multiple applications could be served with one server: use actix_web::*; fn main() { - HttpServer::::new(vec![ + HttpServer::::new(|| vec![ Application::new() .prefix("/app1") .resource("/", |r| r.f(|r| httpcodes::HTTPOk)), diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index 354cac122..addb1541f 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -81,7 +81,7 @@ fn main() { let sys = actix::System::new("example"); HttpServer::new( - Application::new() + || Application::new() .resource("/", |r| r.method(Method::GET).f(index))) .serve::<_, ()>("127.0.0.1:8088").unwrap(); diff --git a/src/application.rs b/src/application.rs index 9da145f03..d51187993 100644 --- a/src/application.rs +++ b/src/application.rs @@ -19,7 +19,7 @@ pub struct HttpApplication { middlewares: Rc>>>, } -impl HttpApplication { +impl HttpApplication { pub(crate) fn prepare_request(&self, req: HttpRequest) -> HttpRequest { req.with_state(Rc::clone(&self.state), self.router.clone()) @@ -34,7 +34,7 @@ impl HttpApplication { } } -impl HttpHandler for HttpApplication { +impl HttpHandler for HttpApplication { fn handle(&self, req: HttpRequest) -> Result, HttpRequest> { if req.path().starts_with(&self.prefix) { @@ -89,7 +89,7 @@ impl Default for Application<()> { } } -impl Application where S: 'static { +impl Application where S: Send + 'static { /// Create application with specific state. Application can be /// configured with builder-like pattern. @@ -133,7 +133,7 @@ impl Application where S: 'static { /// .finish(); /// } /// ``` - pub fn prefix>(&mut self, prefix: P) -> &mut Self { + pub fn prefix>(mut self, prefix: P) -> Application { { let parts = self.parts.as_mut().expect("Use after finish"); let mut prefix = prefix.into(); @@ -176,7 +176,7 @@ impl Application where S: 'static { /// .finish(); /// } /// ``` - pub fn resource(&mut self, path: &str, f: F) -> &mut Self + pub fn resource(mut self, path: &str, f: F) -> Application where F: FnOnce(&mut Resource) + 'static { { @@ -197,7 +197,7 @@ impl Application where S: 'static { } /// Default resource is used if no matched route could be found. - pub fn default_resource(&mut self, f: F) -> &mut Self + pub fn default_resource(mut self, f: F) -> Application where F: FnOnce(&mut Resource) + 'static { { @@ -230,7 +230,7 @@ impl Application where S: 'static { /// .finish(); /// } /// ``` - pub fn external_resource(&mut self, name: T, url: U) -> &mut Self + pub fn external_resource(mut self, name: T, url: U) -> Application where T: AsRef, U: AsRef { { @@ -246,7 +246,7 @@ impl Application where S: 'static { } /// Register a middleware - pub fn middleware(&mut self, mw: T) -> &mut Self + pub fn middleware(mut self, mw: T) -> Application where T: Middleware + 'static { self.parts.as_mut().expect("Use after finish") @@ -274,7 +274,7 @@ impl Application where S: 'static { } } -impl IntoHttpHandler for Application { +impl IntoHttpHandler for Application { type Handler = HttpApplication; fn into_handler(mut self) -> HttpApplication { @@ -282,7 +282,7 @@ impl IntoHttpHandler for Application { } } -impl<'a, S: 'static> IntoHttpHandler for &'a mut Application { +impl<'a, S: Send + 'static> IntoHttpHandler for &'a mut Application { type Handler = HttpApplication; fn into_handler(self) -> HttpApplication { @@ -291,7 +291,7 @@ impl<'a, S: 'static> IntoHttpHandler for &'a mut Application { } #[doc(hidden)] -impl Iterator for Application { +impl Iterator for Application { type Item = HttpApplication; fn next(&mut self) -> Option { diff --git a/src/server.rs b/src/server.rs index 98e602428..a5862c34e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -95,10 +95,11 @@ impl Actor for HttpServer { impl HttpServer where H: HttpHandler { /// Create new http server with vec of http handlers - pub fn new>(handler: U) -> Self - where V: IntoHttpHandler + pub fn new>(factory: F) -> Self + where F: Fn() -> U + Send, + V: IntoHttpHandler { - let apps: Vec<_> = handler.into_iter().map(|h| h.into_handler()).collect(); + let apps: Vec<_> = factory().into_iter().map(|h| h.into_handler()).collect(); HttpServer{ h: Rc::new(apps), io: PhantomData, diff --git a/tests/test_server.rs b/tests/test_server.rs index 35a3d76cc..4a657bccd 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -4,6 +4,7 @@ extern crate tokio_core; extern crate reqwest; use std::{net, thread}; +use std::rc::Rc; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; use tokio_core::net::TcpListener; @@ -16,8 +17,8 @@ fn test_serve() { thread::spawn(|| { let sys = System::new("test"); let srv = HttpServer::new( - vec![Application::new() - .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))]); + || vec![Application::new() + .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))]); srv.serve::<_, ()>("127.0.0.1:58902").unwrap(); sys.run(); }); @@ -36,7 +37,7 @@ fn test_serve_incoming() { let sys = System::new("test"); let srv = HttpServer::new( - Application::new() + || Application::new() .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))); let tcp = TcpListener::from_listener(tcp, &addr2, Arbiter::handle()).unwrap(); srv.serve_incoming::<_, ()>(tcp.incoming(), false).unwrap(); @@ -51,6 +52,7 @@ struct MiddlewareTest { start: Arc, response: Arc, finish: Arc, + test: Rc, } impl middlewares::Middleware for MiddlewareTest { @@ -84,12 +86,11 @@ fn test_middlewares() { let sys = System::new("test"); HttpServer::new( - vec![Application::new() - .middleware(MiddlewareTest{start: act_num1, - response: act_num2, - finish: act_num3}) - .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk)) - .finish()]) + move || vec![Application::new() + .middleware(MiddlewareTest{start: act_num1.clone(), + response: act_num2.clone(), + finish: act_num3.clone(), test: Rc::new(1)}) + .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))]) .serve::<_, ()>("127.0.0.1:58904").unwrap(); sys.run(); }); From 55818028cbcbf0b6217bf298ba4d189b64abe6c7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 12 Dec 2017 08:51:16 -0800 Subject: [PATCH 077/279] state does not need to be Send --- src/application.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/application.rs b/src/application.rs index d51187993..3dd1b1fe8 100644 --- a/src/application.rs +++ b/src/application.rs @@ -19,7 +19,7 @@ pub struct HttpApplication { middlewares: Rc>>>, } -impl HttpApplication { +impl HttpApplication { pub(crate) fn prepare_request(&self, req: HttpRequest) -> HttpRequest { req.with_state(Rc::clone(&self.state), self.router.clone()) @@ -34,7 +34,7 @@ impl HttpApplication { } } -impl HttpHandler for HttpApplication { +impl HttpHandler for HttpApplication { fn handle(&self, req: HttpRequest) -> Result, HttpRequest> { if req.path().starts_with(&self.prefix) { @@ -89,7 +89,7 @@ impl Default for Application<()> { } } -impl Application where S: Send + 'static { +impl Application where S: 'static { /// Create application with specific state. Application can be /// configured with builder-like pattern. @@ -274,7 +274,7 @@ impl Application where S: Send + 'static { } } -impl IntoHttpHandler for Application { +impl IntoHttpHandler for Application { type Handler = HttpApplication; fn into_handler(mut self) -> HttpApplication { @@ -282,7 +282,7 @@ impl IntoHttpHandler for Application { } } -impl<'a, S: Send + 'static> IntoHttpHandler for &'a mut Application { +impl<'a, S: 'static> IntoHttpHandler for &'a mut Application { type Handler = HttpApplication; fn into_handler(self) -> HttpApplication { @@ -291,7 +291,7 @@ impl<'a, S: Send + 'static> IntoHttpHandler for &'a mut Application { } #[doc(hidden)] -impl Iterator for Application { +impl Iterator for Application { type Item = HttpApplication; fn next(&mut self) -> Option { From ab6efd2421e82e8edfb82f108c61ae996bd4510f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 12 Dec 2017 17:21:00 -0800 Subject: [PATCH 078/279] handle http connections in different threads --- Cargo.toml | 2 + examples/tls/src/main.rs | 4 +- guide/src/qs_3.md | 2 +- src/lib.rs | 2 + src/server.rs | 497 ++++++++++++++++++++++++++++++--------- tests/test_server.rs | 4 +- 6 files changed, 394 insertions(+), 117 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7af676c5b..014f7701b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ regex = "0.2" sha1 = "0.2" url = "1.5" libc = "0.2" +socket2 = "0.2" serde = "1.0" serde_json = "1.0" flate2 = "0.2" @@ -53,6 +54,7 @@ brotli2 = "^0.3.2" percent-encoding = "1.0" smallvec = "0.6" bitflags = "1.0" +num_cpus = "1.0" cookie = { version="0.10", features=["percent-encode", "secure"] } # tokio diff --git a/examples/tls/src/main.rs b/examples/tls/src/main.rs index d40719e56..78720c0c9 100644 --- a/examples/tls/src/main.rs +++ b/examples/tls/src/main.rs @@ -30,7 +30,7 @@ fn main() { let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap().parse("12345").unwrap(); HttpServer::new( - Application::new("/") + || Application::new() // enable logger .middleware(middlewares::Logger::default()) // register simple handler, handle all methods @@ -42,7 +42,7 @@ fn main() { .header("LOCATION", "/index.html") .body(Body::Empty) }))) - .serve_tls::<_, ()>("127.0.0.1:8443", pkcs12).unwrap(); + .serve_tls::<_, ()>("127.0.0.1:8443", &pkcs12).unwrap(); println!("Started http server: 127.0.0.1:8443"); let _ = sys.run(); diff --git a/guide/src/qs_3.md b/guide/src/qs_3.md index b6a83cf1a..51e82d493 100644 --- a/guide/src/qs_3.md +++ b/guide/src/qs_3.md @@ -42,7 +42,7 @@ Multiple applications could be served with one server: use actix_web::*; fn main() { - HttpServer::::new(|| vec![ + HttpServer::::new(|| vec![ Application::new() .prefix("/app1") .resource("/", |r| r.f(|r| httpcodes::HTTPOk)), diff --git a/src/lib.rs b/src/lib.rs index 8d1ba6bec..3fb4c8a59 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,8 @@ extern crate flate2; extern crate brotli2; extern crate percent_encoding; extern crate smallvec; +extern crate num_cpus; +extern crate socket2; extern crate actix; extern crate h2 as http2; diff --git a/src/server.rs b/src/server.rs index a5862c34e..5d03d4f9f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,24 +1,27 @@ -use std::{io, net}; +use std::{io, net, thread}; use std::rc::Rc; -use std::net::SocketAddr; +use std::sync::Arc; use std::marker::PhantomData; use actix::dev::*; use futures::Stream; +use futures::sync::mpsc; use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_core::net::{TcpListener, TcpStream}; +use tokio_core::net::TcpStream; +use num_cpus; +use socket2::{Socket, Domain, Type}; #[cfg(feature="tls")] -use futures::Future; +use futures::{future, Future}; #[cfg(feature="tls")] use native_tls::TlsAcceptor; #[cfg(feature="tls")] use tokio_tls::{TlsStream, TlsAcceptorExt}; #[cfg(feature="alpn")] -use futures::Future; +use futures::{future, Future}; #[cfg(feature="alpn")] -use openssl::ssl::{SslMethod, SslAcceptorBuilder}; +use openssl::ssl::{SslMethod, SslAcceptor, SslAcceptorBuilder}; #[cfg(feature="alpn")] use openssl::pkcs12::ParsedPkcs12; #[cfg(feature="alpn")] @@ -29,7 +32,7 @@ use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; /// Various server settings #[derive(Debug, Clone)] pub struct ServerSettings { - addr: Option, + addr: Option, secure: bool, host: String, } @@ -46,7 +49,7 @@ impl Default for ServerSettings { impl ServerSettings { /// Crate server settings instance - fn new(addr: Option, secure: bool) -> Self { + fn new(addr: Option, secure: bool) -> Self { let host = if let Some(ref addr) = addr { format!("{}", addr) } else { @@ -60,7 +63,7 @@ impl ServerSettings { } /// Returns the socket address of the local half of this TCP connection - pub fn local_addr(&self) -> Option { + pub fn local_addr(&self) -> Option { self.addr } @@ -82,47 +85,70 @@ impl ServerSettings { /// `A` - peer address /// /// `H` - request handler -pub struct HttpServer { +pub struct HttpServer + where H: 'static +{ h: Rc>, io: PhantomData, addr: PhantomData, + threads: usize, + factory: Arc U + Send + Sync>, + workers: Vec>>, } -impl Actor for HttpServer { +impl Actor for HttpServer { type Context = Context; } -impl HttpServer where H: HttpHandler +impl HttpServer + where H: HttpHandler, + U: IntoIterator + 'static, + V: IntoHttpHandler, { /// Create new http server with vec of http handlers - pub fn new>(factory: F) -> Self - where F: Fn() -> U + Send, - V: IntoHttpHandler + pub fn new(factory: F) -> Self + where F: Sync + Send + 'static + Fn() -> U, { - let apps: Vec<_> = factory().into_iter().map(|h| h.into_handler()).collect(); - - HttpServer{ h: Rc::new(apps), + HttpServer{ h: Rc::new(Vec::new()), io: PhantomData, - addr: PhantomData } + addr: PhantomData, + threads: num_cpus::get(), + factory: Arc::new(factory), + workers: Vec::new(), + } + } + + /// Set number of workers to start. + /// + /// By default http server uses number of available logical cpu as threads count. + pub fn threads(mut self, num: usize) -> Self { + self.threads = num; + self } } -impl HttpServer +impl HttpServer where T: AsyncRead + AsyncWrite + 'static, A: 'static, H: HttpHandler, + U: IntoIterator + 'static, + V: IntoHttpHandler, { - /// Start listening for incomming connections from stream. + /// Start listening for incomming connections from a stream. + /// + /// This method uses only one thread for handling incoming connections. pub fn serve_incoming(mut self, stream: S, secure: bool) -> io::Result where Self: ActorAddress, S: Stream + 'static { // set server settings - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); let settings = ServerSettings::new(Some(addr), secure); - for h in Rc::get_mut(&mut self.h).unwrap().iter_mut() { - h.server_settings(settings.clone()); + let mut apps: Vec<_> = (*self.factory)().into_iter().map(|h| h.into_handler()).collect(); + for app in &mut apps { + app.server_settings(settings.clone()); } + self.h = Rc::new(apps); // start server Ok(HttpServer::create(move |ctx| { @@ -133,134 +159,226 @@ impl HttpServer } fn bind(&self, addr: S) - -> io::Result> + -> io::Result> { let mut err = None; - let mut addrs = Vec::new(); + let mut sockets = Vec::new(); if let Ok(iter) = addr.to_socket_addrs() { for addr in iter { - match TcpListener::bind(&addr, Arbiter::handle()) { - Ok(tcp) => addrs.push((addr, tcp)), - Err(e) => err = Some(e), + match addr { + net::SocketAddr::V4(a) => { + let socket = Socket::new(Domain::ipv4(), Type::stream(), None)?; + match socket.bind(&a.into()) { + Ok(_) => { + socket.listen(1024) + .expect("failed to set socket backlog"); + socket.set_reuse_address(true) + .expect("failed to set socket reuse address"); + sockets.push((addr, socket)); + }, + Err(e) => err = Some(e), + } + } + net::SocketAddr::V6(a) => { + let socket = Socket::new(Domain::ipv6(), Type::stream(), None)?; + match socket.bind(&a.into()) { + Ok(_) => { + socket.listen(1024) + .expect("failed to set socket backlog"); + socket.set_reuse_address(true) + .expect("failed to set socket reuse address"); + sockets.push((addr, socket)) + } + Err(e) => err = Some(e), + } + } } } } - if addrs.is_empty() { + + if sockets.is_empty() { if let Some(e) = err.take() { Err(e) } else { Err(io::Error::new(io::ErrorKind::Other, "Can not bind to address.")) } } else { - Ok(addrs) + Ok(sockets) } } + + fn start_workers(&mut self, settings: &ServerSettings) + -> Vec>> + { + // start workers + let mut workers = Vec::new(); + for _ in 0..self.threads { + let s = settings.clone(); + let (tx, rx) = mpsc::unbounded::>(); + + let factory = Arc::clone(&self.factory); + let addr = Arbiter::start(move |ctx: &mut Context<_>| { + let mut apps: Vec<_> = (*factory)() + .into_iter().map(|h| h.into_handler()).collect(); + for app in &mut apps { + app.server_settings(s.clone()); + } + ctx.add_stream(rx); + Worker{h: Rc::new(apps)} + }); + workers.push(tx); + self.workers.push(addr); + } + info!("Starting {} http workers", self.threads); + workers + } } -impl HttpServer { - +impl HttpServer + where U: IntoIterator + 'static, + V: IntoHttpHandler, +{ /// Start listening for incomming connections. /// /// This methods converts address to list of `SocketAddr` /// then binds to all available addresses. + /// It also starts number of http handler workers in seperate threads. + /// For each address this method starts separate thread which does `accept()` in a loop. pub fn serve(mut self, addr: S) -> io::Result where Self: ActorAddress, S: net::ToSocketAddrs, { let addrs = self.bind(addr)?; - - // set server settings let settings = ServerSettings::new(Some(addrs[0].0), false); - for h in Rc::get_mut(&mut self.h).unwrap().iter_mut() { - h.server_settings(settings.clone()); + let workers = self.start_workers(&settings); + + // start acceptors threads + for (addr, sock) in addrs { + let wrks = workers.clone(); + let _ = thread::Builder::new().name(format!("Accept on {}", addr)).spawn(move || { + let mut next = 0; + loop { + match sock.accept() { + Ok((socket, addr)) => { + let addr = if let Some(addr) = addr.as_inet() { + net::SocketAddr::V4(addr) + } else { + net::SocketAddr::V6(addr.as_inet6().unwrap()) + }; + let msg = IoStream{ + io: socket.into_tcp_stream(), peer: Some(addr), http2: false}; + wrks[next].unbounded_send(msg).expect("worker thread died"); + next = (next + 1) % wrks.len(); + } + Err(err) => error!("Error accepting connection: {:?}", err), + } + } + }); + info!("Starting http server on {}", addr); } - // start server - Ok(HttpServer::create(move |ctx| { - for (addr, tcp) in addrs { - info!("Starting http server on {}", addr); - - ctx.add_stream(tcp.incoming().map( - move |(t, a)| IoStream{io: t, peer: Some(a), http2: false})); - } - self - })) + // start http server actor + Ok(HttpServer::create(|_| {self})) } } #[cfg(feature="tls")] -impl HttpServer, net::SocketAddr, H> { - +impl HttpServer, net::SocketAddr, H, U> + where U: IntoIterator + 'static, + V: IntoHttpHandler, +{ /// Start listening for incomming tls connections. /// /// This methods converts address to list of `SocketAddr` /// then binds to all available addresses. - pub fn serve_tls(mut self, addr: S, pkcs12: ::Pkcs12) -> io::Result + pub fn serve_tls(self, addr: S, pkcs12: ::Pkcs12) -> io::Result where Self: ActorAddress, S: net::ToSocketAddrs, { let addrs = self.bind(addr)?; - - // set server settings - let settings = ServerSettings::new(Some(addrs[0].0), true); - for h in Rc::get_mut(&mut self.h).unwrap().iter_mut() { - h.server_settings(settings.clone()); - } - + let settings = ServerSettings::new(Some(addrs[0].0), false); let acceptor = match TlsAcceptor::builder(pkcs12) { Ok(builder) => { match builder.build() { - Ok(acceptor) => Rc::new(acceptor), + Ok(acceptor) => acceptor, Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err)) } } Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err)) }; - // start server - Ok(HttpServer::create(move |ctx| { - for (srv, tcp) in addrs { - info!("Starting tls http server on {}", srv); + // start workers + let mut workers = Vec::new(); + for _ in 0..self.threads { + let s = settings.clone(); + let (tx, rx) = mpsc::unbounded::>(); - let acc = acceptor.clone(); - ctx.add_stream(tcp.incoming().and_then(move |(stream, addr)| { - TlsAcceptorExt::accept_async(acc.as_ref(), stream) - .map(move |t| IoStream{io: t, peer: Some(addr), http2: false}) - .map_err(|err| { - trace!("Error during handling tls connection: {}", err); - io::Error::new(io::ErrorKind::Other, err) - }) - })); - } - self - })) + let acc = acceptor.clone(); + let factory = Arc::clone(&self.factory); + let _addr = Arbiter::start(move |ctx: &mut Context<_>| { + let mut apps: Vec<_> = (*factory)() + .into_iter().map(|h| h.into_handler()).collect(); + for app in &mut apps { + app.server_settings(s.clone()); + } + ctx.add_stream(rx); + TlsWorker{h: Rc::new(apps), acceptor: acc} + }); + workers.push(tx); + // self.workers.push(addr); + } + info!("Starting {} http workers", self.threads); + + // start acceptors threads + for (addr, sock) in addrs { + let wrks = workers.clone(); + let _ = thread::Builder::new().name(format!("Accept on {}", addr)).spawn(move || { + let mut next = 0; + loop { + match sock.accept() { + Ok((socket, addr)) => { + let addr = if let Some(addr) = addr.as_inet() { + net::SocketAddr::V4(addr) + } else { + net::SocketAddr::V6(addr.as_inet6().unwrap()) + }; + let msg = IoStream{ + io: socket.into_tcp_stream(), peer: Some(addr), http2: false}; + wrks[next].unbounded_send(msg).expect("worker thread died"); + next = (next + 1) % wrks.len(); + } + Err(err) => error!("Error accepting connection: {:?}", err), + } + } + }); + info!("Starting tls http server on {}", addr); + } + + // start http server actor + Ok(HttpServer::create(|_| {self})) } } #[cfg(feature="alpn")] -impl HttpServer, net::SocketAddr, H> { - +impl HttpServer, net::SocketAddr, H, U> + where U: IntoIterator + 'static, + V: IntoHttpHandler, +{ /// Start listening for incomming tls connections. /// /// This methods converts address to list of `SocketAddr` /// then binds to all available addresses. - pub fn serve_tls(mut self, addr: S, identity: ParsedPkcs12) -> io::Result + pub fn serve_tls(self, addr: S, identity: &ParsedPkcs12) -> io::Result where Self: ActorAddress, S: net::ToSocketAddrs, { let addrs = self.bind(addr)?; - - // set server settings - let settings = ServerSettings::new(Some(addrs[0].0), true); - for h in Rc::get_mut(&mut self.h).unwrap().iter_mut() { - h.server_settings(settings.clone()); - } - + let settings = ServerSettings::new(Some(addrs[0].0), false); let acceptor = match SslAcceptorBuilder::mozilla_intermediate( SslMethod::tls(), &identity.pkey, &identity.cert, &identity.chain) { Ok(mut builder) => { - match builder.builder_mut().set_alpn_protocols(&[b"h2", b"http/1.1"]) { + match builder.set_alpn_protocols(&[b"h2", b"http/1.1"]) { Ok(_) => builder.build(), Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err)), } @@ -268,55 +386,80 @@ impl HttpServer, net::SocketAddr, H> { Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err)) }; - Ok(HttpServer::create(move |ctx| { - for (srv, tcp) in addrs { - info!("Starting tls http server on {}", srv); + // start workers + let mut workers = Vec::new(); + for _ in 0..self.threads { + let s = settings.clone(); + let (tx, rx) = mpsc::unbounded::>(); - let acc = acceptor.clone(); - ctx.add_stream(tcp.incoming().and_then(move |(stream, addr)| { - SslAcceptorExt::accept_async(&acc, stream) - .map(move |stream| { - let http2 = if let Some(p) = - stream.get_ref().ssl().selected_alpn_protocol() - { - p.len() == 2 && &p == b"h2" + let acc = acceptor.clone(); + let factory = Arc::clone(&self.factory); + let _addr = Arbiter::start(move |ctx: &mut Context<_>| { + let mut apps: Vec<_> = (*factory)() + .into_iter().map(|h| h.into_handler()).collect(); + for app in &mut apps { + app.server_settings(s.clone()); + } + ctx.add_stream(rx); + AlpnWorker{h: Rc::new(apps), acceptor: acc} + }); + workers.push(tx); + // self.workers.push(addr); + } + info!("Starting {} http workers", self.threads); + + // start acceptors threads + for (addr, sock) in addrs { + let wrks = workers.clone(); + let _ = thread::Builder::new().name(format!("Accept on {}", addr)).spawn(move || { + let mut next = 0; + loop { + match sock.accept() { + Ok((socket, addr)) => { + let addr = if let Some(addr) = addr.as_inet() { + net::SocketAddr::V4(addr) } else { - false + net::SocketAddr::V6(addr.as_inet6().unwrap()) }; - IoStream{io: stream, peer: Some(addr), http2: http2} - }) - .map_err(|err| { - trace!("Error during handling tls connection: {}", err); - io::Error::new(io::ErrorKind::Other, err) - }) - })); - } - self - })) + let msg = IoStream{ + io: socket.into_tcp_stream(), peer: Some(addr), http2: false}; + wrks[next].unbounded_send(msg).expect("worker thread died"); + next = (next + 1) % wrks.len(); + } + Err(err) => error!("Error accepting connection: {:?}", err), + } + } + }); + info!("Starting tls http server on {}", addr); + } + + // start http server actor + Ok(HttpServer::create(|_| {self})) } } struct IoStream { io: T, - peer: Option, + peer: Option, http2: bool, } impl ResponseType for IoStream - where T: AsyncRead + AsyncWrite + 'static { type Item = (); type Error = (); } -impl StreamHandler, io::Error> for HttpServer +impl StreamHandler, io::Error> for HttpServer where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static, + U: 'static, A: 'static {} -impl Handler, io::Error> for HttpServer +impl Handler, io::Error> for HttpServer where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static, + U: 'static, A: 'static, { fn error(&mut self, err: io::Error, _: &mut Context) { @@ -331,3 +474,135 @@ impl Handler, io::Error> for HttpServer Self::empty() } } + + +/// Http workers +/// +/// Worker accepts Socket objects via unbounded channel and start requests processing. +struct Worker { + h: Rc>, +} + +impl Actor for Worker { + type Context = Context; +} + +impl StreamHandler> for Worker + where H: HttpHandler + 'static {} + +impl Handler> for Worker + where H: HttpHandler + 'static, +{ + fn handle(&mut self, msg: IoStream, _: &mut Context) + -> Response> + { + let io = TcpStream::from_stream(msg.io, Arbiter::handle()) + .expect("failed to associate TCP stream"); + + Arbiter::handle().spawn( + HttpChannel::new(Rc::clone(&self.h), io, msg.peer, msg.http2)); + Self::empty() + } +} + +/// Tls http workers +/// +/// Worker accepts Socket objects via unbounded channel and start requests processing. +#[cfg(feature="tls")] +struct TlsWorker { + h: Rc>, + acceptor: TlsAcceptor, +} + +#[cfg(feature="tls")] +impl Actor for TlsWorker { + type Context = Context; +} + +#[cfg(feature="tls")] +impl StreamHandler> for TlsWorker + where H: HttpHandler + 'static {} + +#[cfg(feature="tls")] +impl Handler> for TlsWorker + where H: HttpHandler + 'static, +{ + fn handle(&mut self, msg: IoStream, _: &mut Context) + -> Response> + { + let IoStream { io, peer, http2 } = msg; + let io = TcpStream::from_stream(io, Arbiter::handle()) + .expect("failed to associate TCP stream"); + + let h = Rc::clone(&self.h); + + Arbiter::handle().spawn( + TlsAcceptorExt::accept_async(&self.acceptor, io).then(move |res| { + match res { + Ok(io) => Arbiter::handle().spawn( + HttpChannel::new(h, io, peer, http2)), + Err(err) => + trace!("Error during handling tls connection: {}", err), + }; + future::result(Ok(())) + }) + ); + + Self::empty() + } +} + +/// Tls http workers with alpn support +/// +/// Worker accepts Socket objects via unbounded channel and start requests processing. +#[cfg(feature="alpn")] +struct AlpnWorker { + h: Rc>, + acceptor: SslAcceptor, +} + +#[cfg(feature="alpn")] +impl Actor for AlpnWorker { + type Context = Context; +} + +#[cfg(feature="alpn")] +impl StreamHandler> for AlpnWorker + where H: HttpHandler + 'static {} + +#[cfg(feature="alpn")] +impl Handler> for AlpnWorker + where H: HttpHandler + 'static, +{ + fn handle(&mut self, msg: IoStream, _: &mut Context) + -> Response> + { + let IoStream { io, peer, .. } = msg; + let io = TcpStream::from_stream(io, Arbiter::handle()) + .expect("failed to associate TCP stream"); + + let h = Rc::clone(&self.h); + + Arbiter::handle().spawn( + SslAcceptorExt::accept_async(&self.acceptor, io).then(move |res| { + match res { + Ok(io) => { + let http2 = if let Some(p) = io.get_ref().ssl().selected_alpn_protocol() + { + p.len() == 2 && &p == b"h2" + } else { + false + }; + Arbiter::handle().spawn( + HttpChannel::new(h, io, peer, http2)); + }, + Err(err) => + trace!("Error during handling tls connection: {}", err), + }; + future::result(Ok(())) + }) + ); + + Self::empty() + } +} diff --git a/tests/test_server.rs b/tests/test_server.rs index 4a657bccd..b3b58b3b7 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -4,7 +4,6 @@ extern crate tokio_core; extern crate reqwest; use std::{net, thread}; -use std::rc::Rc; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; use tokio_core::net::TcpListener; @@ -52,7 +51,6 @@ struct MiddlewareTest { start: Arc, response: Arc, finish: Arc, - test: Rc, } impl middlewares::Middleware for MiddlewareTest { @@ -89,7 +87,7 @@ fn test_middlewares() { move || vec![Application::new() .middleware(MiddlewareTest{start: act_num1.clone(), response: act_num2.clone(), - finish: act_num3.clone(), test: Rc::new(1)}) + finish: act_num3.clone()}) .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))]) .serve::<_, ()>("127.0.0.1:58904").unwrap(); sys.run(); From 2e83c5924d0de509d793b2b62c4056745e841cbe Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 12 Dec 2017 21:32:58 -0800 Subject: [PATCH 079/279] cleanup and optimize some code --- examples/basic.rs | 8 +++++--- src/encoding.rs | 43 ++++++++++++++++++++++++++--------------- src/error.rs | 13 ++++++++++++- src/h1.rs | 47 ++++++++++++++++++++++++++++++++++----------- src/h2.rs | 2 +- src/handler.rs | 4 ++++ src/httprequest.rs | 46 ++++++++++++++++++++++++++------------------ src/httpresponse.rs | 32 ++++++++++++++++++++---------- src/ws.rs | 21 +++++++++++++------- 9 files changed, 148 insertions(+), 68 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index 304eabd27..22dfaba37 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -13,9 +13,11 @@ use futures::future::{FutureResult, result}; /// simple handler fn index(mut req: HttpRequest) -> Result { println!("{:?}", req); - if let Ok(ch) = req.payload_mut().readany() { - if let futures::Async::Ready(Some(d)) = ch { - println!("{}", String::from_utf8_lossy(d.0.as_ref())); + if let Some(payload) = req.payload_mut() { + if let Ok(ch) = payload.readany() { + if let futures::Async::Ready(Some(d)) = ch { + println!("{}", String::from_utf8_lossy(d.0.as_ref())); + } } } diff --git a/src/encoding.rs b/src/encoding.rs index 1c8c88272..be44990b7 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -402,14 +402,14 @@ impl PayloadEncoder { resp.headers_mut().insert( CONTENT_LENGTH, - HeaderValue::from_str(format!("{}", b.len()).as_str()).unwrap()); + HeaderValue::from_str(&b.len().to_string()).unwrap()); *bytes = Binary::from(b); encoding = ContentEncoding::Identity; TransferEncoding::eof() } else { resp.headers_mut().insert( CONTENT_LENGTH, - HeaderValue::from_str(format!("{}", bytes.len()).as_str()).unwrap()); + HeaderValue::from_str(&bytes.len().to_string()).unwrap()); resp.headers_mut().remove(TRANSFER_ENCODING); TransferEncoding::length(bytes.len() as u64) } @@ -478,22 +478,27 @@ impl PayloadEncoder { impl PayloadEncoder { + #[inline] pub fn len(&self) -> usize { self.0.get_ref().len() } + #[inline] pub fn get_mut(&mut self) -> &mut BytesMut { self.0.get_mut() } + #[inline] pub fn is_eof(&self) -> bool { self.0.is_eof() } + #[inline] pub fn write(&mut self, payload: &[u8]) -> Result<(), io::Error> { self.0.write(payload) } + #[inline] pub fn write_eof(&mut self) -> Result<(), io::Error> { self.0.write_eof() } @@ -508,6 +513,7 @@ enum ContentEncoder { impl ContentEncoder { + #[inline] pub fn is_eof(&self) -> bool { match *self { ContentEncoder::Br(ref encoder) => @@ -521,6 +527,7 @@ impl ContentEncoder { } } + #[inline] pub fn get_ref(&self) -> &BytesMut { match *self { ContentEncoder::Br(ref encoder) => @@ -534,6 +541,7 @@ impl ContentEncoder { } } + #[inline] pub fn get_mut(&mut self) -> &mut BytesMut { match *self { ContentEncoder::Br(ref mut encoder) => @@ -547,6 +555,7 @@ impl ContentEncoder { } } + #[inline] pub fn write_eof(&mut self) -> Result<(), io::Error> { let encoder = mem::replace(self, ContentEncoder::Identity(TransferEncoding::eof())); @@ -555,7 +564,6 @@ impl ContentEncoder { match encoder.finish() { Ok(mut writer) => { writer.encode_eof(); - *self = ContentEncoder::Identity(writer); Ok(()) }, Err(err) => Err(err), @@ -565,7 +573,6 @@ impl ContentEncoder { match encoder.finish() { Ok(mut writer) => { writer.encode_eof(); - *self = ContentEncoder::Identity(writer); Ok(()) }, Err(err) => Err(err), @@ -575,7 +582,6 @@ impl ContentEncoder { match encoder.finish() { Ok(mut writer) => { writer.encode_eof(); - *self = ContentEncoder::Identity(writer); Ok(()) }, Err(err) => Err(err), @@ -583,19 +589,18 @@ impl ContentEncoder { }, ContentEncoder::Identity(mut writer) => { writer.encode_eof(); - *self = ContentEncoder::Identity(writer); Ok(()) } } } + #[inline] pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> { match *self { ContentEncoder::Br(ref mut encoder) => { match encoder.write(data) { - Ok(_) => { - encoder.flush() - }, + Ok(_) => + encoder.flush(), Err(err) => { trace!("Error decoding br encoding: {}", err); Err(err) @@ -604,20 +609,18 @@ impl ContentEncoder { }, ContentEncoder::Gzip(ref mut encoder) => { match encoder.write(data) { - Ok(_) => { - encoder.flush() - }, + Ok(_) => + encoder.flush(), Err(err) => { - trace!("Error decoding br encoding: {}", err); + trace!("Error decoding gzip encoding: {}", err); Err(err) }, } } ContentEncoder::Deflate(ref mut encoder) => { match encoder.write(data) { - Ok(_) => { - encoder.flush() - }, + Ok(_) => + encoder.flush(), Err(err) => { trace!("Error decoding deflate encoding: {}", err); Err(err) @@ -655,6 +658,7 @@ enum TransferEncodingKind { impl TransferEncoding { + #[inline] pub fn eof() -> TransferEncoding { TransferEncoding { kind: TransferEncodingKind::Eof, @@ -662,6 +666,7 @@ impl TransferEncoding { } } + #[inline] pub fn chunked() -> TransferEncoding { TransferEncoding { kind: TransferEncodingKind::Chunked(false), @@ -669,6 +674,7 @@ impl TransferEncoding { } } + #[inline] pub fn length(len: u64) -> TransferEncoding { TransferEncoding { kind: TransferEncodingKind::Length(len), @@ -676,6 +682,7 @@ impl TransferEncoding { } } + #[inline] pub fn is_eof(&self) -> bool { match self.kind { TransferEncodingKind::Eof => true, @@ -687,6 +694,7 @@ impl TransferEncoding { } /// Encode message. Return `EOF` state of encoder + #[inline] pub fn encode(&mut self, msg: &[u8]) -> bool { match self.kind { TransferEncodingKind::Eof => { @@ -724,6 +732,7 @@ impl TransferEncoding { } /// Encode eof. Return `EOF` state of encoder + #[inline] pub fn encode_eof(&mut self) { match self.kind { TransferEncodingKind::Eof | TransferEncodingKind::Length(_) => (), @@ -739,11 +748,13 @@ impl TransferEncoding { impl io::Write for TransferEncoding { + #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { self.encode(buf); Ok(buf.len()) } + #[inline] fn flush(&mut self) -> io::Result<()> { Ok(()) } diff --git a/src/error.rs b/src/error.rs index e79b12939..37c0d0e5c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -262,6 +262,9 @@ pub enum MultipartError { /// Multipart boundary is not found #[fail(display="Multipart boundary is not found")] Boundary, + /// Request does not contain payload + #[fail(display="Request does not contain payload")] + NoPayload, /// Error during field parsing #[fail(display="{}", _0)] Parse(#[cause] ParseError), @@ -329,6 +332,9 @@ pub enum WsHandshakeError { /// Websocket key is not set or wrong #[fail(display="Unknown websocket key")] BadWebsocketKey, + /// Request does not contain payload + #[fail(display="Request does not contain payload")] + NoPayload, } impl ResponseError for WsHandshakeError { @@ -351,7 +357,9 @@ impl ResponseError for WsHandshakeError { WsHandshakeError::UnsupportedVersion => HTTPBadRequest.with_reason("Unsupported version"), WsHandshakeError::BadWebsocketKey => - HTTPBadRequest.with_reason("Handshake error") + HTTPBadRequest.with_reason("Handshake error"), + WsHandshakeError::NoPayload => + HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR, Body::Empty), } } } @@ -371,6 +379,9 @@ pub enum UrlencodedError { /// Content type error #[fail(display="Content type error")] ContentType, + /// Request does not contain payload + #[fail(display="Request does not contain payload")] + NoPayload, } /// Return `BadRequest` for `UrlencodedError` diff --git a/src/h1.rs b/src/h1.rs index 5a46a3bb4..10b5cff2d 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -545,28 +545,25 @@ impl Reader { } } - let (mut psender, payload) = Payload::new(false); - let msg = HttpRequest::new(method, uri, version, headers, payload); - - let decoder = if msg.upgrade() { + let decoder = if upgrade(&method, &headers) { Decoder::eof() } else { - let has_len = msg.headers().contains_key(header::CONTENT_LENGTH); + let has_len = headers.contains_key(header::CONTENT_LENGTH); // Chunked encoding - if msg.chunked()? { + if chunked(&headers)? { if has_len { return Err(ParseError::Header) } Decoder::chunked() } else { if !has_len { - psender.feed_eof(); + let msg = HttpRequest::new(method, uri, version, headers, None); return Ok(Message::Http1(msg, None)) } // Content-Length - let len = msg.headers().get(header::CONTENT_LENGTH).unwrap(); + let len = headers.get(header::CONTENT_LENGTH).unwrap(); if let Ok(s) = len.to_str() { if let Ok(len) = s.parse::() { Decoder::length(len) @@ -581,11 +578,13 @@ impl Reader { } }; - let payload = PayloadInfo { - tx: PayloadType::new(msg.headers(), psender), + let (psender, payload) = Payload::new(false); + let info = PayloadInfo { + tx: PayloadType::new(&headers, psender), decoder: decoder, }; - Ok(Message::Http1(msg, Some(payload))) + let msg = HttpRequest::new(method, uri, version, headers, Some(payload)); + Ok(Message::Http1(msg, Some(info))) } } @@ -610,6 +609,32 @@ fn record_header_indices(bytes: &[u8], } } +/// Check if request is UPGRADE +fn upgrade(method: &Method, headers: &HeaderMap) -> bool { + if let Some(conn) = headers.get(header::CONNECTION) { + if let Ok(s) = conn.to_str() { + s.to_lowercase().contains("upgrade") + } else { + *method == Method::CONNECT + } + } else { + *method == Method::CONNECT + } +} + +/// Check if request has chunked transfer encoding +fn chunked(headers: &HeaderMap) -> Result { + if let Some(encodings) = headers.get(header::TRANSFER_ENCODING) { + if let Ok(s) = encodings.to_str() { + Ok(s.to_lowercase().contains("chunked")) + } else { + Err(ParseError::Header) + } + } else { + Ok(false) + } +} + /// Decoders to handle different Transfer-Encodings. /// /// If a message body does not include a Transfer-Encoding, it *should* diff --git a/src/h2.rs b/src/h2.rs index d3f357aef..625681623 100644 --- a/src/h2.rs +++ b/src/h2.rs @@ -237,7 +237,7 @@ impl Entry { let (psender, payload) = Payload::new(false); let mut req = HttpRequest::new( - parts.method, parts.uri, parts.version, parts.headers, payload); + parts.method, parts.uri, parts.version, parts.headers, Some(payload)); // set remote addr req.set_peer_addr(addr); diff --git a/src/handler.rs b/src/handler.rs index 241eabf3c..5c9ba94a8 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -58,6 +58,7 @@ pub(crate) enum ReplyItem { impl Reply { /// Create actor response + #[inline] pub fn actor(ctx: HttpContext) -> Reply where A: Actor>, S: 'static { @@ -65,6 +66,7 @@ impl Reply { } /// Create async response + #[inline] pub fn async(fut: F) -> Reply where F: Future + 'static { @@ -72,10 +74,12 @@ impl Reply { } /// Send response + #[inline] pub fn response>(response: R) -> Reply { Reply(ReplyItem::Message(response.into())) } + #[inline] pub(crate) fn into(self) -> ReplyItem { self.0 } diff --git a/src/httprequest.rs b/src/httprequest.rs index 3752cdf36..266a4e945 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -28,7 +28,7 @@ pub struct HttpMessage { pub params: Params<'static>, pub cookies: Option>>, pub addr: Option, - pub payload: Payload, + pub payload: Option, pub info: Option>, } @@ -43,7 +43,7 @@ impl Default for HttpMessage { params: Params::default(), cookies: None, addr: None, - payload: Payload::empty(), + payload: None, extensions: Extensions::new(), info: None, } @@ -72,13 +72,13 @@ impl HttpMessage { } /// An HTTP Request -pub struct HttpRequest(Rc, Rc, Option>); +pub struct HttpRequest(Rc, Option>, Option>); impl HttpRequest<()> { /// Construct a new Request. #[inline] pub fn new(method: Method, uri: Uri, - version: Version, headers: HeaderMap, payload: Payload) -> HttpRequest + version: Version, headers: HeaderMap, payload: Option) -> HttpRequest { HttpRequest( Rc::new(HttpMessage { @@ -93,7 +93,7 @@ impl HttpRequest<()> { extensions: Extensions::new(), info: None, }), - Rc::new(()), + None, None, ) } @@ -118,14 +118,14 @@ impl HttpRequest<()> { extensions: Extensions::new(), info: None, }), - Rc::new(()), + None, None, ) } /// Construct new http request with state. pub fn with_state(self, state: Rc, router: Router) -> HttpRequest { - HttpRequest(self.0, state, Some(router)) + HttpRequest(self.0, Some(state), Some(router)) } } @@ -133,7 +133,7 @@ impl HttpRequest { /// Construct new http request without state. pub fn clone_without_state(&self) -> HttpRequest { - HttpRequest(Rc::clone(&self.0), Rc::new(()), None) + HttpRequest(Rc::clone(&self.0), None, None) } // get mutable reference for inner message @@ -153,7 +153,7 @@ impl HttpRequest { /// Shared application state #[inline] pub fn state(&self) -> &S { - &self.1 + self.1.as_ref().unwrap() } /// Protocol extensions. @@ -377,20 +377,20 @@ impl HttpRequest { /// Returns reference to the associated http payload. #[inline] - pub fn payload(&self) -> &Payload { - &self.0.payload + pub fn payload(&self) -> Option<&Payload> { + self.0.payload.as_ref() } /// Returns mutable reference to the associated http payload. #[inline] - pub fn payload_mut(&mut self) -> &mut Payload { - &mut self.as_mut().payload + pub fn payload_mut(&mut self) -> Option<&mut Payload> { + self.as_mut().payload.as_mut() } /// Return payload #[inline] - pub fn take_payload(&mut self) -> Payload { - mem::replace(&mut self.as_mut().payload, Payload::empty()) + pub fn take_payload(&mut self) -> Option { + self.as_mut().payload.take() } /// Return stream to process BODY as multipart. @@ -398,7 +398,11 @@ impl HttpRequest { /// Content-type: multipart/form-data; pub fn multipart(&mut self) -> Result { let boundary = Multipart::boundary(&self.0.headers)?; - Ok(Multipart::new(boundary, self.take_payload())) + if let Some(payload) = self.take_payload() { + Ok(Multipart::new(boundary, payload)) + } else { + Err(MultipartError::NoPayload) + } } /// Parse `application/x-www-form-urlencoded` encoded body. @@ -441,7 +445,11 @@ impl HttpRequest { }; if t { - Ok(UrlEncoded{pl: self.take_payload(), body: BytesMut::new()}) + if let Some(payload) = self.take_payload() { + Ok(UrlEncoded{pl: payload, body: BytesMut::new()}) + } else { + Err(UrlencodedError::NoPayload) + } } else { Err(UrlencodedError::ContentType) } @@ -452,13 +460,13 @@ impl Default for HttpRequest<()> { /// Construct default request fn default() -> HttpRequest { - HttpRequest(Rc::new(HttpMessage::default()), Rc::new(()), None) + HttpRequest(Rc::new(HttpMessage::default()), None, None) } } impl Clone for HttpRequest { fn clone(&self) -> HttpRequest { - HttpRequest(Rc::clone(&self.0), Rc::clone(&self.1), None) + HttpRequest(Rc::clone(&self.0), self.1.clone(), None) } } diff --git a/src/httpresponse.rs b/src/httpresponse.rs index e877a761a..ba4cb7b23 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -222,20 +222,20 @@ struct Parts { chunked: bool, encoding: ContentEncoding, connection_type: Option, - cookies: CookieJar, + cookies: Option, } impl Parts { fn new(status: StatusCode) -> Self { Parts { version: None, - headers: HeaderMap::new(), + headers: HeaderMap::with_capacity(8), status: status, reason: None, chunked: false, encoding: ContentEncoding::Auto, connection_type: None, - cookies: CookieJar::new(), + cookies: None, } } } @@ -359,7 +359,13 @@ impl HttpResponseBuilder { /// Set a cookie pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { if let Some(parts) = parts(&mut self.parts, &self.err) { - parts.cookies.add(cookie.into_owned()); + if parts.cookies.is_none() { + let mut jar = CookieJar::new(); + jar.add(cookie.into_owned()); + parts.cookies = Some(jar) + } else { + parts.cookies.as_mut().unwrap().add(cookie.into_owned()); + } } self } @@ -367,9 +373,13 @@ impl HttpResponseBuilder { /// Remote cookie, cookie has to be cookie from `HttpRequest::cookies()` method. pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self { if let Some(parts) = parts(&mut self.parts, &self.err) { + if parts.cookies.is_none() { + parts.cookies = Some(CookieJar::new()) + } + let mut jar = parts.cookies.as_mut().unwrap(); let cookie = cookie.clone().into_owned(); - parts.cookies.add_original(cookie.clone()); - parts.cookies.remove(cookie); + jar.add_original(cookie.clone()); + jar.remove(cookie); } self } @@ -391,10 +401,12 @@ impl HttpResponseBuilder { if let Some(e) = self.err.take() { return Err(e) } - for cookie in parts.cookies.delta() { - parts.headers.append( - header::SET_COOKIE, - HeaderValue::from_str(&cookie.to_string())?); + if let Some(jar) = parts.cookies { + for cookie in jar.delta() { + parts.headers.append( + header::SET_COOKIE, + HeaderValue::from_str(&cookie.to_string())?); + } } Ok(HttpResponse { version: parts.version, diff --git a/src/ws.rs b/src/ws.rs index 2578cdc0d..cd23526fc 100644 --- a/src/ws.rs +++ b/src/ws.rs @@ -96,11 +96,15 @@ pub fn start(mut req: HttpRequest, actor: A) -> Result { let resp = handshake(&req)?; - let stream = WsStream::new(&mut req); - let mut ctx = HttpContext::new(req, actor); - ctx.start(resp); - ctx.add_stream(stream); - Ok(ctx.into()) + if let Some(payload) = req.take_payload() { + let stream = WsStream::new(payload); + let mut ctx = HttpContext::new(req, actor); + ctx.start(resp); + ctx.add_stream(stream); + Ok(ctx.into()) + } else { + Err(WsHandshakeError::NoPayload.into()) + } } /// Prepare `WebSocket` handshake response. @@ -178,8 +182,11 @@ pub struct WsStream { } impl WsStream { - pub fn new(req: &mut HttpRequest) -> WsStream { - WsStream { rx: req.take_payload(), buf: BytesMut::new(), closed: false, error_sent: false } + pub fn new(payload: Payload) -> WsStream { + WsStream { rx: payload, + buf: BytesMut::new(), + closed: false, + error_sent: false } } } From 55204c829c36135e7f80eecdcf94297960d79c29 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Dec 2017 08:00:25 -0800 Subject: [PATCH 080/279] update tests --- src/application.rs | 7 ++--- src/h1.rs | 56 +++++++++++++++++++------------------- src/httprequest.rs | 20 +++++--------- src/middlewares/logger.rs | 9 ++---- src/pred.rs | 24 ++++++++-------- src/router.rs | 13 ++++----- src/ws.rs | 17 ++++++------ tests/test_httprequest.rs | 25 +++++++---------- tests/test_httpresponse.rs | 2 +- 9 files changed, 77 insertions(+), 96 deletions(-) diff --git a/src/application.rs b/src/application.rs index 3dd1b1fe8..b714067e2 100644 --- a/src/application.rs +++ b/src/application.rs @@ -310,7 +310,6 @@ mod tests { use http::{Method, Version, Uri, HeaderMap, StatusCode}; use super::*; use httprequest::HttpRequest; - use payload::Payload; use httpcodes; #[test] @@ -321,13 +320,13 @@ mod tests { let req = HttpRequest::new( Method::GET, Uri::from_str("/test").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); let resp = app.run(req); assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); let req = HttpRequest::new( Method::GET, Uri::from_str("/blah").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); let resp = app.run(req); assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); @@ -336,7 +335,7 @@ mod tests { .finish(); let req = HttpRequest::new( Method::GET, Uri::from_str("/blah").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); let resp = app.run(req); assert_eq!(resp.as_response().unwrap().status(), StatusCode::METHOD_NOT_ALLOWED); } diff --git a/src/h1.rs b/src/h1.rs index 10b5cff2d..8b929177e 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -998,7 +998,7 @@ mod tests { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); - assert!(req.payload().eof()); + assert!(req.payload().is_none()); } Ok(_) | Err(_) => panic!("Error during parsing http request"), } @@ -1021,7 +1021,7 @@ mod tests { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::PUT); assert_eq!(req.path(), "/test"); - assert!(req.payload().eof()); + assert!(req.payload().is_none()); } Ok(_) | Err(_) => panic!("Error during parsing http request"), } @@ -1038,7 +1038,7 @@ mod tests { assert_eq!(req.version(), Version::HTTP_10); assert_eq!(*req.method(), Method::POST); assert_eq!(req.path(), "/test2"); - assert!(req.payload().eof()); + assert!(req.payload().is_none()); } Ok(_) | Err(_) => panic!("Error during parsing http request"), } @@ -1055,7 +1055,7 @@ mod tests { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); - assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"body"); + assert_eq!(req.payload_mut().unwrap().readall().unwrap().as_ref(), b"body"); } Ok(_) | Err(_) => panic!("Error during parsing http request"), } @@ -1073,7 +1073,7 @@ mod tests { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); - assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"body"); + assert_eq!(req.payload_mut().unwrap().readall().unwrap().as_ref(), b"body"); } Ok(_) | Err(_) => panic!("Error during parsing http request"), } @@ -1093,7 +1093,7 @@ mod tests { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); - assert!(req.payload().eof()); + assert!(req.payload().is_none()); } Ok(_) | Err(_) => panic!("Error during parsing http request"), } @@ -1120,7 +1120,7 @@ mod tests { assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value"); - assert!(req.payload().eof()); + assert!(req.payload().is_none()); } Ok(_) | Err(_) => panic!("Error during parsing http request"), } @@ -1229,7 +1229,7 @@ mod tests { connection: upgrade\r\n\r\n"); let req = parse_ready!(&mut buf); - assert!(!req.payload().eof()); + assert!(!req.payload().unwrap().eof()); assert!(req.upgrade()); } @@ -1241,7 +1241,7 @@ mod tests { let req = parse_ready!(&mut buf); assert!(req.upgrade()); - assert!(!req.payload().eof()); + assert!(!req.payload().unwrap().eof()); } #[test] @@ -1251,7 +1251,7 @@ mod tests { transfer-encoding: chunked\r\n\r\n"); let req = parse_ready!(&mut buf); - assert!(!req.payload().eof()); + assert!(req.payload().is_some()); if let Ok(val) = req.chunked() { assert!(val); } else { @@ -1263,7 +1263,7 @@ mod tests { transfer-encoding: chnked\r\n\r\n"); let req = parse_ready!(&mut buf); - assert!(req.payload().eof()); + assert!(req.payload().is_none()); if let Ok(val) = req.chunked() { assert!(!val); } else { @@ -1323,7 +1323,7 @@ mod tests { let mut req = parse_ready!(&mut buf); assert!(!req.keep_alive()); assert!(req.upgrade()); - assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"some raw data"); + assert_eq!(req.payload_mut().unwrap().readall().unwrap().as_ref(), b"some raw data"); } #[test] @@ -1371,13 +1371,13 @@ mod tests { let mut reader = Reader::new(); let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf)); assert!(req.chunked().unwrap()); - assert!(!req.payload().eof()); + assert!(!req.payload().unwrap().eof()); buf.feed_data("4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); not_ready!(reader.parse(&mut buf, &mut readbuf)); - assert!(!req.payload().eof()); - assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"dataline"); - assert!(req.payload().eof()); + assert!(!req.payload().unwrap().eof()); + assert_eq!(req.payload_mut().unwrap().readall().unwrap().as_ref(), b"dataline"); + assert!(req.payload().unwrap().eof()); } #[test] @@ -1391,7 +1391,7 @@ mod tests { let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf)); assert!(req.chunked().unwrap()); - assert!(!req.payload().eof()); + assert!(!req.payload().unwrap().eof()); buf.feed_data( "4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\ @@ -1401,10 +1401,10 @@ mod tests { let req2 = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf)); assert_eq!(*req2.method(), Method::POST); assert!(req2.chunked().unwrap()); - assert!(!req2.payload().eof()); + assert!(!req2.payload().unwrap().eof()); - assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"dataline"); - assert!(req.payload().eof()); + assert_eq!(req.payload_mut().unwrap().readall().unwrap().as_ref(), b"dataline"); + assert!(req.payload().unwrap().eof()); } #[test] @@ -1417,7 +1417,7 @@ mod tests { let mut reader = Reader::new(); let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf)); assert!(req.chunked().unwrap()); - assert!(!req.payload().eof()); + assert!(!req.payload().unwrap().eof()); buf.feed_data("4\r\ndata\r"); not_ready!(reader.parse(&mut buf, &mut readbuf)); @@ -1439,12 +1439,12 @@ mod tests { //buf.feed_data("test: test\r\n"); //not_ready!(reader.parse(&mut buf, &mut readbuf)); - assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"dataline"); - assert!(!req.payload().eof()); + assert_eq!(req.payload_mut().unwrap().readall().unwrap().as_ref(), b"dataline"); + assert!(!req.payload().unwrap().eof()); buf.feed_data("\r\n"); not_ready!(reader.parse(&mut buf, &mut readbuf)); - assert!(req.payload().eof()); + assert!(req.payload().unwrap().eof()); } #[test] @@ -1457,13 +1457,13 @@ mod tests { let mut reader = Reader::new(); let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf)); assert!(req.chunked().unwrap()); - assert!(!req.payload().eof()); + assert!(!req.payload().unwrap().eof()); buf.feed_data("4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") not_ready!(reader.parse(&mut buf, &mut readbuf)); - assert!(!req.payload().eof()); - assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"dataline"); - assert!(req.payload().eof()); + assert!(!req.payload().unwrap().eof()); + assert_eq!(req.payload_mut().unwrap().readall().unwrap().as_ref(), b"dataline"); + assert!(req.payload().unwrap().eof()); } /*#[test] diff --git a/src/httprequest.rs b/src/httprequest.rs index 266a4e945..7d5fbb740 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -114,7 +114,7 @@ impl HttpRequest<()> { params: Params::default(), cookies: None, addr: None, - payload: Payload::empty(), + payload: None, extensions: Extensions::new(), info: None, }), @@ -530,7 +530,6 @@ mod tests { use http::Uri; use std::str::FromStr; use router::Pattern; - use payload::Payload; use resource::Resource; #[test] @@ -539,8 +538,7 @@ mod tests { headers.insert(header::TRANSFER_ENCODING, header::HeaderValue::from_static("chunked")); let mut req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, Payload::empty()); + Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); assert_eq!(req.urlencoded().err().unwrap(), UrlencodedError::Chunked); @@ -550,8 +548,7 @@ mod tests { headers.insert(header::CONTENT_LENGTH, header::HeaderValue::from_static("xxxx")); let mut req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, - headers, Payload::empty()); + Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); assert_eq!(req.urlencoded().err().unwrap(), UrlencodedError::UnknownLength); @@ -561,8 +558,7 @@ mod tests { headers.insert(header::CONTENT_LENGTH, header::HeaderValue::from_static("1000000")); let mut req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, Payload::empty()); + Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); assert_eq!(req.urlencoded().err().unwrap(), UrlencodedError::Overflow); @@ -572,8 +568,7 @@ mod tests { headers.insert(header::CONTENT_LENGTH, header::HeaderValue::from_static("10")); let mut req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, Payload::empty()); + Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); assert_eq!(req.urlencoded().err().unwrap(), UrlencodedError::ContentType); } @@ -584,8 +579,7 @@ mod tests { headers.insert(header::HOST, header::HeaderValue::from_static("www.rust-lang.org")); let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, Payload::empty()); + Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); let mut resource = Resource::default(); resource.name("index"); @@ -612,7 +606,7 @@ mod tests { fn test_url_for_external() { let req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); let mut resource = Resource::<()>::default(); resource.name("index"); diff --git a/src/middlewares/logger.rs b/src/middlewares/logger.rs index 57b12cf81..507908148 100644 --- a/src/middlewares/logger.rs +++ b/src/middlewares/logger.rs @@ -291,7 +291,6 @@ mod tests { use time; use http::{Method, Version, StatusCode, Uri}; use http::header::{self, HeaderMap}; - use payload::Payload; #[test] fn test_logger() { @@ -300,8 +299,7 @@ mod tests { let mut headers = HeaderMap::new(); headers.insert(header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB")); let mut req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, Payload::empty()); + Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); let resp = HttpResponse::build(StatusCode::OK) .header("X-Test", "ttt") .force_close().body(Body::Empty).unwrap(); @@ -332,8 +330,7 @@ mod tests { let mut headers = HeaderMap::new(); headers.insert(header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB")); let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, Payload::empty()); + Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); let resp = HttpResponse::build(StatusCode::OK) .force_close().body(Body::Empty).unwrap(); let entry_time = time::now(); @@ -351,7 +348,7 @@ mod tests { let req = HttpRequest::new( Method::GET, Uri::from_str("/?test").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); let resp = HttpResponse::build(StatusCode::OK) .force_close().body(Body::Empty).unwrap(); let entry_time = time::now(); diff --git a/src/pred.rs b/src/pred.rs index c907d2793..82283899f 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -153,7 +153,6 @@ mod tests { use std::str::FromStr; use http::{Uri, Version, Method}; use http::header::{self, HeaderMap}; - use payload::Payload; #[test] fn test_header() { @@ -161,8 +160,7 @@ mod tests { headers.insert(header::TRANSFER_ENCODING, header::HeaderValue::from_static("chunked")); let mut req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, Payload::empty()); + Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); let pred = Header("transfer-encoding", "chunked"); assert!(pred.check(&mut req)); @@ -178,10 +176,10 @@ mod tests { fn test_methods() { let mut req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); let mut req2 = HttpRequest::new( Method::POST, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); assert!(Get().check(&mut req)); assert!(!Get().check(&mut req2)); @@ -190,43 +188,43 @@ mod tests { let mut r = HttpRequest::new( Method::PUT, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); assert!(Put().check(&mut r)); assert!(!Put().check(&mut req)); let mut r = HttpRequest::new( Method::DELETE, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); assert!(Delete().check(&mut r)); assert!(!Delete().check(&mut req)); let mut r = HttpRequest::new( Method::HEAD, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); assert!(Head().check(&mut r)); assert!(!Head().check(&mut req)); let mut r = HttpRequest::new( Method::OPTIONS, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); assert!(Options().check(&mut r)); assert!(!Options().check(&mut req)); let mut r = HttpRequest::new( Method::CONNECT, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); assert!(Connect().check(&mut r)); assert!(!Connect().check(&mut req)); let mut r = HttpRequest::new( Method::PATCH, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); assert!(Patch().check(&mut r)); assert!(!Patch().check(&mut req)); let mut r = HttpRequest::new( Method::TRACE, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); assert!(Trace().check(&mut r)); assert!(!Trace().check(&mut req)); } @@ -235,7 +233,7 @@ mod tests { fn test_preds() { let mut r = HttpRequest::new( Method::TRACE, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); assert!(Not(Get()).check(&mut r)); assert!(!Not(Trace()).check(&mut r)); diff --git a/src/router.rs b/src/router.rs index b6e1313dc..b5cdd8330 100644 --- a/src/router.rs +++ b/src/router.rs @@ -305,7 +305,6 @@ mod tests { use http::{Uri, Version, Method}; use http::header::HeaderMap; use std::str::FromStr; - use payload::Payload; #[test] fn test_recognizer() { @@ -320,39 +319,39 @@ mod tests { let mut req = HttpRequest::new( Method::GET, Uri::from_str("/name").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); assert!(rec.recognize(&mut req).is_some()); assert!(req.match_info().is_empty()); let mut req = HttpRequest::new( Method::GET, Uri::from_str("/name/value").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); assert!(rec.recognize(&mut req).is_some()); assert_eq!(req.match_info().get("val").unwrap(), "value"); assert_eq!(&req.match_info()["val"], "value"); let mut req = HttpRequest::new( Method::GET, Uri::from_str("/name/value2/index.html").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); assert!(rec.recognize(&mut req).is_some()); assert_eq!(req.match_info().get("val").unwrap(), "value2"); let mut req = HttpRequest::new( Method::GET, Uri::from_str("/vtest/ttt/index.html").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); assert!(rec.recognize(&mut req).is_some()); assert_eq!(req.match_info().get("val").unwrap(), "test"); assert_eq!(req.match_info().get("val2").unwrap(), "ttt"); let mut req = HttpRequest::new( Method::GET, Uri::from_str("/v/blah-blah/index.html").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); assert!(rec.recognize(&mut req).is_some()); assert_eq!(req.match_info().get("tail").unwrap(), "blah-blah/index.html"); let mut req = HttpRequest::new( Method::GET, Uri::from_str("/bbb/index.html").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); assert!(rec.recognize(&mut req).is_some()); assert_eq!(req.match_info().get("test").unwrap(), "bbb"); } diff --git a/src/ws.rs b/src/ws.rs index cd23526fc..65ddb5afe 100644 --- a/src/ws.rs +++ b/src/ws.rs @@ -339,31 +339,30 @@ impl WsWriter { mod tests { use super::*; use std::str::FromStr; - use payload::Payload; use http::{Method, HeaderMap, Version, Uri, header}; #[test] fn test_handshake() { let req = HttpRequest::new(Method::POST, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); assert_eq!(WsHandshakeError::GetMethodRequired, handshake(&req).err().unwrap()); let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); assert_eq!(WsHandshakeError::NoWebsocketUpgrade, handshake(&req).err().unwrap()); let mut headers = HeaderMap::new(); headers.insert(header::UPGRADE, header::HeaderValue::from_static("test")); let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, Payload::empty()); + Version::HTTP_11, headers, None); assert_eq!(WsHandshakeError::NoWebsocketUpgrade, handshake(&req).err().unwrap()); let mut headers = HeaderMap::new(); headers.insert(header::UPGRADE, header::HeaderValue::from_static("websocket")); let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, Payload::empty()); + Version::HTTP_11, headers, None); assert_eq!(WsHandshakeError::NoConnectionUpgrade, handshake(&req).err().unwrap()); let mut headers = HeaderMap::new(); @@ -372,7 +371,7 @@ mod tests { headers.insert(header::CONNECTION, header::HeaderValue::from_static("upgrade")); let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, Payload::empty()); + Version::HTTP_11, headers, None); assert_eq!(WsHandshakeError::NoVersionHeader, handshake(&req).err().unwrap()); let mut headers = HeaderMap::new(); @@ -383,7 +382,7 @@ mod tests { headers.insert(SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("5")); let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, Payload::empty()); + Version::HTTP_11, headers, None); assert_eq!(WsHandshakeError::UnsupportedVersion, handshake(&req).err().unwrap()); let mut headers = HeaderMap::new(); @@ -394,7 +393,7 @@ mod tests { headers.insert(SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13")); let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, Payload::empty()); + Version::HTTP_11, headers, None); assert_eq!(WsHandshakeError::BadWebsocketKey, handshake(&req).err().unwrap()); let mut headers = HeaderMap::new(); @@ -407,7 +406,7 @@ mod tests { headers.insert(SEC_WEBSOCKET_KEY, header::HeaderValue::from_static("13")); let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, Payload::empty()); + Version::HTTP_11, headers, None); assert_eq!(StatusCode::SWITCHING_PROTOCOLS, handshake(&req).unwrap().status()); } } diff --git a/tests/test_httprequest.rs b/tests/test_httprequest.rs index b6fecce57..aaaaf2764 100644 --- a/tests/test_httprequest.rs +++ b/tests/test_httprequest.rs @@ -13,16 +13,14 @@ use http::{header, Method, Version, HeaderMap, Uri}; #[test] fn test_debug() { let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, - HeaderMap::new(), Payload::empty()); + Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, HeaderMap::new(), None); let _ = format!("{:?}", req); } #[test] fn test_no_request_cookies() { let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, HeaderMap::new(), None); assert!(req.cookies().unwrap().is_empty()); } @@ -33,8 +31,7 @@ fn test_request_cookies() { header::HeaderValue::from_static("cookie1=value1; cookie2=value2")); let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, Payload::empty()); + Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); { let cookies = req.cookies().unwrap(); assert_eq!(cookies.len(), 2); @@ -57,7 +54,7 @@ fn test_request_cookies() { #[test] fn test_no_request_range_header() { let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); let ranges = req.range(100).unwrap(); assert!(ranges.is_empty()); } @@ -69,7 +66,7 @@ fn test_request_range_header() { header::HeaderValue::from_static("bytes=0-4")); let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, Payload::empty()); + Version::HTTP_11, headers, None); let ranges = req.range(100).unwrap(); assert_eq!(ranges.len(), 1); assert_eq!(ranges[0].start, 0); @@ -79,7 +76,7 @@ fn test_request_range_header() { #[test] fn test_request_query() { let req = HttpRequest::new(Method::GET, Uri::from_str("/?id=test").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); assert_eq!(req.query_string(), "id=test"); let query = req.query(); assert_eq!(&query["id"], "test"); @@ -88,7 +85,7 @@ fn test_request_query() { #[test] fn test_request_match_info() { let mut req = HttpRequest::new(Method::GET, Uri::from_str("/value/?id=test").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Version::HTTP_11, HeaderMap::new(), None); let mut resource = Resource::default(); resource.name("index"); @@ -103,16 +100,14 @@ fn test_request_match_info() { #[test] fn test_chunked() { let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), Payload::empty()); + Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, HeaderMap::new(), None); assert!(!req.chunked().unwrap()); let mut headers = HeaderMap::new(); headers.insert(header::TRANSFER_ENCODING, header::HeaderValue::from_static("chunked")); let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, - headers, Payload::empty()); + Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); assert!(req.chunked().unwrap()); let mut headers = HeaderMap::new(); @@ -122,6 +117,6 @@ fn test_chunked() { header::HeaderValue::from_str(s).unwrap()); let req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, Payload::empty()); + Version::HTTP_11, headers, None); assert!(req.chunked().is_err()); } diff --git a/tests/test_httpresponse.rs b/tests/test_httpresponse.rs index 53b1149b9..922e07a95 100644 --- a/tests/test_httpresponse.rs +++ b/tests/test_httpresponse.rs @@ -15,7 +15,7 @@ fn test_response_cookies() { header::HeaderValue::from_static("cookie1=value1; cookie2=value2")); let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, Payload::empty()); + Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); let cookies = req.cookies().unwrap(); let resp = httpcodes::HTTPOk From d4187f682b023df1b059a30787cbf3a428a276fd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Dec 2017 11:10:03 -0800 Subject: [PATCH 081/279] various cleanups --- Cargo.toml | 1 - src/context.rs | 4 +-- src/encoding.rs | 24 +++++++++--------- src/h1writer.rs | 17 ++++++++----- src/handler.rs | 10 ++++---- src/httpcodes.rs | 2 +- src/httpresponse.rs | 2 +- src/middlewares/defaultheaders.rs | 6 ++--- src/middlewares/mod.rs | 6 ++--- src/middlewares/session.rs | 8 +++--- src/param.rs | 2 +- src/pipeline.rs | 42 ++++++++++++++++--------------- src/server.rs | 1 + tests/test_server.rs | 8 +++--- 14 files changed, 70 insertions(+), 63 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 014f7701b..0133e25b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,6 @@ serde_derive = "1.0" [build-dependencies] skeptic = "0.13" -serde_derive = "1.0" version_check = "0.1" [profile.release] diff --git a/src/context.rs b/src/context.rs index c9f770147..f792370f5 100644 --- a/src/context.rs +++ b/src/context.rs @@ -25,7 +25,7 @@ pub(crate) trait IoContext: 'static { #[derive(Debug)] pub(crate) enum Frame { - Message(HttpResponse), + Message(Box), Payload(Option), Drain(Rc>), } @@ -141,7 +141,7 @@ impl HttpContext where A: Actor { Body::StreamingContext | Body::UpgradeContext => self.streaming = true, _ => (), } - self.stream.push_back(Frame::Message(resp)) + self.stream.push_back(Frame::Message(Box::new(resp))) } /// Write payload diff --git a/src/encoding.rs b/src/encoding.rs index be44990b7..e4af95929 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -125,9 +125,9 @@ impl PayloadWriter for PayloadType { } enum Decoder { - Deflate(DeflateDecoder>), - Gzip(Option>), - Br(BrotliDecoder>), + Deflate(Box>>), + Gzip(Box>>), + Br(Box>>), Identity, } @@ -158,10 +158,10 @@ impl EncodedPayload { pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload { let dec = match enc { ContentEncoding::Br => Decoder::Br( - BrotliDecoder::new(BytesMut::with_capacity(8192).writer())), + Box::new(BrotliDecoder::new(BytesMut::with_capacity(8192).writer()))), ContentEncoding::Deflate => Decoder::Deflate( - DeflateDecoder::new(BytesMut::with_capacity(8192).writer())), - ContentEncoding::Gzip => Decoder::Gzip(None), + Box::new(DeflateDecoder::new(BytesMut::with_capacity(8192).writer()))), + ContentEncoding::Gzip => Decoder::Gzip(Box::new(None)), _ => Decoder::Identity, }; EncodedPayload { @@ -204,13 +204,13 @@ impl PayloadWriter for EncodedPayload { } loop { let len = self.dst.get_ref().len(); - let len_buf = decoder.as_mut().unwrap().get_mut().buf.len(); + let len_buf = decoder.as_mut().as_mut().unwrap().get_mut().buf.len(); if len < len_buf * 2 { self.dst.get_mut().reserve(len_buf * 2 - len); unsafe{self.dst.get_mut().set_len(len_buf * 2)}; } - match decoder.as_mut().unwrap().read(&mut self.dst.get_mut()) { + match decoder.as_mut().as_mut().unwrap().read(&mut self.dst.get_mut()) { Ok(n) => { if n == 0 { self.inner.feed_eof(); @@ -271,13 +271,13 @@ impl PayloadWriter for EncodedPayload { if decoder.is_none() { let mut buf = BytesMut::new(); buf.extend(data); - *decoder = Some(GzDecoder::new(Wrapper{buf: buf}).unwrap()); + *(decoder.as_mut()) = Some(GzDecoder::new(Wrapper{buf: buf}).unwrap()); } else { - decoder.as_mut().unwrap().get_mut().buf.extend(data); + decoder.as_mut().as_mut().unwrap().get_mut().buf.extend(data); } loop { - let len_buf = decoder.as_mut().unwrap().get_mut().buf.len(); + let len_buf = decoder.as_mut().as_mut().unwrap().get_mut().buf.len(); if len_buf == 0 { return } @@ -287,7 +287,7 @@ impl PayloadWriter for EncodedPayload { self.dst.get_mut().reserve(len_buf * 2 - len); unsafe{self.dst.get_mut().set_len(len_buf * 2)}; } - match decoder.as_mut().unwrap().read(&mut self.dst.get_mut()) { + match decoder.as_mut().as_mut().unwrap().read(&mut self.dst.get_mut()) { Ok(n) => { if n == 0 { return diff --git a/src/h1writer.rs b/src/h1writer.rs index 58b6da58c..ec3c6c314 100644 --- a/src/h1writer.rs +++ b/src/h1writer.rs @@ -1,8 +1,7 @@ use std::io; -use std::fmt::Write; use futures::{Async, Poll}; use tokio_io::AsyncWrite; -use http::{Version, StatusCode}; +use http::Version; use http::header::{HeaderValue, CONNECTION, CONTENT_TYPE, DATE}; use date; @@ -151,11 +150,17 @@ impl Writer for H1Writer { buffer.reserve(100 + msg.headers().len() * AVERAGE_HEADER_SIZE); } - if version == Version::HTTP_11 && msg.status() == StatusCode::OK { - buffer.extend(b"HTTP/1.1 200 OK\r\n"); - } else { - let _ = write!(buffer, "{:?} {}\r\n", version, msg.status()); + match version { + Version::HTTP_11 => buffer.extend(b"HTTP/1.1 "), + Version::HTTP_2 => buffer.extend(b"HTTP/2.0 "), + Version::HTTP_10 => buffer.extend(b"HTTP/1.0 "), + Version::HTTP_09 => buffer.extend(b"HTTP/0.9 "), } + buffer.extend(msg.status().as_u16().to_string().as_bytes()); + buffer.extend(b" "); + buffer.extend(msg.reason().as_bytes()); + buffer.extend(b"\r\n"); + for (key, value) in msg.headers() { let t: &[u8] = key.as_ref(); buffer.extend(t); diff --git a/src/handler.rs b/src/handler.rs index 5c9ba94a8..f059e81c0 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -50,7 +50,7 @@ impl Handler for F pub struct Reply(ReplyItem); pub(crate) enum ReplyItem { - Message(HttpResponse), + Message(Box), Actor(Box), Future(Box>), } @@ -76,7 +76,7 @@ impl Reply { /// Send response #[inline] pub fn response>(response: R) -> Reply { - Reply(ReplyItem::Message(response.into())) + Reply(ReplyItem::Message(Box::new(response.into()))) } #[inline] @@ -107,14 +107,14 @@ impl FromRequest for HttpResponse { type Error = Error; fn from_request(self, _: HttpRequest) -> Result { - Ok(Reply(ReplyItem::Message(self))) + Ok(Reply(ReplyItem::Message(Box::new(self)))) } } impl From for Reply { fn from(resp: HttpResponse) -> Reply { - Reply(ReplyItem::Message(resp)) + Reply(ReplyItem::Message(Box::new(resp))) } } @@ -138,7 +138,7 @@ impl> From> for Reply { fn from(res: Result) -> Self { match res { Ok(val) => val, - Err(err) => Reply(ReplyItem::Message(err.into().into())), + Err(err) => Reply(ReplyItem::Message(Box::new(err.into().into()))), } } } diff --git a/src/httpcodes.rs b/src/httpcodes.rs index e2af3ec50..f00a2a5f5 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -166,7 +166,7 @@ mod tests { #[test] fn test_with_reason() { let resp = HTTPOk.response(); - assert_eq!(resp.reason(), ""); + assert_eq!(resp.reason(), "OK"); let resp = HTTPBadRequest.with_reason("test"); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); diff --git a/src/httpresponse.rs b/src/httpresponse.rs index ba4cb7b23..3ff4089c7 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -119,7 +119,7 @@ impl HttpResponse { if let Some(reason) = self.reason { reason } else { - "" + self.status.canonical_reason().unwrap_or("") } } diff --git a/src/middlewares/defaultheaders.rs b/src/middlewares/defaultheaders.rs index a0b772e90..3335847e0 100644 --- a/src/middlewares/defaultheaders.rs +++ b/src/middlewares/defaultheaders.rs @@ -37,7 +37,7 @@ impl DefaultHeaders { impl Middleware for DefaultHeaders { - fn response(&self, _: &mut HttpRequest, mut resp: HttpResponse) -> Response { + fn response(&self, _: &mut HttpRequest, mut resp: Box) -> Response { for (key, value) in self.0.iter() { if !resp.headers().contains_key(key) { resp.headers_mut().insert(key, value.clone()); @@ -97,14 +97,14 @@ mod tests { let mut req = HttpRequest::default(); let resp = HttpResponse::Ok().finish().unwrap(); - let resp = match mw.response(&mut req, resp) { + let resp = match mw.response(&mut req, Box::new(resp)) { Response::Done(resp) => resp, _ => panic!(), }; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish().unwrap(); - let resp = match mw.response(&mut req, resp) { + let resp = match mw.response(&mut req, Box::new(resp)) { Response::Done(resp) => resp, _ => panic!(), }; diff --git a/src/middlewares/mod.rs b/src/middlewares/mod.rs index b9798c97b..d5d88fc78 100644 --- a/src/middlewares/mod.rs +++ b/src/middlewares/mod.rs @@ -21,7 +21,7 @@ pub enum Started { Err(Error), /// New http response got generated. If middleware generates response /// handler execution halts. - Response(HttpResponse), + Response(Box), /// Execution completed, runs future to completion. Future(Box, Error=Error>>), } @@ -31,7 +31,7 @@ pub enum Response { /// Moddleware error Err(Error), /// New http response got generated - Done(HttpResponse), + Done(Box), /// Result is a future that resolves to a new http response Future(Box>), } @@ -56,7 +56,7 @@ pub trait Middleware { /// Method is called when handler returns response, /// but before sending http message to peer. - fn response(&self, req: &mut HttpRequest, resp: HttpResponse) -> Response { + fn response(&self, req: &mut HttpRequest, resp: Box) -> Response { Response::Done(resp) } diff --git a/src/middlewares/session.rs b/src/middlewares/session.rs index a807b0c03..d38fb0682 100644 --- a/src/middlewares/session.rs +++ b/src/middlewares/session.rs @@ -107,7 +107,7 @@ impl> Middleware for SessionStorage { Started::Future(Box::new(fut)) } - fn response(&self, req: &mut HttpRequest, resp: HttpResponse) -> Response { + fn response(&self, req: &mut HttpRequest, resp: Box) -> Response { if let Some(s_box) = req.extensions().remove::>() { s_box.0.write(resp) } else { @@ -129,7 +129,7 @@ pub trait SessionImpl: 'static { fn clear(&mut self); /// Write session to storage backend. - fn write(&self, resp: HttpResponse) -> Response; + fn write(&self, resp: Box) -> Response; } /// Session's storage backend trait definition. @@ -155,7 +155,7 @@ impl SessionImpl for DummySessionImpl { fn set(&mut self, key: &str, value: String) {} fn remove(&mut self, key: &str) {} fn clear(&mut self) {} - fn write(&self, resp: HttpResponse) -> Response { + fn write(&self, resp: Box) -> Response { Response::Done(resp) } } @@ -205,7 +205,7 @@ impl SessionImpl for CookieSession { self.state.clear() } - fn write(&self, mut resp: HttpResponse) -> Response { + fn write(&self, mut resp: Box) -> Response { if self.changed { let _ = self.inner.set_cookie(&mut resp, &self.state); } diff --git a/src/param.rs b/src/param.rs index 63c37f13b..b948ac187 100644 --- a/src/param.rs +++ b/src/param.rs @@ -20,7 +20,7 @@ pub trait FromParam: Sized { /// /// If resource path contains variable patterns, `Params` stores this variables. #[derive(Debug)] -pub struct Params<'a>(SmallVec<[(&'a str, &'a str); 4]>); +pub struct Params<'a>(SmallVec<[(&'a str, &'a str); 3]>); impl<'a> Default for Params<'a> { fn default() -> Params<'a> { diff --git a/src/pipeline.rs b/src/pipeline.rs index cea38fa9e..9c7aa60d4 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -141,7 +141,8 @@ impl Pipeline { impl Pipeline<()> { pub fn error>(err: R) -> Box { Box::new(Pipeline( - PipelineInfo::new(HttpRequest::default()), ProcessResponse::init(err.into()))) + PipelineInfo::new( + HttpRequest::default()), ProcessResponse::init(Box::new(err.into())))) } } @@ -346,15 +347,15 @@ impl StartMiddlewares { fut: Some(fut)}), Ok(Async::Ready(resp)) => { if let Some(resp) = resp { - return RunMiddlewares::init(info, resp); + return RunMiddlewares::init(info, Box::new(resp)); } info.count += 1; } Err(err) => - return ProcessResponse::init(err.into()), + return ProcessResponse::init(Box::new(err.into())), }, Started::Err(err) => - return ProcessResponse::init(err.into()), + return ProcessResponse::init(Box::new(err.into())), } } } @@ -369,7 +370,7 @@ impl StartMiddlewares { Ok(Async::Ready(resp)) => { info.count += 1; if let Some(resp) = resp { - return Ok(RunMiddlewares::init(info, resp)); + return Ok(RunMiddlewares::init(info, Box::new(resp))); } if info.count == len { let reply = (unsafe{&*self.hnd})(info.req.clone()); @@ -387,13 +388,13 @@ impl StartMiddlewares { continue 'outer }, Started::Err(err) => - return Ok(ProcessResponse::init(err.into())) + return Ok(ProcessResponse::init(Box::new(err.into()))) } } } } Err(err) => - return Ok(ProcessResponse::init(err.into())) + return Ok(ProcessResponse::init(Box::new(err.into()))) } } } @@ -441,14 +442,14 @@ impl WaitingResponse { Ok(Async::Ready(None)) => { error!("Unexpected eof"); let err: Error = UnexpectedTaskFrame.into(); - return Ok(ProcessResponse::init(err.into())) + return Ok(ProcessResponse::init(Box::new(err.into()))) }, Ok(Async::NotReady) => { self.stream = PipelineResponse::Context(context); return Err(PipelineState::Handler(self)) }, Err(err) => - return Ok(ProcessResponse::init(err.into())) + return Ok(ProcessResponse::init(Box::new(err.into()))) } } }, @@ -459,9 +460,9 @@ impl WaitingResponse { Err(PipelineState::Handler(self)) } Ok(Async::Ready(response)) => - Ok(RunMiddlewares::init(info, response)), + Ok(RunMiddlewares::init(info, Box::new(response))), Err(err) => - Ok(ProcessResponse::init(err.into())), + Ok(ProcessResponse::init(Box::new(err.into()))), } } PipelineResponse::None => { @@ -481,7 +482,7 @@ struct RunMiddlewares { impl RunMiddlewares { - fn init(info: &mut PipelineInfo, mut resp: HttpResponse) -> PipelineState + fn init(info: &mut PipelineInfo, mut resp: Box) -> PipelineState { if info.count == 0 { return ProcessResponse::init(resp); @@ -493,7 +494,7 @@ impl RunMiddlewares { resp = match info.mws[curr].response(info.req_mut(), resp) { Response::Err(err) => { info.count = curr + 1; - return ProcessResponse::init(err.into()) + return ProcessResponse::init(Box::new(err.into())) } Response::Done(r) => { curr += 1; @@ -521,10 +522,10 @@ impl RunMiddlewares { return Ok(PipelineState::RunMiddlewares(self)), Ok(Async::Ready(resp)) => { self.curr += 1; - resp + Box::new(resp) } Err(err) => - return Ok(ProcessResponse::init(err.into())), + return Ok(ProcessResponse::init(Box::new(err.into()))), }; loop { @@ -533,7 +534,7 @@ impl RunMiddlewares { } else { match info.mws[self.curr].response(info.req_mut(), resp) { Response::Err(err) => - return Ok(ProcessResponse::init(err.into())), + return Ok(ProcessResponse::init(Box::new(err.into()))), Response::Done(r) => { self.curr += 1; resp = r @@ -550,7 +551,7 @@ impl RunMiddlewares { } struct ProcessResponse { - resp: HttpResponse, + resp: Box, iostate: IOState, running: RunningState, drain: DrainVec, @@ -596,6 +597,7 @@ impl IOState { } struct DrainVec(Vec>>); + impl Drop for DrainVec { fn drop(&mut self) { for drain in &mut self.0 { @@ -606,7 +608,7 @@ impl Drop for DrainVec { impl ProcessResponse { - fn init(resp: HttpResponse) -> PipelineState + fn init(resp: Box) -> PipelineState { PipelineState::Response( ProcessResponse{ resp: resp, @@ -786,14 +788,14 @@ impl ProcessResponse { /// Middlewares start executor struct FinishingMiddlewares { - resp: HttpResponse, + resp: Box, fut: Option>>, _s: PhantomData, } impl FinishingMiddlewares { - fn init(info: &mut PipelineInfo, resp: HttpResponse) -> PipelineState { + fn init(info: &mut PipelineInfo, resp: Box) -> PipelineState { if info.count == 0 { Completed::init(info) } else { diff --git a/src/server.rs b/src/server.rs index 5d03d4f9f..3e81e5422 100644 --- a/src/server.rs +++ b/src/server.rs @@ -267,6 +267,7 @@ impl HttpServer }; let msg = IoStream{ io: socket.into_tcp_stream(), peer: Some(addr), http2: false}; + println!("next: {}", next); wrks[next].unbounded_send(msg).expect("worker thread died"); next = (next + 1) % wrks.len(); } diff --git a/tests/test_server.rs b/tests/test_server.rs index b3b58b3b7..65a0a38e1 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -59,7 +59,7 @@ impl middlewares::Middleware for MiddlewareTest { middlewares::Started::Done } - fn response(&self, _: &mut HttpRequest, resp: HttpResponse) -> middlewares::Response { + fn response(&self, _: &mut HttpRequest, resp: Box) -> middlewares::Response { self.response.store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed); middlewares::Response::Done(resp) } @@ -85,9 +85,9 @@ fn test_middlewares() { HttpServer::new( move || vec![Application::new() - .middleware(MiddlewareTest{start: act_num1.clone(), - response: act_num2.clone(), - finish: act_num3.clone()}) + .middleware(MiddlewareTest{start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3)}) .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))]) .serve::<_, ()>("127.0.0.1:58904").unwrap(); sys.run(); From 6b61041aec84dc05e81c087ac9d8cfcee0ad4bac Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Dec 2017 11:16:26 -0800 Subject: [PATCH 082/279] move tests --- src/httprequest.rs | 112 ++++++++++++++++++++++++++++++++++ src/httpresponse.rs | 45 +++++++++++++- tests/test_httprequest.rs | 122 ------------------------------------- tests/test_httpresponse.rs | 41 ------------- 4 files changed, 155 insertions(+), 165 deletions(-) delete mode 100644 tests/test_httprequest.rs delete mode 100644 tests/test_httpresponse.rs diff --git a/src/httprequest.rs b/src/httprequest.rs index 7d5fbb740..4a5b1b0ee 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -532,6 +532,118 @@ mod tests { use router::Pattern; use resource::Resource; + #[test] + fn test_debug() { + let req = HttpRequest::new( + Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, HeaderMap::new(), None); + let dbg = format!("{:?}", req); + assert!(dbg.contains("HttpRequest")); + } + + #[test] + fn test_no_request_cookies() { + let req = HttpRequest::new( + Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, HeaderMap::new(), None); + assert!(req.cookies().unwrap().is_empty()); + } + + #[test] + fn test_request_cookies() { + let mut headers = HeaderMap::new(); + headers.insert(header::COOKIE, + header::HeaderValue::from_static("cookie1=value1; cookie2=value2")); + + let req = HttpRequest::new( + Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); + { + 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"); + } + + let cookie = req.cookie("cookie1"); + assert!(cookie.is_some()); + let cookie = cookie.unwrap(); + assert_eq!(cookie.name(), "cookie1"); + assert_eq!(cookie.value(), "value1"); + + let cookie = req.cookie("cookie-unknown"); + assert!(cookie.is_none()); + } + + #[test] + fn test_no_request_range_header() { + let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), + Version::HTTP_11, HeaderMap::new(), None); + let ranges = req.range(100).unwrap(); + assert!(ranges.is_empty()); + } + + #[test] + fn test_request_range_header() { + let mut headers = HeaderMap::new(); + headers.insert(header::RANGE, + header::HeaderValue::from_static("bytes=0-4")); + + let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), + Version::HTTP_11, headers, None); + let ranges = req.range(100).unwrap(); + assert_eq!(ranges.len(), 1); + assert_eq!(ranges[0].start, 0); + assert_eq!(ranges[0].length, 5); + } + + #[test] + fn test_request_query() { + let req = HttpRequest::new(Method::GET, Uri::from_str("/?id=test").unwrap(), + Version::HTTP_11, HeaderMap::new(), None); + assert_eq!(req.query_string(), "id=test"); + let query = req.query(); + assert_eq!(&query["id"], "test"); + } + + #[test] + fn test_request_match_info() { + let mut req = HttpRequest::new(Method::GET, Uri::from_str("/value/?id=test").unwrap(), + Version::HTTP_11, HeaderMap::new(), None); + + let mut resource = Resource::default(); + resource.name("index"); + let mut map = HashMap::new(); + map.insert(Pattern::new("index", "/{key}/"), Some(resource)); + let router = Router::new("", map); + assert!(router.recognize(&mut req).is_some()); + + assert_eq!(req.match_info().get("key"), Some("value")); + } + + #[test] + fn test_chunked() { + let req = HttpRequest::new( + Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, HeaderMap::new(), None); + assert!(!req.chunked().unwrap()); + + let mut headers = HeaderMap::new(); + headers.insert(header::TRANSFER_ENCODING, + header::HeaderValue::from_static("chunked")); + let req = HttpRequest::new( + Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); + assert!(req.chunked().unwrap()); + + let mut headers = HeaderMap::new(); + let s = unsafe{str::from_utf8_unchecked(b"some va\xadscc\xacas0xsdasdlue".as_ref())}; + + headers.insert(header::TRANSFER_ENCODING, + header::HeaderValue::from_str(s).unwrap()); + let req = HttpRequest::new( + Method::GET, Uri::from_str("/").unwrap(), + Version::HTTP_11, headers, None); + assert!(req.chunked().is_err()); + } + #[test] fn test_urlencoded_error() { let mut headers = HeaderMap::new(); diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 3ff4089c7..48b9e8877 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -600,7 +600,50 @@ impl FromRequest for BytesMut { #[cfg(test)] mod tests { use super::*; + use std::str::FromStr; + use time::Duration; + use http::{Method, Uri}; use body::Binary; + use {headers, httpcodes}; + + #[test] + fn test_debug() { + let resp = HttpResponse::Ok().finish().unwrap(); + let dbg = format!("{:?}", resp); + assert!(dbg.contains("HttpResponse")); + } + + #[test] + fn test_response_cookies() { + let mut headers = HeaderMap::new(); + headers.insert(header::COOKIE, + header::HeaderValue::from_static("cookie1=value1; cookie2=value2")); + + let req = HttpRequest::new( + Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); + let cookies = req.cookies().unwrap(); + + let resp = httpcodes::HTTPOk + .build() + .cookie(headers::Cookie::build("name", "value") + .domain("www.rust-lang.org") + .path("/test") + .http_only(true) + .max_age(Duration::days(1)) + .finish()) + .del_cookie(&cookies[0]) + .body(Body::Empty); + + assert!(resp.is_ok()); + let resp = resp.unwrap(); + + let mut val: Vec<_> = resp.headers().get_all("Set-Cookie") + .iter().map(|v| v.to_str().unwrap().to_owned()).collect(); + val.sort(); + assert!(val[0].starts_with("cookie1=; Max-Age=0;")); + assert_eq!( + val[1],"name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400"); + } #[test] fn test_basic_builder() { @@ -611,8 +654,6 @@ mod tests { .finish().unwrap(); assert_eq!(resp.version(), Some(Version::HTTP_10)); assert_eq!(resp.status(), StatusCode::NO_CONTENT); - - let _t = format!("{:?}", resp); } #[test] diff --git a/tests/test_httprequest.rs b/tests/test_httprequest.rs deleted file mode 100644 index aaaaf2764..000000000 --- a/tests/test_httprequest.rs +++ /dev/null @@ -1,122 +0,0 @@ -extern crate actix_web; -extern crate http; -extern crate time; - -use std::str; -use std::str::FromStr; -use std::collections::HashMap; -use actix_web::*; -use actix_web::dev::*; -use http::{header, Method, Version, HeaderMap, Uri}; - - -#[test] -fn test_debug() { - let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, HeaderMap::new(), None); - let _ = format!("{:?}", req); -} - -#[test] -fn test_no_request_cookies() { - let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, HeaderMap::new(), None); - assert!(req.cookies().unwrap().is_empty()); -} - -#[test] -fn test_request_cookies() { - let mut headers = HeaderMap::new(); - headers.insert(header::COOKIE, - header::HeaderValue::from_static("cookie1=value1; cookie2=value2")); - - let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); - { - 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"); - } - - let cookie = req.cookie("cookie1"); - assert!(cookie.is_some()); - let cookie = cookie.unwrap(); - assert_eq!(cookie.name(), "cookie1"); - assert_eq!(cookie.value(), "value1"); - - let cookie = req.cookie("cookie-unknown"); - assert!(cookie.is_none()); -} - -#[test] -fn test_no_request_range_header() { - let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); - let ranges = req.range(100).unwrap(); - assert!(ranges.is_empty()); -} - -#[test] -fn test_request_range_header() { - let mut headers = HeaderMap::new(); - headers.insert(header::RANGE, - header::HeaderValue::from_static("bytes=0-4")); - - let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, None); - let ranges = req.range(100).unwrap(); - assert_eq!(ranges.len(), 1); - assert_eq!(ranges[0].start, 0); - assert_eq!(ranges[0].length, 5); -} - -#[test] -fn test_request_query() { - let req = HttpRequest::new(Method::GET, Uri::from_str("/?id=test").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); - assert_eq!(req.query_string(), "id=test"); - let query = req.query(); - assert_eq!(&query["id"], "test"); -} - -#[test] -fn test_request_match_info() { - let mut req = HttpRequest::new(Method::GET, Uri::from_str("/value/?id=test").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); - - let mut resource = Resource::default(); - resource.name("index"); - let mut map = HashMap::new(); - map.insert(Pattern::new("index", "/{key}/"), Some(resource)); - let router = Router::new("", map); - assert!(router.recognize(&mut req).is_some()); - - assert_eq!(req.match_info().get("key"), Some("value")); -} - -#[test] -fn test_chunked() { - let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, HeaderMap::new(), None); - assert!(!req.chunked().unwrap()); - - let mut headers = HeaderMap::new(); - headers.insert(header::TRANSFER_ENCODING, - header::HeaderValue::from_static("chunked")); - let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); - assert!(req.chunked().unwrap()); - - let mut headers = HeaderMap::new(); - let s = unsafe{str::from_utf8_unchecked(b"some va\xadscc\xacas0xsdasdlue".as_ref())}; - - headers.insert(header::TRANSFER_ENCODING, - header::HeaderValue::from_str(s).unwrap()); - let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, None); - assert!(req.chunked().is_err()); -} diff --git a/tests/test_httpresponse.rs b/tests/test_httpresponse.rs deleted file mode 100644 index 922e07a95..000000000 --- a/tests/test_httpresponse.rs +++ /dev/null @@ -1,41 +0,0 @@ -extern crate actix_web; -extern crate http; -extern crate time; - -use actix_web::*; -use std::str::FromStr; -use time::Duration; -use http::{header, Method, Version, HeaderMap, Uri}; - - -#[test] -fn test_response_cookies() { - let mut headers = HeaderMap::new(); - headers.insert(header::COOKIE, - header::HeaderValue::from_static("cookie1=value1; cookie2=value2")); - - let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); - let cookies = req.cookies().unwrap(); - - let resp = httpcodes::HTTPOk - .build() - .cookie(headers::Cookie::build("name", "value") - .domain("www.rust-lang.org") - .path("/test") - .http_only(true) - .max_age(Duration::days(1)) - .finish()) - .del_cookie(&cookies[0]) - .body(Body::Empty); - - assert!(resp.is_ok()); - let resp = resp.unwrap(); - - let mut val: Vec<_> = resp.headers().get_all("Set-Cookie") - .iter().map(|v| v.to_str().unwrap().to_owned()).collect(); - val.sort(); - assert!(val[0].starts_with("cookie1=; Max-Age=0;")); - assert_eq!( - val[1],"name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400"); -} From 81f8da03ae9cb0092ca0b093daeff393120b640d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Dec 2017 12:47:07 -0800 Subject: [PATCH 083/279] refactor http workers --- src/encoding.rs | 14 +-- src/h1writer.rs | 4 +- src/server.rs | 306 ++++++++++++++++-------------------------------- 3 files changed, 106 insertions(+), 218 deletions(-) diff --git a/src/encoding.rs b/src/encoding.rs index e4af95929..489dcf251 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -379,8 +379,7 @@ impl PayloadEncoder { error!("Chunked transfer is enabled but body is set to Empty"); } resp.headers_mut().insert(CONTENT_LENGTH, HeaderValue::from_static("0")); - resp.headers_mut().remove(TRANSFER_ENCODING); - TransferEncoding::length(0) + TransferEncoding::eof() }, Body::Binary(ref mut bytes) => { if compression { @@ -410,8 +409,7 @@ impl PayloadEncoder { resp.headers_mut().insert( CONTENT_LENGTH, HeaderValue::from_str(&bytes.len().to_string()).unwrap()); - resp.headers_mut().remove(TRANSFER_ENCODING); - TransferEncoding::length(bytes.len() as u64) + TransferEncoding::eof() } } Body::Streaming(_) | Body::StreamingContext => { @@ -555,7 +553,7 @@ impl ContentEncoder { } } - #[inline] + #[inline(always)] pub fn write_eof(&mut self) -> Result<(), io::Error> { let encoder = mem::replace(self, ContentEncoder::Identity(TransferEncoding::eof())); @@ -594,7 +592,7 @@ impl ContentEncoder { } } - #[inline] + #[inline(always)] pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> { match *self { ContentEncoder::Br(ref mut encoder) => { @@ -694,7 +692,7 @@ impl TransferEncoding { } /// Encode message. Return `EOF` state of encoder - #[inline] + #[inline(always)] pub fn encode(&mut self, msg: &[u8]) -> bool { match self.kind { TransferEncodingKind::Eof => { @@ -732,7 +730,7 @@ impl TransferEncoding { } /// Encode eof. Return `EOF` state of encoder - #[inline] + #[inline(always)] pub fn encode_eof(&mut self) { match self.kind { TransferEncodingKind::Eof | TransferEncodingKind::Length(_) => (), diff --git a/src/h1writer.rs b/src/h1writer.rs index ec3c6c314..186cdf138 100644 --- a/src/h1writer.rs +++ b/src/h1writer.rs @@ -145,9 +145,9 @@ impl Writer for H1Writer { { let buffer = self.encoder.get_mut(); if let Body::Binary(ref bytes) = *msg.body() { - buffer.reserve(100 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len()); + buffer.reserve(130 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len()); } else { - buffer.reserve(100 + msg.headers().len() * AVERAGE_HEADER_SIZE); + buffer.reserve(130 + msg.headers().len() * AVERAGE_HEADER_SIZE); } match version { diff --git a/src/server.rs b/src/server.rs index 3e81e5422..433ee5af2 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,6 +1,7 @@ use std::{io, net, thread}; use std::rc::Rc; use std::sync::Arc; +//use std::time::Duration; use std::marker::PhantomData; use actix::dev::*; @@ -207,7 +208,7 @@ impl HttpServer } } - fn start_workers(&mut self, settings: &ServerSettings) + fn start_workers(&mut self, settings: &ServerSettings, handler: &StreamHandlerType) -> Vec>> { // start workers @@ -216,6 +217,7 @@ impl HttpServer let s = settings.clone(); let (tx, rx) = mpsc::unbounded::>(); + let h = handler.clone(); let factory = Arc::clone(&self.factory); let addr = Arbiter::start(move |ctx: &mut Context<_>| { let mut apps: Vec<_> = (*factory)() @@ -224,7 +226,7 @@ impl HttpServer app.server_settings(s.clone()); } ctx.add_stream(rx); - Worker{h: Rc::new(apps)} + Worker{h: Rc::new(apps), handler: h} }); workers.push(tx); self.workers.push(addr); @@ -250,32 +252,12 @@ impl HttpServer { let addrs = self.bind(addr)?; let settings = ServerSettings::new(Some(addrs[0].0), false); - let workers = self.start_workers(&settings); + let workers = self.start_workers(&settings, &StreamHandlerType::Normal); // start acceptors threads for (addr, sock) in addrs { - let wrks = workers.clone(); - let _ = thread::Builder::new().name(format!("Accept on {}", addr)).spawn(move || { - let mut next = 0; - loop { - match sock.accept() { - Ok((socket, addr)) => { - let addr = if let Some(addr) = addr.as_inet() { - net::SocketAddr::V4(addr) - } else { - net::SocketAddr::V6(addr.as_inet6().unwrap()) - }; - let msg = IoStream{ - io: socket.into_tcp_stream(), peer: Some(addr), http2: false}; - println!("next: {}", next); - wrks[next].unbounded_send(msg).expect("worker thread died"); - next = (next + 1) % wrks.len(); - } - Err(err) => error!("Error accepting connection: {:?}", err), - } - } - }); info!("Starting http server on {}", addr); + start_accept_thread(sock, addr, workers.clone()); } // start http server actor @@ -292,7 +274,7 @@ impl HttpServer, net::SocketAddr, H, /// /// This methods converts address to list of `SocketAddr` /// then binds to all available addresses. - pub fn serve_tls(self, addr: S, pkcs12: ::Pkcs12) -> io::Result + pub fn serve_tls(mut self, addr: S, pkcs12: ::Pkcs12) -> io::Result where Self: ActorAddress, S: net::ToSocketAddrs, { @@ -307,52 +289,12 @@ impl HttpServer, net::SocketAddr, H, } Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err)) }; - - // start workers - let mut workers = Vec::new(); - for _ in 0..self.threads { - let s = settings.clone(); - let (tx, rx) = mpsc::unbounded::>(); - - let acc = acceptor.clone(); - let factory = Arc::clone(&self.factory); - let _addr = Arbiter::start(move |ctx: &mut Context<_>| { - let mut apps: Vec<_> = (*factory)() - .into_iter().map(|h| h.into_handler()).collect(); - for app in &mut apps { - app.server_settings(s.clone()); - } - ctx.add_stream(rx); - TlsWorker{h: Rc::new(apps), acceptor: acc} - }); - workers.push(tx); - // self.workers.push(addr); - } - info!("Starting {} http workers", self.threads); + let workers = self.start_workers(&settings, &StreamHandlerType::Tls(acceptor)); // start acceptors threads for (addr, sock) in addrs { - let wrks = workers.clone(); - let _ = thread::Builder::new().name(format!("Accept on {}", addr)).spawn(move || { - let mut next = 0; - loop { - match sock.accept() { - Ok((socket, addr)) => { - let addr = if let Some(addr) = addr.as_inet() { - net::SocketAddr::V4(addr) - } else { - net::SocketAddr::V6(addr.as_inet6().unwrap()) - }; - let msg = IoStream{ - io: socket.into_tcp_stream(), peer: Some(addr), http2: false}; - wrks[next].unbounded_send(msg).expect("worker thread died"); - next = (next + 1) % wrks.len(); - } - Err(err) => error!("Error accepting connection: {:?}", err), - } - } - }); info!("Starting tls http server on {}", addr); + start_accept_thread(sock, addr, workers.clone()); } // start http server actor @@ -369,7 +311,7 @@ impl HttpServer, net::SocketAddr, H, /// /// This methods converts address to list of `SocketAddr` /// then binds to all available addresses. - pub fn serve_tls(self, addr: S, identity: &ParsedPkcs12) -> io::Result + pub fn serve_tls(mut self, addr: S, identity: &ParsedPkcs12) -> io::Result where Self: ActorAddress, S: net::ToSocketAddrs, { @@ -386,52 +328,12 @@ impl HttpServer, net::SocketAddr, H, }, Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err)) }; - - // start workers - let mut workers = Vec::new(); - for _ in 0..self.threads { - let s = settings.clone(); - let (tx, rx) = mpsc::unbounded::>(); - - let acc = acceptor.clone(); - let factory = Arc::clone(&self.factory); - let _addr = Arbiter::start(move |ctx: &mut Context<_>| { - let mut apps: Vec<_> = (*factory)() - .into_iter().map(|h| h.into_handler()).collect(); - for app in &mut apps { - app.server_settings(s.clone()); - } - ctx.add_stream(rx); - AlpnWorker{h: Rc::new(apps), acceptor: acc} - }); - workers.push(tx); - // self.workers.push(addr); - } - info!("Starting {} http workers", self.threads); + let workers = self.start_workers(&settings, &StreamHandlerType::Alpn(acceptor)); // start acceptors threads for (addr, sock) in addrs { - let wrks = workers.clone(); - let _ = thread::Builder::new().name(format!("Accept on {}", addr)).spawn(move || { - let mut next = 0; - loop { - match sock.accept() { - Ok((socket, addr)) => { - let addr = if let Some(addr) = addr.as_inet() { - net::SocketAddr::V4(addr) - } else { - net::SocketAddr::V6(addr.as_inet6().unwrap()) - }; - let msg = IoStream{ - io: socket.into_tcp_stream(), peer: Some(addr), http2: false}; - wrks[next].unbounded_send(msg).expect("worker thread died"); - next = (next + 1) % wrks.len(); - } - Err(err) => error!("Error accepting connection: {:?}", err), - } - } - }); info!("Starting tls http server on {}", addr); + start_accept_thread(sock, addr, workers.clone()); } // start http server actor @@ -482,10 +384,15 @@ impl Handler, io::Error> for HttpServer /// Worker accepts Socket objects via unbounded channel and start requests processing. struct Worker { h: Rc>, + handler: StreamHandlerType, } impl Actor for Worker { type Context = Context; + + fn started(&mut self, ctx: &mut Self::Context) { + + } } impl StreamHandler> for Worker @@ -497,113 +404,96 @@ impl Handler> for Worker fn handle(&mut self, msg: IoStream, _: &mut Context) -> Response> { - let io = TcpStream::from_stream(msg.io, Arbiter::handle()) - .expect("failed to associate TCP stream"); - - Arbiter::handle().spawn( - HttpChannel::new(Rc::clone(&self.h), io, msg.peer, msg.http2)); + self.handler.handle(Rc::clone(&self.h), msg); Self::empty() } } -/// Tls http workers -/// -/// Worker accepts Socket objects via unbounded channel and start requests processing. -#[cfg(feature="tls")] -struct TlsWorker { - h: Rc>, - acceptor: TlsAcceptor, +#[derive(Clone)] +enum StreamHandlerType { + Normal, + #[cfg(feature="tls")] + Tls(TlsAcceptor), + #[cfg(feature="alpn")] + Alpn(SslAcceptor), } -#[cfg(feature="tls")] -impl Actor for TlsWorker { - type Context = Context; -} +impl StreamHandlerType { + fn handle(&mut self, h: Rc>, msg: IoStream) { + match *self { + StreamHandlerType::Normal => { + let io = TcpStream::from_stream(msg.io, Arbiter::handle()) + .expect("failed to associate TCP stream"); -#[cfg(feature="tls")] -impl StreamHandler> for TlsWorker - where H: HttpHandler + 'static {} + Arbiter::handle().spawn(HttpChannel::new(h, io, msg.peer, msg.http2)); + } + #[cfg(feature="tls")] + StreamHandlerType::Tls(ref acceptor) => { + let IoStream { io, peer, http2 } = msg; + let io = TcpStream::from_stream(io, Arbiter::handle()) + .expect("failed to associate TCP stream"); -#[cfg(feature="tls")] -impl Handler> for TlsWorker - where H: HttpHandler + 'static, -{ - fn handle(&mut self, msg: IoStream, _: &mut Context) - -> Response> - { - let IoStream { io, peer, http2 } = msg; - let io = TcpStream::from_stream(io, Arbiter::handle()) - .expect("failed to associate TCP stream"); - - let h = Rc::clone(&self.h); - - Arbiter::handle().spawn( - TlsAcceptorExt::accept_async(&self.acceptor, io).then(move |res| { - match res { - Ok(io) => Arbiter::handle().spawn( - HttpChannel::new(h, io, peer, http2)), - Err(err) => - trace!("Error during handling tls connection: {}", err), - }; - future::result(Ok(())) - }) - ); - - Self::empty() - } -} - -/// Tls http workers with alpn support -/// -/// Worker accepts Socket objects via unbounded channel and start requests processing. -#[cfg(feature="alpn")] -struct AlpnWorker { - h: Rc>, - acceptor: SslAcceptor, -} - -#[cfg(feature="alpn")] -impl Actor for AlpnWorker { - type Context = Context; -} - -#[cfg(feature="alpn")] -impl StreamHandler> for AlpnWorker - where H: HttpHandler + 'static {} - -#[cfg(feature="alpn")] -impl Handler> for AlpnWorker - where H: HttpHandler + 'static, -{ - fn handle(&mut self, msg: IoStream, _: &mut Context) - -> Response> - { - let IoStream { io, peer, .. } = msg; - let io = TcpStream::from_stream(io, Arbiter::handle()) - .expect("failed to associate TCP stream"); - - let h = Rc::clone(&self.h); - - Arbiter::handle().spawn( - SslAcceptorExt::accept_async(&self.acceptor, io).then(move |res| { - match res { - Ok(io) => { - let http2 = if let Some(p) = io.get_ref().ssl().selected_alpn_protocol() - { - p.len() == 2 && &p == b"h2" - } else { - false + Arbiter::handle().spawn( + TlsAcceptorExt::accept_async(acceptor, io).then(move |res| { + match res { + Ok(io) => Arbiter::handle().spawn( + HttpChannel::new(h, io, peer, http2)), + Err(err) => + trace!("Error during handling tls connection: {}", err), }; - Arbiter::handle().spawn( - HttpChannel::new(h, io, peer, http2)); - }, - Err(err) => - trace!("Error during handling tls connection: {}", err), - }; - future::result(Ok(())) - }) - ); + future::result(Ok(())) + }) + ); + } + #[cfg(feature="alpn")] + StreamHandlerType::Alpn(ref acceptor) => { + let IoStream { io, peer, .. } = msg; + let io = TcpStream::from_stream(io, Arbiter::handle()) + .expect("failed to associate TCP stream"); - Self::empty() + Arbiter::handle().spawn( + SslAcceptorExt::accept_async(acceptor, io).then(move |res| { + match res { + Ok(io) => { + let http2 = if let Some(p) = io.get_ref().ssl().selected_alpn_protocol() + { + p.len() == 2 && &p == b"h2" + } else { + false + }; + Arbiter::handle().spawn(HttpChannel::new(h, io, peer, http2)); + }, + Err(err) => + trace!("Error during handling tls connection: {}", err), + }; + future::result(Ok(())) + }) + ); + } + } } } + +fn start_accept_thread(sock: Socket, addr: net::SocketAddr, + workers: Vec>>) { + // start acceptors thread + let _ = thread::Builder::new().name(format!("Accept on {}", addr)).spawn(move || { + let mut next = 0; + loop { + match sock.accept() { + Ok((socket, addr)) => { + let addr = if let Some(addr) = addr.as_inet() { + net::SocketAddr::V4(addr) + } else { + net::SocketAddr::V6(addr.as_inet6().unwrap()) + }; + let msg = IoStream{ + io: socket.into_tcp_stream(), peer: Some(addr), http2: false}; + workers[next].unbounded_send(msg).expect("worker thread died"); + next = (next + 1) % workers.len(); + } + Err(err) => error!("Error accepting connection: {:?}", err), + } + } + }); +} From 96f598f2c4562258e914a500c4f102f2f4ca2932 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Dec 2017 16:44:35 -0800 Subject: [PATCH 084/279] various optimizations --- src/body.rs | 4 + src/date.rs | 68 ----------------- src/encoding.rs | 49 ++++++++----- src/h1.rs | 17 ++--- src/h1writer.rs | 90 +++++++++++------------ src/h2writer.rs | 11 ++- src/httprequest.rs | 2 +- src/httpresponse.rs | 99 ++++++++----------------- src/lib.rs | 2 +- src/payload.rs | 8 +- src/server.rs | 35 ++++++--- src/utils.rs | 173 ++++++++++++++++++++++++++++++++++++++++++++ src/ws.rs | 2 +- 13 files changed, 328 insertions(+), 232 deletions(-) delete mode 100644 src/date.rs create mode 100644 src/utils.rs diff --git a/src/body.rs b/src/body.rs index 73bd8920c..34c06dd35 100644 --- a/src/body.rs +++ b/src/body.rs @@ -48,6 +48,7 @@ pub enum Binary { impl Body { /// Does this body streaming. + #[inline] pub fn is_streaming(&self) -> bool { match *self { Body::Streaming(_) | Body::StreamingContext @@ -57,6 +58,7 @@ impl Body { } /// Is this binary body. + #[inline] pub fn is_binary(&self) -> bool { match *self { Body::Binary(_) => true, @@ -114,10 +116,12 @@ impl From for Body where T: Into{ } impl Binary { + #[inline] pub fn is_empty(&self) -> bool { self.len() == 0 } + #[inline] pub fn len(&self) -> usize { match *self { Binary::Bytes(ref bytes) => bytes.len(), diff --git a/src/date.rs b/src/date.rs deleted file mode 100644 index 27ae1db22..000000000 --- a/src/date.rs +++ /dev/null @@ -1,68 +0,0 @@ -use std::cell::RefCell; -use std::fmt::{self, Write}; -use std::str; -use time::{self, Duration}; - -// "Sun, 06 Nov 1994 08:49:37 GMT".len() -pub const DATE_VALUE_LENGTH: usize = 29; - -pub fn extend(dst: &mut [u8]) { - CACHED.with(|cache| { - let mut cache = cache.borrow_mut(); - let now = time::get_time(); - if now > cache.next_update { - cache.update(now); - } - - dst.copy_from_slice(cache.buffer()); - }) -} - -struct CachedDate { - bytes: [u8; DATE_VALUE_LENGTH], - pos: usize, - next_update: time::Timespec, -} - -thread_local!(static CACHED: RefCell = RefCell::new(CachedDate { - bytes: [0; DATE_VALUE_LENGTH], - pos: 0, - next_update: time::Timespec::new(0, 0), -})); - -impl CachedDate { - fn buffer(&self) -> &[u8] { - &self.bytes[..] - } - - fn update(&mut self, now: time::Timespec) { - self.pos = 0; - write!(self, "{}", time::at_utc(now).rfc822()).unwrap(); - assert_eq!(self.pos, DATE_VALUE_LENGTH); - self.next_update = now + Duration::seconds(1); - self.next_update.nsec = 0; - } -} - -impl fmt::Write for CachedDate { - fn write_str(&mut self, s: &str) -> fmt::Result { - let len = s.len(); - self.bytes[self.pos..self.pos + len].copy_from_slice(s.as_bytes()); - self.pos += len; - Ok(()) - } -} - -#[test] -fn test_date_len() { - assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len()); -} - -#[test] -fn test_date() { - let mut buf1 = [0u8; 29]; - extend(&mut buf1); - let mut buf2 = [0u8; 29]; - extend(&mut buf2); - assert_eq!(buf1, buf2); -} diff --git a/src/encoding.rs b/src/encoding.rs index 489dcf251..c6a3df5f4 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -13,6 +13,7 @@ use flate2::write::{GzEncoder, DeflateDecoder, DeflateEncoder}; use brotli2::write::{BrotliDecoder, BrotliEncoder}; use bytes::{Bytes, BytesMut, BufMut, Writer}; +use utils; use body::{Body, Binary}; use error::PayloadError; use httprequest::HttpMessage; @@ -35,6 +36,14 @@ pub enum ContentEncoding { } impl ContentEncoding { + + fn is_compression(&self) -> bool { + match *self { + ContentEncoding::Identity | ContentEncoding::Auto => false, + _ => true + } + } + fn as_str(&self) -> &'static str { match *self { ContentEncoding::Br => "br", @@ -270,10 +279,10 @@ impl PayloadWriter for EncodedPayload { Decoder::Gzip(ref mut decoder) => { if decoder.is_none() { let mut buf = BytesMut::new(); - buf.extend(data); + buf.extend_from_slice(&data); *(decoder.as_mut()) = Some(GzDecoder::new(Wrapper{buf: buf}).unwrap()); } else { - decoder.as_mut().as_mut().unwrap().get_mut().buf.extend(data); + decoder.as_mut().as_mut().unwrap().get_mut().buf.extend_from_slice(&data); } loop { @@ -362,8 +371,10 @@ impl PayloadEncoder { } encoding => encoding, }; - resp.headers_mut().insert( - CONTENT_ENCODING, HeaderValue::from_static(encoding.as_str())); + if encoding.is_compression() { + resp.headers_mut().insert( + CONTENT_ENCODING, HeaderValue::from_static(encoding.as_str())); + } encoding } else { ContentEncoding::Identity @@ -400,15 +411,13 @@ impl PayloadEncoder { let b = enc.get_mut().take(); resp.headers_mut().insert( - CONTENT_LENGTH, - HeaderValue::from_str(&b.len().to_string()).unwrap()); + CONTENT_LENGTH, utils::convert_into_header(b.len())); *bytes = Binary::from(b); encoding = ContentEncoding::Identity; TransferEncoding::eof() } else { resp.headers_mut().insert( - CONTENT_LENGTH, - HeaderValue::from_str(&bytes.len().to_string()).unwrap()); + CONTENT_LENGTH, utils::convert_into_header(bytes.len())); TransferEncoding::eof() } } @@ -491,12 +500,14 @@ impl PayloadEncoder { self.0.is_eof() } - #[inline] + #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] + #[inline(always)] pub fn write(&mut self, payload: &[u8]) -> Result<(), io::Error> { self.0.write(payload) } - #[inline] + #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] + #[inline(always)] pub fn write_eof(&mut self) -> Result<(), io::Error> { self.0.write_eof() } @@ -553,6 +564,7 @@ impl ContentEncoder { } } + #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] #[inline(always)] pub fn write_eof(&mut self) -> Result<(), io::Error> { let encoder = mem::replace(self, ContentEncoder::Identity(TransferEncoding::eof())); @@ -592,6 +604,7 @@ impl ContentEncoder { } } + #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] #[inline(always)] pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> { match *self { @@ -692,11 +705,11 @@ impl TransferEncoding { } /// Encode message. Return `EOF` state of encoder - #[inline(always)] + #[inline] pub fn encode(&mut self, msg: &[u8]) -> bool { match self.kind { TransferEncodingKind::Eof => { - self.buffer.extend(msg); + self.buffer.extend_from_slice(msg); msg.is_empty() }, TransferEncodingKind::Chunked(ref mut eof) => { @@ -706,11 +719,11 @@ impl TransferEncoding { if msg.is_empty() { *eof = true; - self.buffer.extend(b"0\r\n\r\n"); + self.buffer.extend_from_slice(b"0\r\n\r\n"); } else { write!(self.buffer, "{:X}\r\n", msg.len()).unwrap(); - self.buffer.extend(msg); - self.buffer.extend(b"\r\n"); + self.buffer.extend_from_slice(msg); + self.buffer.extend_from_slice(b"\r\n"); } *eof }, @@ -720,7 +733,7 @@ impl TransferEncoding { } let max = cmp::min(*remaining, msg.len() as u64); trace!("sized write = {}", max); - self.buffer.extend(msg[..max as usize].as_ref()); + self.buffer.extend_from_slice(msg[..max as usize].as_ref()); *remaining -= max as u64; trace!("encoded {} bytes, remaining = {}", max, remaining); @@ -730,14 +743,14 @@ impl TransferEncoding { } /// Encode eof. Return `EOF` state of encoder - #[inline(always)] + #[inline] pub fn encode_eof(&mut self) { match self.kind { TransferEncodingKind::Eof | TransferEncodingKind::Length(_) => (), TransferEncodingKind::Chunked(ref mut eof) => { if !*eof { *eof = true; - self.buffer.extend(b"0\r\n\r\n"); + self.buffer.extend_from_slice(b"0\r\n\r\n"); } }, } diff --git a/src/h1.rs b/src/h1.rs index 8b929177e..3b47ca84a 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -88,8 +88,8 @@ impl Http1 keepalive_timer: None } } - pub fn into_inner(mut self) -> (Rc>, T, Option, Bytes) { - (self.handlers, self.stream.unwrap(), self.addr, self.read_buf.freeze()) + pub fn into_inner(self) -> (Rc>, T, Option, Bytes) { + (self.handlers, self.stream.into_inner(), self.addr, self.read_buf.freeze()) } pub fn poll(&mut self) -> Poll { @@ -129,7 +129,7 @@ impl Http1 } else { self.flags.remove(Flags::KEEPALIVE); } - self.stream = H1Writer::new(self.stream.unwrap()); + self.stream.reset(); item.flags.insert(EntryFlags::EOF); if ready { @@ -185,7 +185,7 @@ impl Http1 // read incoming data while !self.flags.contains(Flags::ERROR) && !self.flags.contains(Flags::H2) && - self.tasks.len() < MAX_PIPELINED_MESSAGES { + self.tasks.len() < MAX_PIPELINED_MESSAGES { match self.reader.parse(self.stream.get_mut(), &mut self.read_buf) { Ok(Async::Ready(Item::Http1(mut req))) => { not_ready = false; @@ -252,12 +252,12 @@ impl Http1 if self.flags.contains(Flags::KEEPALIVE) { if self.keepalive_timer.is_none() { trace!("Start keep-alive timer"); - let mut timeout = Timeout::new( + let mut to = Timeout::new( Duration::new(KEEPALIVE_PERIOD, 0), Arbiter::handle()).unwrap(); // register timeout - let _ = timeout.poll(); - self.keepalive_timer = Some(timeout); + let _ = to.poll(); + self.keepalive_timer = Some(to); } } else { // keep-alive disable, drop connection @@ -482,8 +482,7 @@ impl Reader { } } - fn parse_message(buf: &mut BytesMut) -> Result - { + fn parse_message(buf: &mut BytesMut) -> Result { if buf.is_empty() { return Ok(Message::NotReady); } diff --git a/src/h1writer.rs b/src/h1writer.rs index 186cdf138..7815df2cd 100644 --- a/src/h1writer.rs +++ b/src/h1writer.rs @@ -4,7 +4,7 @@ use tokio_io::AsyncWrite; use http::Version; use http::header::{HeaderValue, CONNECTION, CONTENT_TYPE, DATE}; -use date; +use utils; use body::Body; use encoding::PayloadEncoder; use httprequest::HttpMessage; @@ -45,7 +45,7 @@ bitflags! { pub(crate) struct H1Writer { flags: Flags, - stream: Option, + stream: T, encoder: PayloadEncoder, written: u64, headers_size: u32, @@ -56,7 +56,7 @@ impl H1Writer { pub fn new(stream: T) -> H1Writer { H1Writer { flags: Flags::empty(), - stream: Some(stream), + stream: stream, encoder: PayloadEncoder::default(), written: 0, headers_size: 0, @@ -64,11 +64,16 @@ impl H1Writer { } pub fn get_mut(&mut self) -> &mut T { - self.stream.as_mut().unwrap() + &mut self.stream } - pub fn unwrap(&mut self) -> T { - self.stream.take().unwrap() + pub fn reset(&mut self) { + self.written = 0; + self.flags = Flags::empty(); + } + + pub fn into_inner(self) -> T { + self.stream } pub fn disconnected(&mut self) { @@ -82,22 +87,20 @@ impl H1Writer { fn write_to_stream(&mut self) -> Result { let buffer = self.encoder.get_mut(); - if let Some(ref mut stream) = self.stream { - while !buffer.is_empty() { - match stream.write(buffer.as_ref()) { - Ok(n) => { - buffer.split_to(n); - self.written += n as u64; - }, - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { - if buffer.len() > MAX_WRITE_BUFFER_SIZE { - return Ok(WriterState::Pause) - } else { - return Ok(WriterState::Done) - } + while !buffer.is_empty() { + match self.stream.write(buffer.as_ref()) { + Ok(n) => { + buffer.split_to(n); + self.written += n as u64; + }, + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { + if buffer.len() > MAX_WRITE_BUFFER_SIZE { + return Ok(WriterState::Pause) + } else { + return Ok(WriterState::Done) } - Err(err) => return Err(err), } + Err(err) => return Err(err), } } Ok(WriterState::Done) @@ -143,50 +146,47 @@ impl Writer for H1Writer { // render message { - let buffer = self.encoder.get_mut(); + let mut buffer = self.encoder.get_mut(); if let Body::Binary(ref bytes) = *msg.body() { - buffer.reserve(130 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len()); + buffer.reserve(150 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len()); } else { - buffer.reserve(130 + msg.headers().len() * AVERAGE_HEADER_SIZE); + buffer.reserve(150 + msg.headers().len() * AVERAGE_HEADER_SIZE); } match version { - Version::HTTP_11 => buffer.extend(b"HTTP/1.1 "), - Version::HTTP_2 => buffer.extend(b"HTTP/2.0 "), - Version::HTTP_10 => buffer.extend(b"HTTP/1.0 "), - Version::HTTP_09 => buffer.extend(b"HTTP/0.9 "), + Version::HTTP_11 => buffer.extend_from_slice(b"HTTP/1.1 "), + Version::HTTP_2 => buffer.extend_from_slice(b"HTTP/2.0 "), + Version::HTTP_10 => buffer.extend_from_slice(b"HTTP/1.0 "), + Version::HTTP_09 => buffer.extend_from_slice(b"HTTP/0.9 "), } - buffer.extend(msg.status().as_u16().to_string().as_bytes()); - buffer.extend(b" "); - buffer.extend(msg.reason().as_bytes()); - buffer.extend(b"\r\n"); + utils::convert_u16(msg.status().as_u16(), &mut buffer); + buffer.extend_from_slice(b" "); + buffer.extend_from_slice(msg.reason().as_bytes()); + buffer.extend_from_slice(b"\r\n"); for (key, value) in msg.headers() { let t: &[u8] = key.as_ref(); - buffer.extend(t); - buffer.extend(b": "); - buffer.extend(value.as_ref()); - buffer.extend(b"\r\n"); + buffer.extend_from_slice(t); + buffer.extend_from_slice(b": "); + buffer.extend_from_slice(value.as_ref()); + buffer.extend_from_slice(b"\r\n"); } - // using http::h1::date is quite a lot faster than generating - // a unique Date header each time like req/s goes up about 10% + // using utils::date is quite a lot faster if !msg.headers().contains_key(DATE) { - buffer.reserve(date::DATE_VALUE_LENGTH + 8); - buffer.extend(b"Date: "); - let mut bytes = [0u8; 29]; - date::extend(&mut bytes[..]); - buffer.extend(&bytes); - buffer.extend(b"\r\n"); + buffer.reserve(utils::DATE_VALUE_LENGTH + 8); + buffer.extend_from_slice(b"Date: "); + utils::extend(&mut buffer); + buffer.extend_from_slice(b"\r\n"); } // default content-type if !msg.headers().contains_key(CONTENT_TYPE) { - buffer.extend(b"ContentType: application/octet-stream\r\n".as_ref()); + buffer.extend_from_slice(b"ContentType: application/octet-stream\r\n"); } // msg eof - buffer.extend(b"\r\n"); + buffer.extend_from_slice(b"\r\n"); self.headers_size = buffer.len() as u32; } diff --git a/src/h2writer.rs b/src/h2writer.rs index f2c07d651..bfc596dc9 100644 --- a/src/h2writer.rs +++ b/src/h2writer.rs @@ -1,12 +1,12 @@ use std::{io, cmp}; -use bytes::Bytes; +use bytes::{Bytes, BytesMut}; use futures::{Async, Poll}; use http2::{Reason, SendStream}; use http2::server::Respond; use http::{Version, HttpTryFrom, Response}; use http::header::{HeaderValue, CONNECTION, CONTENT_TYPE, TRANSFER_ENCODING, DATE}; -use date; +use utils; use body::Body; use encoding::PayloadEncoder; use httprequest::HttpMessage; @@ -124,11 +124,10 @@ impl Writer for H2Writer { msg.headers_mut().remove(CONNECTION); msg.headers_mut().remove(TRANSFER_ENCODING); - // using http::h1::date is quite a lot faster than generating - // a unique Date header each time like req/s goes up about 10% + // using utils::date is quite a lot faster if !msg.headers().contains_key(DATE) { - let mut bytes = [0u8; 29]; - date::extend(&mut bytes[..]); + let mut bytes = BytesMut::with_capacity(29); + utils::extend(&mut bytes); msg.headers_mut().insert(DATE, HeaderValue::try_from(&bytes[..]).unwrap()); } diff --git a/src/httprequest.rs b/src/httprequest.rs index 4a5b1b0ee..80aa24409 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -515,7 +515,7 @@ impl Future for UrlEncoded { Ok(Async::Ready(m)) }, Ok(Async::Ready(Some(item))) => { - self.body.extend(item.0); + self.body.extend_from_slice(&item.0); continue }, Err(err) => Err(err), diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 48b9e8877..3d3ca4cc0 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -47,8 +47,9 @@ impl HttpResponse { #[inline] pub fn build(status: StatusCode) -> HttpResponseBuilder { HttpResponseBuilder { - parts: Some(Parts::new(status)), + response: Some(HttpResponse::new(status, Body::Empty)), err: None, + cookies: None, } } @@ -57,7 +58,7 @@ impl HttpResponse { pub fn new(status: StatusCode, body: Body) -> HttpResponse { HttpResponse { version: None, - headers: Default::default(), + headers: HeaderMap::with_capacity(8), status: status, reason: None, body: body, @@ -213,49 +214,22 @@ impl fmt::Debug for HttpResponse { } } -#[derive(Debug)] -struct Parts { - version: Option, - headers: HeaderMap, - status: StatusCode, - reason: Option<&'static str>, - chunked: bool, - encoding: ContentEncoding, - connection_type: Option, - cookies: Option, -} - -impl Parts { - fn new(status: StatusCode) -> Self { - Parts { - version: None, - headers: HeaderMap::with_capacity(8), - status: status, - reason: None, - chunked: false, - encoding: ContentEncoding::Auto, - connection_type: None, - cookies: None, - } - } -} - - /// An HTTP response builder /// /// This type can be used to construct an instance of `HttpResponse` through a /// builder-like pattern. #[derive(Debug)] pub struct HttpResponseBuilder { - parts: Option, + response: Option, err: Option, + cookies: Option, } impl HttpResponseBuilder { /// Set the HTTP version of this response. #[inline] pub fn version(&mut self, version: Version) -> &mut Self { - if let Some(parts) = parts(&mut self.parts, &self.err) { + if let Some(parts) = parts(&mut self.response, &self.err) { parts.version = Some(version); } self @@ -264,7 +238,7 @@ impl HttpResponseBuilder { /// Set the `StatusCode` for this response. #[inline] pub fn status(&mut self, status: StatusCode) -> &mut Self { - if let Some(parts) = parts(&mut self.parts, &self.err) { + if let Some(parts) = parts(&mut self.response, &self.err) { parts.status = status; } self @@ -276,7 +250,7 @@ impl HttpResponseBuilder { where HeaderName: HttpTryFrom, HeaderValue: HttpTryFrom { - if let Some(parts) = parts(&mut self.parts, &self.err) { + if let Some(parts) = parts(&mut self.response, &self.err) { match HeaderName::try_from(key) { Ok(key) => { match HeaderValue::try_from(value) { @@ -293,7 +267,7 @@ impl HttpResponseBuilder { /// Set the custom reason for the response. #[inline] pub fn reason(&mut self, reason: &'static str) -> &mut Self { - if let Some(parts) = parts(&mut self.parts, &self.err) { + if let Some(parts) = parts(&mut self.response, &self.err) { parts.reason = Some(reason); } self @@ -306,7 +280,7 @@ impl HttpResponseBuilder { /// To enforce specific encoding, use specific ContentEncoding` value. #[inline] pub fn content_encoding(&mut self, enc: ContentEncoding) -> &mut Self { - if let Some(parts) = parts(&mut self.parts, &self.err) { + if let Some(parts) = parts(&mut self.response, &self.err) { parts.encoding = enc; } self @@ -315,7 +289,7 @@ impl HttpResponseBuilder { /// Set connection type #[inline] pub fn connection_type(&mut self, conn: ConnectionType) -> &mut Self { - if let Some(parts) = parts(&mut self.parts, &self.err) { + if let Some(parts) = parts(&mut self.response, &self.err) { parts.connection_type = Some(conn); } self @@ -336,7 +310,7 @@ impl HttpResponseBuilder { /// Enables automatic chunked transfer encoding #[inline] pub fn enable_chunked(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.parts, &self.err) { + if let Some(parts) = parts(&mut self.response, &self.err) { parts.chunked = true; } self @@ -347,7 +321,7 @@ impl HttpResponseBuilder { pub fn content_type(&mut self, value: V) -> &mut Self where HeaderValue: HttpTryFrom { - if let Some(parts) = parts(&mut self.parts, &self.err) { + if let Some(parts) = parts(&mut self.response, &self.err) { match HeaderValue::try_from(value) { Ok(value) => { parts.headers.insert(header::CONTENT_TYPE, value); }, Err(e) => self.err = Some(e.into()), @@ -358,25 +332,23 @@ impl HttpResponseBuilder { /// Set a cookie pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { - if let Some(parts) = parts(&mut self.parts, &self.err) { - if parts.cookies.is_none() { - let mut jar = CookieJar::new(); - jar.add(cookie.into_owned()); - parts.cookies = Some(jar) - } else { - parts.cookies.as_mut().unwrap().add(cookie.into_owned()); - } + if self.cookies.is_none() { + let mut jar = CookieJar::new(); + jar.add(cookie.into_owned()); + self.cookies = Some(jar) + } else { + self.cookies.as_mut().unwrap().add(cookie.into_owned()); } self } /// Remote cookie, cookie has to be cookie from `HttpRequest::cookies()` method. pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self { - if let Some(parts) = parts(&mut self.parts, &self.err) { - if parts.cookies.is_none() { - parts.cookies = Some(CookieJar::new()) + { + if self.cookies.is_none() { + self.cookies = Some(CookieJar::new()) } - let mut jar = parts.cookies.as_mut().unwrap(); + let jar = self.cookies.as_mut().unwrap(); let cookie = cookie.clone().into_owned(); jar.add_original(cookie.clone()); jar.remove(cookie); @@ -397,36 +369,26 @@ impl HttpResponseBuilder { /// Set a body and generate `HttpResponse`. /// `HttpResponseBuilder` can not be used after this call. pub fn body>(&mut self, body: B) -> Result { - let mut parts = self.parts.take().expect("cannot reuse response builder"); if let Some(e) = self.err.take() { return Err(e) } - if let Some(jar) = parts.cookies { + let mut response = self.response.take().expect("cannot reuse response builder"); + if let Some(ref jar) = self.cookies { for cookie in jar.delta() { - parts.headers.append( + response.headers.append( header::SET_COOKIE, HeaderValue::from_str(&cookie.to_string())?); } } - Ok(HttpResponse { - version: parts.version, - headers: parts.headers, - status: parts.status, - reason: parts.reason, - body: body.into(), - chunked: parts.chunked, - encoding: parts.encoding, - connection_type: parts.connection_type, - response_size: 0, - error: None, - }) + response.body = body.into(); + Ok(response) } /// Set a json body and generate `HttpResponse` pub fn json(&mut self, value: T) -> Result { let body = serde_json::to_string(&value)?; - let contains = if let Some(parts) = parts(&mut self.parts, &self.err) { + let contains = if let Some(parts) = parts(&mut self.response, &self.err) { parts.headers.contains_key(header::CONTENT_TYPE) } else { true @@ -444,7 +406,8 @@ impl HttpResponseBuilder { } } -fn parts<'a>(parts: &'a mut Option, err: &Option) -> Option<&'a mut Parts> +fn parts<'a>(parts: &'a mut Option, err: &Option) + -> Option<&'a mut HttpResponse> { if err.is_some() { return None diff --git a/src/lib.rs b/src/lib.rs index 3fb4c8a59..8f05bc2ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,7 +55,7 @@ extern crate tokio_openssl; mod application; mod body; mod context; -mod date; +mod utils; mod encoding; mod httprequest; mod httpresponse; diff --git a/src/payload.rs b/src/payload.rs index a77bdcba0..3aff42162 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -250,7 +250,7 @@ impl Inner { let mut chunk = self.items.pop_front().unwrap(); let rem = cmp::min(size - buf.len(), chunk.len()); self.len -= rem; - buf.extend(&chunk.split_to(rem)); + buf.extend_from_slice(&chunk.split_to(rem)); if !chunk.is_empty() { self.items.push_front(chunk); return Ok(Async::Ready(buf.freeze())) @@ -299,12 +299,12 @@ impl Inner { let mut buf = BytesMut::with_capacity(length); if num > 0 { for _ in 0..num { - buf.extend(self.items.pop_front().unwrap()); + buf.extend_from_slice(&self.items.pop_front().unwrap()); } } if offset > 0 { let mut chunk = self.items.pop_front().unwrap(); - buf.extend(chunk.split_to(offset)); + buf.extend_from_slice(&chunk.split_to(offset)); if !chunk.is_empty() { self.items.push_front(chunk) } @@ -330,7 +330,7 @@ impl Inner { if len > 0 { let mut buf = BytesMut::with_capacity(len); for item in &self.items { - buf.extend(item); + buf.extend_from_slice(item); } self.items = VecDeque::new(); self.len = 0; diff --git a/src/server.rs b/src/server.rs index 433ee5af2..4e9c00e55 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,7 +1,7 @@ use std::{io, net, thread}; use std::rc::Rc; use std::sync::Arc; -//use std::time::Duration; +use std::time::Duration; use std::marker::PhantomData; use actix::dev::*; @@ -28,6 +28,7 @@ use openssl::pkcs12::ParsedPkcs12; #[cfg(feature="alpn")] use tokio_openssl::{SslStream, SslAcceptorExt}; +use utils; use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; /// Various server settings @@ -99,10 +100,23 @@ pub struct HttpServer impl Actor for HttpServer { type Context = Context; + + fn started(&mut self, ctx: &mut Self::Context) { + self.update_time(ctx); + } +} + +impl HttpServer { + fn update_time(&self, ctx: &mut Context) { + utils::update_date(); + ctx.run_later(Duration::new(1, 0), |slf, ctx| slf.update_time(ctx)); + } } impl HttpServer - where H: HttpHandler, + where A: 'static, + T: AsyncRead + AsyncWrite + 'static, + H: HttpHandler, U: IntoIterator + 'static, V: IntoHttpHandler, { @@ -126,15 +140,7 @@ impl HttpServer self.threads = num; self } -} -impl HttpServer - where T: AsyncRead + AsyncWrite + 'static, - A: 'static, - H: HttpHandler, - U: IntoIterator + 'static, - V: IntoHttpHandler, -{ /// Start listening for incomming connections from a stream. /// /// This method uses only one thread for handling incoming connections. @@ -387,11 +393,18 @@ struct Worker { handler: StreamHandlerType, } +impl Worker { + fn update_time(&self, ctx: &mut Context) { + utils::update_date(); + ctx.run_later(Duration::new(1, 0), |slf, ctx| slf.update_time(ctx)); + } +} + impl Actor for Worker { type Context = Context; fn started(&mut self, ctx: &mut Self::Context) { - + self.update_time(ctx); } } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 000000000..878391f9e --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,173 @@ +use std::{str, mem, ptr, slice}; +use std::cell::RefCell; +use std::fmt::{self, Write}; +use time; +use bytes::BytesMut; +use http::header::HeaderValue; + +// "Sun, 06 Nov 1994 08:49:37 GMT".len() +pub const DATE_VALUE_LENGTH: usize = 29; + +pub fn extend(dst: &mut BytesMut) { + CACHED.with(|cache| { + dst.extend_from_slice(cache.borrow().buffer()); + }) +} + +pub fn update_date() { + CACHED.with(|cache| { + cache.borrow_mut().update(); + }); +} + +struct CachedDate { + bytes: [u8; DATE_VALUE_LENGTH], + pos: usize, +} + +thread_local!(static CACHED: RefCell = RefCell::new(CachedDate { + bytes: [0; DATE_VALUE_LENGTH], + pos: 0, +})); + +impl CachedDate { + fn buffer(&self) -> &[u8] { + &self.bytes[..] + } + + fn update(&mut self) { + self.pos = 0; + write!(self, "{}", time::at_utc(time::get_time()).rfc822()).unwrap(); + assert_eq!(self.pos, DATE_VALUE_LENGTH); + } +} + +impl fmt::Write for CachedDate { + fn write_str(&mut self, s: &str) -> fmt::Result { + let len = s.len(); + self.bytes[self.pos..self.pos + len].copy_from_slice(s.as_bytes()); + self.pos += len; + Ok(()) + } +} + +const DEC_DIGITS_LUT: &[u8] = + b"0001020304050607080910111213141516171819\ + 2021222324252627282930313233343536373839\ + 4041424344454647484950515253545556575859\ + 6061626364656667686970717273747576777879\ + 8081828384858687888990919293949596979899"; + +pub(crate) fn convert_u16(mut n: u16, bytes: &mut BytesMut) { + let mut buf: [u8; 39] = unsafe { mem::uninitialized() }; + let mut curr = buf.len() as isize; + let buf_ptr = buf.as_mut_ptr(); + let lut_ptr = DEC_DIGITS_LUT.as_ptr(); + + unsafe { + // need at least 16 bits for the 4-characters-at-a-time to work. + if mem::size_of::() >= 2 { + // eagerly decode 4 characters at a time + while n >= 10_000 { + let rem = (n % 10_000) as isize; + n /= 10_000; + + let d1 = (rem / 100) << 1; + let d2 = (rem % 100) << 1; + curr -= 4; + ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); + ptr::copy_nonoverlapping(lut_ptr.offset(d2), buf_ptr.offset(curr + 2), 2); + } + } + + // if we reach here numbers are <= 9999, so at most 4 chars long + let mut n = n as isize; // possibly reduce 64bit math + + // decode 2 more chars, if > 2 chars + if n >= 100 { + let d1 = (n % 100) << 1; + n /= 100; + curr -= 2; + ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); + } + + // decode last 1 or 2 chars + if n < 10 { + curr -= 1; + *buf_ptr.offset(curr) = (n as u8) + b'0'; + } else { + let d1 = n << 1; + curr -= 2; + ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); + } + } + + unsafe { + bytes.extend_from_slice( + slice::from_raw_parts(buf_ptr.offset(curr), buf.len() - curr as usize)); + } +} + +pub(crate) fn convert_into_header(mut n: usize) -> HeaderValue { + let mut curr: isize = 39; + let mut buf: [u8; 39] = unsafe { mem::uninitialized() }; + let buf_ptr = buf.as_mut_ptr(); + let lut_ptr = DEC_DIGITS_LUT.as_ptr(); + + unsafe { + // need at least 16 bits for the 4-characters-at-a-time to work. + if mem::size_of::() >= 2 { + // eagerly decode 4 characters at a time + while n >= 10_000 { + let rem = (n % 10_000) as isize; + n /= 10_000; + + let d1 = (rem / 100) << 1; + let d2 = (rem % 100) << 1; + curr -= 4; + ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); + ptr::copy_nonoverlapping(lut_ptr.offset(d2), buf_ptr.offset(curr + 2), 2); + } + } + + // if we reach here numbers are <= 9999, so at most 4 chars long + let mut n = n as isize; // possibly reduce 64bit math + + // decode 2 more chars, if > 2 chars + if n >= 100 { + let d1 = (n % 100) << 1; + n /= 100; + curr -= 2; + ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); + } + + // decode last 1 or 2 chars + if n < 10 { + curr -= 1; + *buf_ptr.offset(curr) = (n as u8) + b'0'; + } else { + let d1 = n << 1; + curr -= 2; + ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); + } + } + + unsafe { + HeaderValue::from_bytes( + slice::from_raw_parts(buf_ptr.offset(curr), buf.len() - curr as usize)).unwrap() + } +} + +#[test] +fn test_date_len() { + assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len()); +} + +#[test] +fn test_date() { + let mut buf1 = BytesMut::new(); + extend(&mut buf1); + let mut buf2 = BytesMut::new(); + extend(&mut buf2); + assert_eq!(buf1, buf2); +} diff --git a/src/ws.rs b/src/ws.rs index 65ddb5afe..d30e525ae 100644 --- a/src/ws.rs +++ b/src/ws.rs @@ -201,7 +201,7 @@ impl Stream for WsStream { loop { match self.rx.readany() { Ok(Async::Ready(Some(chunk))) => { - self.buf.extend(chunk.0) + self.buf.extend_from_slice(&chunk.0) } Ok(Async::Ready(None)) => { done = true; From 653b4318955b8397635d2695b4316ed8898584bf Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Dec 2017 17:28:16 -0800 Subject: [PATCH 085/279] fix example --- examples/websocket-chat/src/main.rs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs index 7547b505d..b553b5f24 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -192,23 +192,27 @@ fn main() { Ok(()) })); - // Websocket sessions state - let state = WsChatSessionState { addr: server }; // Create Http server with websocket support HttpServer::new( - Application::with_state("/", state) + move || { + // Websocket sessions state + let state = WsChatSessionState { addr: server.clone() }; + + Application::with_state(state) // redirect to websocket.html - .resource("/", |r| r.method(Method::GET).f(|req| { - httpcodes::HTTPFound - .build() - .header("LOCATION", "/static/websocket.html") - .body(Body::Empty) - })) + .resource("/", |r| r.method(Method::GET).f(|req| { + httpcodes::HTTPFound + .build() + .header("LOCATION", "/static/websocket.html") + .body(Body::Empty) + })) // websocket - .resource("/ws/", |r| r.route().f(chat_route)) + .resource("/ws/", |r| r.route().f(chat_route)) // static resources - .resource("/static", |r| r.h(fs::StaticFiles::new("static/", true)))) + .resource("/static/{tail:.*}", + |r| r.h(fs::StaticFiles::new("tail", "static/", true))) + }) .serve::<_, ()>("127.0.0.1:8080").unwrap(); let _ = sys.run(); From c2751efa871119b1fe6318c66bfe563fd3930bb5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Dec 2017 21:38:47 -0800 Subject: [PATCH 086/279] refactor keep-alive; update guide --- build.rs | 2 +- guide/src/SUMMARY.md | 2 +- guide/src/qs_3.md | 43 +++++++++++++++++++++++ guide/src/qs_6.md | 37 -------------------- src/channel.rs | 5 +-- src/h1.rs | 41 ++++++++++++---------- src/h2.rs | 43 ++++++++++++++--------- src/lib.rs | 1 + src/server.rs | 82 ++++++++++++++++++++++++++++++++++---------- 9 files changed, 161 insertions(+), 95 deletions(-) delete mode 100644 guide/src/qs_6.md diff --git a/build.rs b/build.rs index 3b916a95f..081d2b509 100644 --- a/build.rs +++ b/build.rs @@ -15,10 +15,10 @@ fn main() { "guide/src/qs_1.md", "guide/src/qs_2.md", "guide/src/qs_3.md", + "guide/src/qs_3_5.md", "guide/src/qs_4.md", "guide/src/qs_4_5.md", "guide/src/qs_5.md", - "guide/src/qs_6.md", "guide/src/qs_7.md", "guide/src/qs_9.md", "guide/src/qs_10.md", diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index a9befac89..e260000a7 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -3,9 +3,9 @@ [Quickstart](./qs_1.md) - [Getting Started](./qs_2.md) - [Application](./qs_3.md) +- [Server](./qs_3_5.md) - [Handler](./qs_4.md) - [Errors](./qs_4_5.md) -- [State](./qs_6.md) - [URL Dispatch](./qs_5.md) - [Request & Response](./qs_7.md) - [WebSockets](./qs_9.md) diff --git a/guide/src/qs_3.md b/guide/src/qs_3.md index 51e82d493..909ba2922 100644 --- a/guide/src/qs_3.md +++ b/guide/src/qs_3.md @@ -56,3 +56,46 @@ fn main() { ``` All `/app1` requests route to first application, `/app2` to second and then all other to third. + +## State + +Application state is shared with all routes and resources within same application. +State could be accessed with `HttpRequest::state()` method as a read-only item +but interior mutability pattern with `RefCell` could be used to archive state mutability. +State could be accessed with `HttpContext::state()` in case of http actor. +State also available to route matching predicates and middlewares. + +Let's write simple application that uses shared state. We are going to store requests count +in the state: + +```rust +# extern crate actix; +# extern crate actix_web; +# +use actix_web::*; +use std::cell::Cell; + +// This struct represents state +struct AppState { + counter: Cell, +} + +fn index(req: HttpRequest) -> String { + let count = req.state().counter.get() + 1; // <- get count + req.state().counter.set(count); // <- store new count in state + + format!("Request number: {}", count) // <- response with count +} + +fn main() { + Application::with_state(AppState{counter: Cell::new(0)}) + .resource("/", |r| r.method(Method::GET).f(index)) + .finish(); +} +``` + +Note on application state, http server accepts application factory rather than application +instance. Http server construct application instance for each thread, so application state +must be constructed multiple times. If you want to share state between different thread +shared object should be used, like `Arc`. Application state does not need to be `Send` and `Sync` +but application factory must be `Send` + `Sync`. diff --git a/guide/src/qs_6.md b/guide/src/qs_6.md deleted file mode 100644 index f7c889464..000000000 --- a/guide/src/qs_6.md +++ /dev/null @@ -1,37 +0,0 @@ -# Application state - -Application state is shared with all routes and resources within same application. -State could be accessed with `HttpRequest::state()` method as a read-only item -but interior mutability pattern with `RefCell` could be used to archive state mutability. -State could be accessed with `HttpContext::state()` in case of http actor. -State also available to route matching predicates. State is not available -to application middlewares, middlewares receives `HttpRequest<()>` object. - -Let's write simple application that uses shared state. We are going to store requests count -in the state: - -```rust -# extern crate actix; -# extern crate actix_web; -# -use actix_web::*; -use std::cell::Cell; - -// This struct represents state -struct AppState { - counter: Cell, -} - -fn index(req: HttpRequest) -> String { - let count = req.state().counter.get() + 1; // <- get count - req.state().counter.set(count); // <- store new count in state - - format!("Request number: {}", count) // <- response with count -} - -fn main() { - Application::with_state(AppState{counter: Cell::new(0)}) - .resource("/", |r| r.method(Method::GET).f(index)) - .finish(); -} -``` diff --git a/src/channel.rs b/src/channel.rs index bf7e24a96..6503e82f8 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -11,7 +11,7 @@ use h2; use error::Error; use h1writer::Writer; use httprequest::HttpRequest; -use server::ServerSettings; +use server::{ServerSettings, WorkerSettings}; /// Low level http request handler #[allow(unused_variables)] @@ -67,7 +67,8 @@ pub struct HttpChannel impl HttpChannel where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static { - pub fn new(h: Rc>, io: T, peer: Option, http2: bool) -> HttpChannel + pub(crate) fn new(h: Rc>, + io: T, peer: Option, http2: bool) -> HttpChannel { if http2 { HttpChannel { diff --git a/src/h1.rs b/src/h1.rs index 3b47ca84a..0f35f131b 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -17,12 +17,12 @@ use pipeline::Pipeline; use encoding::PayloadType; use channel::{HttpHandler, HttpHandlerTask}; use h1writer::H1Writer; +use server::WorkerSettings; use httpcodes::HTTPNotFound; use httprequest::HttpRequest; use error::{ParseError, PayloadError, ResponseError}; use payload::{Payload, PayloadWriter, DEFAULT_BUFFER_SIZE}; -const KEEPALIVE_PERIOD: u64 = 15; // seconds const INIT_BUFFER_SIZE: usize = 8192; const MAX_BUFFER_SIZE: usize = 131_072; const MAX_HEADERS: usize = 100; @@ -59,7 +59,7 @@ enum Item { pub(crate) struct Http1 { flags: Flags, - handlers: Rc>, + settings: Rc>, addr: Option, stream: H1Writer, reader: Reader, @@ -77,9 +77,9 @@ impl Http1 where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static { - pub fn new(h: Rc>, stream: T, addr: Option) -> Self { + pub fn new(h: Rc>, stream: T, addr: Option) -> Self { Http1{ flags: Flags::KEEPALIVE, - handlers: h, + settings: h, addr: addr, stream: H1Writer::new(stream), reader: Reader::new(), @@ -88,8 +88,8 @@ impl Http1 keepalive_timer: None } } - pub fn into_inner(self) -> (Rc>, T, Option, Bytes) { - (self.handlers, self.stream.into_inner(), self.addr, self.read_buf.freeze()) + pub fn into_inner(self) -> (Rc>, T, Option, Bytes) { + (self.settings, self.stream.into_inner(), self.addr, self.read_buf.freeze()) } pub fn poll(&mut self) -> Poll { @@ -198,7 +198,7 @@ impl Http1 // start request processing let mut pipe = None; - for h in self.handlers.iter() { + for h in self.settings.handlers().iter() { req = match h.handle(req) { Ok(t) => { pipe = Some(t); @@ -249,19 +249,24 @@ impl Http1 Ok(Async::NotReady) => { // start keep-alive timer, this is also slow request timeout if self.tasks.is_empty() { - if self.flags.contains(Flags::KEEPALIVE) { - if self.keepalive_timer.is_none() { - trace!("Start keep-alive timer"); - let mut to = Timeout::new( - Duration::new(KEEPALIVE_PERIOD, 0), - Arbiter::handle()).unwrap(); - // register timeout - let _ = to.poll(); - self.keepalive_timer = Some(to); + if let Some(keep_alive) = self.settings.keep_alive() { + if keep_alive > 0 && self.flags.contains(Flags::KEEPALIVE) { + if self.keepalive_timer.is_none() { + trace!("Start keep-alive timer"); + let mut to = Timeout::new( + Duration::new(keep_alive as u64, 0), + Arbiter::handle()).unwrap(); + // register timeout + let _ = to.poll(); + self.keepalive_timer = Some(to); + } + } else { + // keep-alive disable, drop connection + return Ok(Async::Ready(Http1Result::Done)) } } else { - // keep-alive disable, drop connection - return Ok(Async::Ready(Http1Result::Done)) + // keep-alive unset, rely on operating system + return Ok(Async::NotReady) } } break diff --git a/src/h2.rs b/src/h2.rs index 625681623..875662777 100644 --- a/src/h2.rs +++ b/src/h2.rs @@ -16,6 +16,7 @@ use tokio_core::reactor::Timeout; use pipeline::Pipeline; use h2writer::H2Writer; +use server::WorkerSettings; use channel::{HttpHandler, HttpHandlerTask}; use error::PayloadError; use encoding::PayloadType; @@ -23,8 +24,6 @@ use httpcodes::HTTPNotFound; use httprequest::HttpRequest; use payload::{Payload, PayloadWriter}; -const KEEPALIVE_PERIOD: u64 = 15; // seconds - bitflags! { struct Flags: u8 { const DISCONNECTED = 0b0000_0010; @@ -36,7 +35,7 @@ pub(crate) struct Http2 where T: AsyncRead + AsyncWrite + 'static, H: 'static { flags: Flags, - handlers: Rc>, + settings: Rc>, addr: Option, state: State>, tasks: VecDeque, @@ -53,14 +52,14 @@ impl Http2 where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static { - pub fn new(h: Rc>, stream: T, addr: Option, buf: Bytes) -> Self + pub fn new(h: Rc>, io: T, addr: Option, buf: Bytes) -> Self { Http2{ flags: Flags::empty(), - handlers: h, + settings: h, addr: addr, tasks: VecDeque::new(), state: State::Handshake( - Server::handshake(IoWrapper{unread: Some(buf), inner: stream})), + Server::handshake(IoWrapper{unread: Some(buf), inner: io})), keepalive_timer: None, } } @@ -151,18 +150,28 @@ impl Http2 self.keepalive_timer.take(); self.tasks.push_back( - Entry::new(parts, body, resp, self.addr, &self.handlers)); + Entry::new(parts, body, resp, self.addr, &self.settings)); } Ok(Async::NotReady) => { // start keep-alive timer - if self.tasks.is_empty() && self.keepalive_timer.is_none() { - trace!("Start keep-alive timer"); - let mut timeout = Timeout::new( - Duration::new(KEEPALIVE_PERIOD, 0), - Arbiter::handle()).unwrap(); - // register timeout - let _ = timeout.poll(); - self.keepalive_timer = Some(timeout); + if self.tasks.is_empty() { + if let Some(keep_alive) = self.settings.keep_alive() { + if keep_alive > 0 && self.keepalive_timer.is_none() { + trace!("Start keep-alive timer"); + let mut timeout = Timeout::new( + Duration::new(keep_alive as u64, 0), + Arbiter::handle()).unwrap(); + // register timeout + let _ = timeout.poll(); + self.keepalive_timer = Some(timeout); + } + } else { + // keep-alive disable, drop connection + return Ok(Async::Ready(())) + } + } else { + // keep-alive unset, rely on operating system + return Ok(Async::NotReady) } } Err(err) => { @@ -230,7 +239,7 @@ impl Entry { recv: RecvStream, resp: Respond, addr: Option, - handlers: &Rc>) -> Entry + settings: &Rc>) -> Entry where H: HttpHandler + 'static { // Payload and Content-Encoding @@ -247,7 +256,7 @@ impl Entry { // start request processing let mut task = None; - for h in handlers.iter() { + for h in settings.handlers().iter() { req = match h.handle(req) { Ok(t) => { task = Some(t); diff --git a/src/lib.rs b/src/lib.rs index 8f05bc2ca..7c05015e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,6 +110,7 @@ pub mod headers { //! Headers implementation pub use encoding::ContentEncoding; + pub use httpresponse::ConnectionType; pub use cookie::Cookie; pub use cookie::CookieBuilder; diff --git a/src/server.rs b/src/server.rs index 4e9c00e55..ee7ad90e4 100644 --- a/src/server.rs +++ b/src/server.rs @@ -90,10 +90,11 @@ impl ServerSettings { pub struct HttpServer where H: 'static { - h: Rc>, + h: Option>>, io: PhantomData, addr: PhantomData, threads: usize, + keep_alive: Option, factory: Arc U + Send + Sync>, workers: Vec>>, } @@ -124,10 +125,11 @@ impl HttpServer pub fn new(factory: F) -> Self where F: Sync + Send + 'static + Fn() -> U, { - HttpServer{ h: Rc::new(Vec::new()), + HttpServer{ h: None, io: PhantomData, addr: PhantomData, threads: num_cpus::get(), + keep_alive: None, factory: Arc::new(factory), workers: Vec::new(), } @@ -141,6 +143,20 @@ impl HttpServer self } + /// Set server keep-alive setting. + /// + /// By default keep alive is enabled. + /// + /// - `Some(75)` - enable + /// + /// - `Some(0)` - disable + /// + /// - `None` - use `SO_KEEPALIVE` socket option + pub fn keep_alive(mut self, val: Option) -> Self { + self.keep_alive = val; + self + } + /// Start listening for incomming connections from a stream. /// /// This method uses only one thread for handling incoming connections. @@ -155,7 +171,7 @@ impl HttpServer for app in &mut apps { app.server_settings(settings.clone()); } - self.h = Rc::new(apps); + self.h = Some(Rc::new(WorkerSettings{h: apps, keep_alive: self.keep_alive})); // start server Ok(HttpServer::create(move |ctx| { @@ -215,15 +231,16 @@ impl HttpServer } fn start_workers(&mut self, settings: &ServerSettings, handler: &StreamHandlerType) - -> Vec>> + -> Vec>> { // start workers let mut workers = Vec::new(); for _ in 0..self.threads { let s = settings.clone(); - let (tx, rx) = mpsc::unbounded::>(); + let (tx, rx) = mpsc::unbounded::>(); let h = handler.clone(); + let ka = self.keep_alive.clone(); let factory = Arc::clone(&self.factory); let addr = Arbiter::start(move |ctx: &mut Context<_>| { let mut apps: Vec<_> = (*factory)() @@ -232,7 +249,7 @@ impl HttpServer app.server_settings(s.clone()); } ctx.add_stream(rx); - Worker{h: Rc::new(apps), handler: h} + Worker::new(apps, h, ka) }); workers.push(tx); self.workers.push(addr); @@ -379,7 +396,7 @@ impl Handler, io::Error> for HttpServer -> Response> { Arbiter::handle().spawn( - HttpChannel::new(Rc::clone(&self.h), msg.io, msg.peer, msg.http2)); + HttpChannel::new(Rc::clone(&self.h.as_ref().unwrap()), msg.io, msg.peer, msg.http2)); Self::empty() } } @@ -389,11 +406,33 @@ impl Handler, io::Error> for HttpServer /// /// Worker accepts Socket objects via unbounded channel and start requests processing. struct Worker { - h: Rc>, + h: Rc>, handler: StreamHandlerType, } +pub(crate) struct WorkerSettings { + h: Vec, + keep_alive: Option, +} + +impl WorkerSettings { + pub fn handlers(&self) -> &Vec { + &self.h + } + pub fn keep_alive(&self) -> Option { + self.keep_alive + } +} + impl Worker { + + fn new(h: Vec, handler: StreamHandlerType, keep_alive: Option) -> Worker { + Worker { + h: Rc::new(WorkerSettings{h: h, keep_alive: keep_alive}), + handler: handler, + } + } + fn update_time(&self, ctx: &mut Context) { utils::update_date(); ctx.run_later(Duration::new(1, 0), |slf, ctx| slf.update_time(ctx)); @@ -408,15 +447,20 @@ impl Actor for Worker { } } -impl StreamHandler> for Worker +impl StreamHandler> for Worker where H: HttpHandler + 'static {} -impl Handler> for Worker +impl Handler> for Worker where H: HttpHandler + 'static, { - fn handle(&mut self, msg: IoStream, _: &mut Context) - -> Response> + fn handle(&mut self, msg: IoStream, _: &mut Context) + -> Response> { + if let None = self.h.keep_alive { + if msg.io.set_keepalive(Some(Duration::new(75, 0))).is_err() { + error!("Can not set socket keep-alive option"); + } + } self.handler.handle(Rc::clone(&self.h), msg); Self::empty() } @@ -432,10 +476,11 @@ enum StreamHandlerType { } impl StreamHandlerType { - fn handle(&mut self, h: Rc>, msg: IoStream) { + + fn handle(&mut self, h: Rc>, msg: IoStream) { match *self { StreamHandlerType::Normal => { - let io = TcpStream::from_stream(msg.io, Arbiter::handle()) + let io = TcpStream::from_stream(msg.io.into_tcp_stream(), Arbiter::handle()) .expect("failed to associate TCP stream"); Arbiter::handle().spawn(HttpChannel::new(h, io, msg.peer, msg.http2)); @@ -443,7 +488,7 @@ impl StreamHandlerType { #[cfg(feature="tls")] StreamHandlerType::Tls(ref acceptor) => { let IoStream { io, peer, http2 } = msg; - let io = TcpStream::from_stream(io, Arbiter::handle()) + let io = TcpStream::from_stream(io.into_tcp_stream(), Arbiter::handle()) .expect("failed to associate TCP stream"); Arbiter::handle().spawn( @@ -461,7 +506,7 @@ impl StreamHandlerType { #[cfg(feature="alpn")] StreamHandlerType::Alpn(ref acceptor) => { let IoStream { io, peer, .. } = msg; - let io = TcpStream::from_stream(io, Arbiter::handle()) + let io = TcpStream::from_stream(io.into_tcp_stream(), Arbiter::handle()) .expect("failed to associate TCP stream"); Arbiter::handle().spawn( @@ -488,7 +533,7 @@ impl StreamHandlerType { } fn start_accept_thread(sock: Socket, addr: net::SocketAddr, - workers: Vec>>) { + workers: Vec>>) { // start acceptors thread let _ = thread::Builder::new().name(format!("Accept on {}", addr)).spawn(move || { let mut next = 0; @@ -500,8 +545,7 @@ fn start_accept_thread(sock: Socket, addr: net::SocketAddr, } else { net::SocketAddr::V6(addr.as_inet6().unwrap()) }; - let msg = IoStream{ - io: socket.into_tcp_stream(), peer: Some(addr), http2: false}; + let msg = IoStream{io: socket, peer: Some(addr), http2: false}; workers[next].unbounded_send(msg).expect("worker thread died"); next = (next + 1) % workers.len(); } From 406ef202626b34388a91ac0fb30efc6be33a40dc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Dec 2017 21:44:16 -0800 Subject: [PATCH 087/279] add readme --- README.md | 4 +-- guide/src/qs_3_5.md | 71 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 guide/src/qs_3_5.md diff --git a/README.md b/README.md index b06b8af0c..db7d57d2c 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ fn index(req: HttpRequest) -> String { fn main() { HttpServer::new( - Application::new() + || Application::new() .resource("/{name}", |r| r.f(index))) .serve::<_, ()>("127.0.0.1:8080"); } @@ -34,7 +34,7 @@ fn main() { * Transparent content compression/decompression (br, gzip, deflate) * Configurable request routing * Multipart streams - * Middlewares (Logger, Session included) + * Middlewares (Logger, Session, DefaultHeaders) * Built on top of [Actix](https://github.com/actix/actix). ## HTTP/2 diff --git a/guide/src/qs_3_5.md b/guide/src/qs_3_5.md new file mode 100644 index 000000000..2a7750b32 --- /dev/null +++ b/guide/src/qs_3_5.md @@ -0,0 +1,71 @@ +# Server + +## Multi-threading + +Http server automatically starts number of http workers, by default +this number is equal to number of logical cpu in the system. This number +could be overriden with `HttpServer::threads()` method. + +```rust +# extern crate actix_web; +# extern crate tokio_core; +# use tokio_core::net::TcpStream; +# use std::net::SocketAddr; +use actix_web::*; + +fn main() { + HttpServer::::new( + || Application::new() + .resource("/", |r| r.f(|r| httpcodes::HTTPOk))) + .threads(4); // <- Start 4 threads +} +``` + +Server create separate application instance for each created worker. Application state +is not shared between threads, to share state `Arc` could be used. Application state +does not need to be `Send` and `Sync` but application factory must be `Send` + `Sync`. + +## Keep-Alive + +Actix can wait for requesta on a keep-alive connection. *Keep alive* +connection behavior is defined by server settings. + + * `Some(75)` - enable 75 sec *keep alive* timer according request and response settings. + * `Some(0)` - disable *keep alive*. + * `None` - Use `SO_KEEPALIVE` socket option. + +```rust +# extern crate actix_web; +# extern crate tokio_core; +# use tokio_core::net::TcpStream; +# use std::net::SocketAddr; +use actix_web::*; + +fn main() { + HttpServer::::new(|| + Application::new() + .resource("/", |r| r.f(|r| httpcodes::HTTPOk))) + .keep_alive(None); // <- Use `SO_KEEPALIVE` socket option. +} +``` + +If first option is selected then *keep alive* state +calculated based on response's *connection-type*. By default +`HttpResponse::connection_type` is not defined in that case *keep alive* +defined by request's http version. Keep alive is off for *HTTP/1.0* +and is on for *HTTP/1.1* and "HTTP/2.0". + +*Connection type* could be change with `HttpResponseBuilder::connection_type()` method. + +```rust +# extern crate actix_web; +# use actix_web::httpcodes::*; +use actix_web::*; + +fn index(req: HttpRequest) -> HttpResponse { + HTTPOk.build() + .connection_type(headers::ConnectionType::Close) // <- Close connection + .finish().unwrap() +} +# fn main() {} +``` From 408ddf0be1d10f5d0550b9217bf6ae9dccb3c90a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Dec 2017 21:56:30 -0800 Subject: [PATCH 088/279] add ssl guide ref --- examples/tls/src/main.rs | 2 +- guide/src/qs_13.md | 6 +++--- guide/src/qs_3_5.md | 34 ++++++++++++++++++++++++++++++++++ src/server.rs | 2 +- 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/examples/tls/src/main.rs b/examples/tls/src/main.rs index 78720c0c9..2e0d55e3f 100644 --- a/examples/tls/src/main.rs +++ b/examples/tls/src/main.rs @@ -42,7 +42,7 @@ fn main() { .header("LOCATION", "/index.html") .body(Body::Empty) }))) - .serve_tls::<_, ()>("127.0.0.1:8443", &pkcs12).unwrap(); + .serve_ssl::<_, ()>("127.0.0.1:8443", &pkcs12).unwrap(); println!("Started http server: 127.0.0.1:8443"); let _ = sys.run(); diff --git a/guide/src/qs_13.md b/guide/src/qs_13.md index c3b0b0e72..a529fb9b2 100644 --- a/guide/src/qs_13.md +++ b/guide/src/qs_13.md @@ -26,9 +26,9 @@ fn main() { let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap().parse("12345").unwrap(); HttpServer::new( - Application::new("/") - .resource("/index.html", |r| r.f(index)) - .serve_tls::<_, ()>("127.0.0.1:8080", pkcs12).unwrap(); + || Application::new() + .resource("/index.html", |r| r.f(index))) + .serve_ssl::<_, ()>("127.0.0.1:8080", pkcs12).unwrap(); } ``` diff --git a/guide/src/qs_3_5.md b/guide/src/qs_3_5.md index 2a7750b32..da21e3ce4 100644 --- a/guide/src/qs_3_5.md +++ b/guide/src/qs_3_5.md @@ -25,6 +25,40 @@ Server create separate application instance for each created worker. Application is not shared between threads, to share state `Arc` could be used. Application state does not need to be `Send` and `Sync` but application factory must be `Send` + `Sync`. +## SSL + +There are two `tls` and `alpn` features for ssl server. `tls` feature is for `native-tls` +integration and `alpn` is for `openssl`. + +```toml +[dependencies] +actix-web = { git = "https://github.com/actix/actix-web", features=["alpn"] } +``` + +```rust,ignore +use std::fs::File; +use actix_web::*; + +fn main() { + let mut file = File::open("identity.pfx").unwrap(); + let mut pkcs12 = vec![]; + file.read_to_end(&mut pkcs12).unwrap(); + let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap().parse("12345").unwrap(); + + HttpServer::new( + || Application::new() + .resource("/index.html", |r| r.f(index))) + .serve_ssl::<_, ()>("127.0.0.1:8080", pkcs12).unwrap(); +} +``` + +Note on *HTTP/2* protocol over tls without prior knowlage, it requires +[tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only +`openssl` has `alpn ` support. + +Please check [example](https://github.com/actix/actix-web/tree/master/examples/tls) +for concrete example. + ## Keep-Alive Actix can wait for requesta on a keep-alive connection. *Keep alive* diff --git a/src/server.rs b/src/server.rs index ee7ad90e4..24c8318f0 100644 --- a/src/server.rs +++ b/src/server.rs @@ -334,7 +334,7 @@ impl HttpServer, net::SocketAddr, H, /// /// This methods converts address to list of `SocketAddr` /// then binds to all available addresses. - pub fn serve_tls(mut self, addr: S, identity: &ParsedPkcs12) -> io::Result + pub fn serve_ssl(mut self, addr: S, identity: &ParsedPkcs12) -> io::Result where Self: ActorAddress, S: net::ToSocketAddrs, { From b7cde3f4a98175e0ab30cfcbe7e4c0a0f1a1bfc9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Dec 2017 22:36:28 -0800 Subject: [PATCH 089/279] update guide --- guide/src/qs_10.md | 2 ++ guide/src/qs_13.md | 8 ++++---- guide/src/qs_2.md | 2 +- guide/src/qs_3_5.md | 4 +++- guide/src/qs_7.md | 42 ++++++++++++++++++++++++++++++++++++++++++ guide/src/qs_9.md | 3 +++ src/body.rs | 4 ++-- src/error.rs | 3 +++ src/h1.rs | 2 +- src/h2.rs | 2 +- src/httpresponse.rs | 2 +- src/lib.rs | 1 + src/payload.rs | 6 ++++++ src/server.rs | 22 +++++++++++----------- 14 files changed, 81 insertions(+), 22 deletions(-) diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index 9af3301e1..10e5c7bc6 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -85,3 +85,5 @@ fn main() { ``` ## User sessions + +[WIP] diff --git a/guide/src/qs_13.md b/guide/src/qs_13.md index a529fb9b2..ee0c21f17 100644 --- a/guide/src/qs_13.md +++ b/guide/src/qs_13.md @@ -1,10 +1,10 @@ -# HTTP/2 +# HTTP/2.0 -Actix web automatically upgrades connection to *HTTP/2* if possible. +Actix web automatically upgrades connection to *HTTP/2.0* if possible. ## Negotiation -*HTTP/2* protocol over tls without prior knowlage requires +*HTTP/2.0* protocol over tls without prior knowlage requires [tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only `rust-openssl` has support. Turn on `alpn` feature to enable `alpn` negotiation. With enable `alpn` feature `HttpServer` provides @@ -32,7 +32,7 @@ fn main() { } ``` -Upgrade to *HTTP/2* schema described in +Upgrade to *HTTP/2.0* schema described in [rfc section 3.2](https://http2.github.io/http2-spec/#rfc.section.3.2) is not supported. Starting *HTTP/2* with prior knowledge is supported for both clear text connection and tls connection. [rfc section 3.4](https://http2.github.io/http2-spec/#rfc.section.3.4) diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md index 1b28892e7..0c29f5278 100644 --- a/guide/src/qs_2.md +++ b/guide/src/qs_2.md @@ -49,7 +49,7 @@ request handler with the application's `resource` on a particular *HTTP method* # } # fn main() { let app = Application::new() - .resource("/", |r| r.method(Method::GET).f(index)) + .resource("/", |r| r.f(index)) .finish(); # } ``` diff --git a/guide/src/qs_3_5.md b/guide/src/qs_3_5.md index da21e3ce4..5bb06fdf4 100644 --- a/guide/src/qs_3_5.md +++ b/guide/src/qs_3_5.md @@ -1,5 +1,6 @@ # Server + ## Multi-threading Http server automatically starts number of http workers, by default @@ -52,7 +53,7 @@ fn main() { } ``` -Note on *HTTP/2* protocol over tls without prior knowlage, it requires +Note on *HTTP/2.0* protocol over tls without prior knowlage, it requires [tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only `openssl` has `alpn ` support. @@ -99,6 +100,7 @@ use actix_web::*; fn index(req: HttpRequest) -> HttpResponse { HTTPOk.build() .connection_type(headers::ConnectionType::Close) // <- Close connection + .force_close() // <- Alternative method .finish().unwrap() } # fn main() {} diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 3e3fd8f7c..7e8bd08a9 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -82,3 +82,45 @@ fn main() { .finish(); } ``` + +## Chunked transfer encoding + +Actix automatically decode *chunked* encoding. `HttpRequest::payload()` already contains +decoded bytes stream. If request payload compressed with one of supported +compression codecs (br, gzip, deflate) bytes stream get decompressed. + +Chunked encoding on response could be enabled with `HttpResponseBuilder::chunked()` method. +But this takes effect only for `Body::Streaming(BodyStream)` or `Body::StreamingContext` bodies. +Also if response payload compression is enabled and streaming body is used, chunked encoding +get enabled automatically. + +Enabling chunked encoding for *HTTP/2.0* responses is forbidden. + +```rust +# extern crate actix_web; +use actix_web::*; +use actix_web::headers::ContentEncoding; + +fn index(req: HttpRequest) -> HttpResponse { + HttpResponse::Ok() + .chunked() + .body(Body::Streaming(Payload::empty().stream())).unwrap() +} +# fn main() {} +``` + +## Cookies + +[WIP] + +## Multipart body + +[WIP] + +## Urlencoded body + +[WIP] + +## Streaming request + +[WIP] diff --git a/guide/src/qs_9.md b/guide/src/qs_9.md index 7627a1570..cde41c746 100644 --- a/guide/src/qs_9.md +++ b/guide/src/qs_9.md @@ -1 +1,4 @@ # WebSockets + +[WIP] + diff --git a/src/body.rs b/src/body.rs index 34c06dd35..b9e6676e7 100644 --- a/src/body.rs +++ b/src/body.rs @@ -6,8 +6,8 @@ use futures::Stream; use error::Error; -pub(crate) type BodyStream = Box>; - +/// Type represent streaming body +pub type BodyStream = Box>; /// Represents various types of http message body. pub enum Body { diff --git a/src/error.rs b/src/error.rs index 37c0d0e5c..a44863ff5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -213,6 +213,9 @@ impl From for PayloadError { } } +/// `InternalServerError` for `PayloadError` +impl ResponseError for PayloadError {} + /// Return `BadRequest` for `cookie::ParseError` impl ResponseError for cookie::ParseError { fn error_response(&self) -> HttpResponse { diff --git a/src/h1.rs b/src/h1.rs index 0f35f131b..fef40e56b 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -254,7 +254,7 @@ impl Http1 if self.keepalive_timer.is_none() { trace!("Start keep-alive timer"); let mut to = Timeout::new( - Duration::new(keep_alive as u64, 0), + Duration::new(keep_alive, 0), Arbiter::handle()).unwrap(); // register timeout let _ = to.poll(); diff --git a/src/h2.rs b/src/h2.rs index 875662777..9dd85a935 100644 --- a/src/h2.rs +++ b/src/h2.rs @@ -159,7 +159,7 @@ impl Http2 if keep_alive > 0 && self.keepalive_timer.is_none() { trace!("Start keep-alive timer"); let mut timeout = Timeout::new( - Duration::new(keep_alive as u64, 0), + Duration::new(keep_alive, 0), Arbiter::handle()).unwrap(); // register timeout let _ = timeout.poll(); diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 3d3ca4cc0..a60411d27 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -309,7 +309,7 @@ impl HttpResponseBuilder { /// Enables automatic chunked transfer encoding #[inline] - pub fn enable_chunked(&mut self) -> &mut Self { + pub fn chunked(&mut self) -> &mut Self { if let Some(parts) = parts(&mut self.response, &self.err) { parts.chunked = true; } diff --git a/src/lib.rs b/src/lib.rs index 7c05015e5..4623b7a85 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -128,6 +128,7 @@ pub mod dev { //! use actix_web::dev::*; //! ``` + pub use body::BodyStream; pub use info::ConnectionInfo; pub use handler::Handler; pub use router::{Router, Pattern}; diff --git a/src/payload.rs b/src/payload.rs index 3aff42162..6d81e2f6e 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -7,6 +7,7 @@ use bytes::{Bytes, BytesMut}; use futures::{Async, Poll, Stream}; use futures::task::{Task, current as current_task}; +use body::BodyStream; use actix::ResponseType; use error::PayloadError; @@ -121,6 +122,11 @@ impl Payload { pub fn set_buffer_size(&self, size: usize) { self.inner.borrow_mut().set_buffer_size(size) } + + /// Convert payload into BodyStream + pub fn stream(self) -> BodyStream { + Box::new(self.map(|item| item.0).map_err(|e| e.into())) + } } impl Stream for Payload { diff --git a/src/server.rs b/src/server.rs index 24c8318f0..50d47d2ec 100644 --- a/src/server.rs +++ b/src/server.rs @@ -94,7 +94,7 @@ pub struct HttpServer io: PhantomData, addr: PhantomData, threads: usize, - keep_alive: Option, + keep_alive: Option, factory: Arc U + Send + Sync>, workers: Vec>>, } @@ -152,7 +152,7 @@ impl HttpServer /// - `Some(0)` - disable /// /// - `None` - use `SO_KEEPALIVE` socket option - pub fn keep_alive(mut self, val: Option) -> Self { + pub fn keep_alive(mut self, val: Option) -> Self { self.keep_alive = val; self } @@ -240,7 +240,7 @@ impl HttpServer let (tx, rx) = mpsc::unbounded::>(); let h = handler.clone(); - let ka = self.keep_alive.clone(); + let ka = self.keep_alive; let factory = Arc::clone(&self.factory); let addr = Arbiter::start(move |ctx: &mut Context<_>| { let mut apps: Vec<_> = (*factory)() @@ -396,7 +396,7 @@ impl Handler, io::Error> for HttpServer -> Response> { Arbiter::handle().spawn( - HttpChannel::new(Rc::clone(&self.h.as_ref().unwrap()), msg.io, msg.peer, msg.http2)); + HttpChannel::new(Rc::clone(self.h.as_ref().unwrap()), msg.io, msg.peer, msg.http2)); Self::empty() } } @@ -412,21 +412,21 @@ struct Worker { pub(crate) struct WorkerSettings { h: Vec, - keep_alive: Option, + keep_alive: Option, } impl WorkerSettings { pub fn handlers(&self) -> &Vec { &self.h } - pub fn keep_alive(&self) -> Option { + pub fn keep_alive(&self) -> Option { self.keep_alive } } impl Worker { - fn new(h: Vec, handler: StreamHandlerType, keep_alive: Option) -> Worker { + fn new(h: Vec, handler: StreamHandlerType, keep_alive: Option) -> Worker { Worker { h: Rc::new(WorkerSettings{h: h, keep_alive: keep_alive}), handler: handler, @@ -456,10 +456,10 @@ impl Handler> for Worker fn handle(&mut self, msg: IoStream, _: &mut Context) -> Response> { - if let None = self.h.keep_alive { - if msg.io.set_keepalive(Some(Duration::new(75, 0))).is_err() { - error!("Can not set socket keep-alive option"); - } + if self.h.keep_alive.is_none() && + msg.io.set_keepalive(Some(Duration::new(75, 0))).is_err() + { + error!("Can not set socket keep-alive option"); } self.handler.handle(Rc::clone(&self.h), msg); Self::empty() From 9d0a64ac9888f4387f1fd489abaf7838db97feb1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Dec 2017 22:43:16 -0800 Subject: [PATCH 090/279] remove unused file --- src/recognizer.rs | 192 ---------------------------------------------- 1 file changed, 192 deletions(-) delete mode 100644 src/recognizer.rs diff --git a/src/recognizer.rs b/src/recognizer.rs deleted file mode 100644 index 79be1cb1b..000000000 --- a/src/recognizer.rs +++ /dev/null @@ -1,192 +0,0 @@ -use regex::RegexSet; - -pub struct RouteRecognizer { - re: RegexSet, - routes: Vec, -} - -impl RouteRecognizer { - - pub fn new(routes: U) -> Self - where U: IntoIterator, K: Into, - { - let mut paths = Vec::new(); - let mut routes = Vec::new(); - for item in routes { - let pattern = parse(&item.0.into()); - paths.push(pattern); - routes.push(item.1); - }; - let regset = RegexSet::new(&paths); - - RouteRecognizer { - re: regset.unwrap(), - routes: routes, - } - } - - pub fn recognize(&self, path: &str) -> Option<&T> { - if path.is_empty() { - if let Some(idx) = self.re.matches("/").into_iter().next() { - return Some(&self.routes[idx]) - } - } else if let Some(idx) = self.re.matches(path).into_iter().next() { - return Some(&self.routes[idx]) - } - None - } -} - -fn parse(pattern: &str) -> String { - const DEFAULT_PATTERN: &str = "[^/]+"; - - let mut re = String::from("^/"); - let mut in_param = false; - let mut in_param_pattern = false; - let mut param_name = String::new(); - let mut param_pattern = String::from(DEFAULT_PATTERN); - - for (index, ch) in pattern.chars().enumerate() { - // All routes must have a leading slash so its optional to have one - if index == 0 && ch == '/' { - continue; - } - - if in_param { - // In parameter segment: `{....}` - if ch == '}' { - re.push_str(&format!(r"(?P<{}>{})", ¶m_name, ¶m_pattern)); - - param_name.clear(); - param_pattern = String::from(DEFAULT_PATTERN); - - in_param_pattern = false; - in_param = false; - } else if ch == ':' { - // The parameter name has been determined; custom pattern land - in_param_pattern = true; - param_pattern.clear(); - } else if in_param_pattern { - // Ignore leading whitespace for pattern - if !(ch == ' ' && param_pattern.is_empty()) { - param_pattern.push(ch); - } - } else { - param_name.push(ch); - } - } else if ch == '{' { - in_param = true; - } else { - re.push(ch); - } - } - - re.push('$'); - re -} - -#[cfg(test)] -mod tests { - use regex::Regex; - use super::*; - use std::iter::FromIterator; - - #[test] - fn test_recognizer() { - let routes = vec![ - ("/name", None, 1), - ("/name/{val}", None, 2), - ("/name/{val}/index.html", None, 3), - ("/v{val}/{val2}/index.html", None, 4), - ("/v/{tail:.*}", None, 5), - ]; - let rec = RouteRecognizer::new("", routes); - - let (params, val) = rec.recognize("/name").unwrap(); - assert_eq!(*val, 1); - assert!(params.unwrap().is_empty()); - - let (params, val) = rec.recognize("/name/value").unwrap(); - assert_eq!(*val, 2); - assert!(!params.as_ref().unwrap().is_empty()); - assert_eq!(params.as_ref().unwrap().get("val").unwrap(), "value"); - assert_eq!(¶ms.as_ref().unwrap()["val"], "value"); - - let (params, val) = rec.recognize("/name/value2/index.html").unwrap(); - assert_eq!(*val, 3); - assert!(!params.as_ref().unwrap().is_empty()); - assert_eq!(params.as_ref().unwrap().get("val").unwrap(), "value2"); - assert_eq!(params.as_ref().unwrap().by_idx(0).unwrap(), "value2"); - - let (params, val) = rec.recognize("/vtest/ttt/index.html").unwrap(); - assert_eq!(*val, 4); - assert!(!params.as_ref().unwrap().is_empty()); - assert_eq!(params.as_ref().unwrap().get("val").unwrap(), "test"); - assert_eq!(params.as_ref().unwrap().get("val2").unwrap(), "ttt"); - assert_eq!(params.as_ref().unwrap().by_idx(0).unwrap(), "test"); - assert_eq!(params.as_ref().unwrap().by_idx(1).unwrap(), "ttt"); - - let (params, val) = rec.recognize("/v/blah-blah/index.html").unwrap(); - assert_eq!(*val, 5); - assert!(!params.as_ref().unwrap().is_empty()); - assert_eq!(params.as_ref().unwrap().get("tail").unwrap(), "blah-blah/index.html"); - } - - fn assert_parse(pattern: &str, expected_re: &str) -> Regex { - let (re_str, _) = parse(pattern); - assert_eq!(&*re_str, expected_re); - Regex::new(&re_str).unwrap() - } - - #[test] - fn test_parse_static() { - let re = assert_parse("/", r"^/$"); - assert!(re.is_match("/")); - assert!(!re.is_match("/a")); - - let re = assert_parse("/name", r"^/name$"); - assert!(re.is_match("/name")); - assert!(!re.is_match("/name1")); - assert!(!re.is_match("/name/")); - assert!(!re.is_match("/name~")); - - let re = assert_parse("/name/", r"^/name/$"); - assert!(re.is_match("/name/")); - assert!(!re.is_match("/name")); - assert!(!re.is_match("/name/gs")); - - let re = assert_parse("/user/profile", r"^/user/profile$"); - assert!(re.is_match("/user/profile")); - assert!(!re.is_match("/user/profile/profile")); - } - - #[test] - fn test_parse_param() { - let re = assert_parse("/user/{id}", r"^/user/(?P[^/]+)$"); - assert!(re.is_match("/user/profile")); - assert!(re.is_match("/user/2345")); - assert!(!re.is_match("/user/2345/")); - assert!(!re.is_match("/user/2345/sdg")); - - let captures = re.captures("/user/profile").unwrap(); - assert_eq!(captures.get(1).unwrap().as_str(), "profile"); - assert_eq!(captures.name("id").unwrap().as_str(), "profile"); - - let captures = re.captures("/user/1245125").unwrap(); - assert_eq!(captures.get(1).unwrap().as_str(), "1245125"); - assert_eq!(captures.name("id").unwrap().as_str(), "1245125"); - - let re = assert_parse( - "/v{version}/resource/{id}", - r"^/v(?P[^/]+)/resource/(?P[^/]+)$", - ); - assert!(re.is_match("/v1/resource/320120")); - assert!(!re.is_match("/v/resource/1")); - assert!(!re.is_match("/resource")); - - let captures = re.captures("/v151/resource/adahg32").unwrap(); - assert_eq!(captures.get(1).unwrap().as_str(), "151"); - assert_eq!(captures.name("version").unwrap().as_str(), "151"); - assert_eq!(captures.name("id").unwrap().as_str(), "adahg32"); - } -} From 4529efa9480a4b57540f5e08bcbe53f2ff4ab8fd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Dec 2017 22:54:52 -0800 Subject: [PATCH 091/279] rename module --- src/encoding.rs | 6 +++--- src/h1writer.rs | 10 +++++----- src/h2writer.rs | 6 +++--- src/{utils.rs => helpers.rs} | 2 +- src/lib.rs | 2 +- src/server.rs | 6 +++--- 6 files changed, 16 insertions(+), 16 deletions(-) rename src/{utils.rs => helpers.rs} (99%) diff --git a/src/encoding.rs b/src/encoding.rs index c6a3df5f4..653894a96 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -13,7 +13,7 @@ use flate2::write::{GzEncoder, DeflateDecoder, DeflateEncoder}; use brotli2::write::{BrotliDecoder, BrotliEncoder}; use bytes::{Bytes, BytesMut, BufMut, Writer}; -use utils; +use helpers; use body::{Body, Binary}; use error::PayloadError; use httprequest::HttpMessage; @@ -411,13 +411,13 @@ impl PayloadEncoder { let b = enc.get_mut().take(); resp.headers_mut().insert( - CONTENT_LENGTH, utils::convert_into_header(b.len())); + CONTENT_LENGTH, helpers::convert_into_header(b.len())); *bytes = Binary::from(b); encoding = ContentEncoding::Identity; TransferEncoding::eof() } else { resp.headers_mut().insert( - CONTENT_LENGTH, utils::convert_into_header(bytes.len())); + CONTENT_LENGTH, helpers::convert_into_header(bytes.len())); TransferEncoding::eof() } } diff --git a/src/h1writer.rs b/src/h1writer.rs index 7815df2cd..956f752b3 100644 --- a/src/h1writer.rs +++ b/src/h1writer.rs @@ -4,7 +4,7 @@ use tokio_io::AsyncWrite; use http::Version; use http::header::{HeaderValue, CONNECTION, CONTENT_TYPE, DATE}; -use utils; +use helpers; use body::Body; use encoding::PayloadEncoder; use httprequest::HttpMessage; @@ -159,7 +159,7 @@ impl Writer for H1Writer { Version::HTTP_10 => buffer.extend_from_slice(b"HTTP/1.0 "), Version::HTTP_09 => buffer.extend_from_slice(b"HTTP/0.9 "), } - utils::convert_u16(msg.status().as_u16(), &mut buffer); + helpers::convert_u16(msg.status().as_u16(), &mut buffer); buffer.extend_from_slice(b" "); buffer.extend_from_slice(msg.reason().as_bytes()); buffer.extend_from_slice(b"\r\n"); @@ -172,11 +172,11 @@ impl Writer for H1Writer { buffer.extend_from_slice(b"\r\n"); } - // using utils::date is quite a lot faster + // using helpers::date is quite a lot faster if !msg.headers().contains_key(DATE) { - buffer.reserve(utils::DATE_VALUE_LENGTH + 8); + buffer.reserve(helpers::DATE_VALUE_LENGTH + 8); buffer.extend_from_slice(b"Date: "); - utils::extend(&mut buffer); + helpers::date(&mut buffer); buffer.extend_from_slice(b"\r\n"); } diff --git a/src/h2writer.rs b/src/h2writer.rs index bfc596dc9..53a07b80f 100644 --- a/src/h2writer.rs +++ b/src/h2writer.rs @@ -6,7 +6,7 @@ use http2::server::Respond; use http::{Version, HttpTryFrom, Response}; use http::header::{HeaderValue, CONNECTION, CONTENT_TYPE, TRANSFER_ENCODING, DATE}; -use utils; +use helpers; use body::Body; use encoding::PayloadEncoder; use httprequest::HttpMessage; @@ -124,10 +124,10 @@ impl Writer for H2Writer { msg.headers_mut().remove(CONNECTION); msg.headers_mut().remove(TRANSFER_ENCODING); - // using utils::date is quite a lot faster + // using helpers::date is quite a lot faster if !msg.headers().contains_key(DATE) { let mut bytes = BytesMut::with_capacity(29); - utils::extend(&mut bytes); + helpers::date(&mut bytes); msg.headers_mut().insert(DATE, HeaderValue::try_from(&bytes[..]).unwrap()); } diff --git a/src/utils.rs b/src/helpers.rs similarity index 99% rename from src/utils.rs rename to src/helpers.rs index 878391f9e..b849ba5b0 100644 --- a/src/utils.rs +++ b/src/helpers.rs @@ -8,7 +8,7 @@ use http::header::HeaderValue; // "Sun, 06 Nov 1994 08:49:37 GMT".len() pub const DATE_VALUE_LENGTH: usize = 29; -pub fn extend(dst: &mut BytesMut) { +pub fn date(dst: &mut BytesMut) { CACHED.with(|cache| { dst.extend_from_slice(cache.borrow().buffer()); }) diff --git a/src/lib.rs b/src/lib.rs index 4623b7a85..ce50edcb2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,7 +55,7 @@ extern crate tokio_openssl; mod application; mod body; mod context; -mod utils; +mod helpers; mod encoding; mod httprequest; mod httpresponse; diff --git a/src/server.rs b/src/server.rs index 50d47d2ec..95e3ed2e5 100644 --- a/src/server.rs +++ b/src/server.rs @@ -28,7 +28,7 @@ use openssl::pkcs12::ParsedPkcs12; #[cfg(feature="alpn")] use tokio_openssl::{SslStream, SslAcceptorExt}; -use utils; +use helpers; use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; /// Various server settings @@ -109,7 +109,7 @@ impl Actor for HttpServer { impl HttpServer { fn update_time(&self, ctx: &mut Context) { - utils::update_date(); + helpers::update_date(); ctx.run_later(Duration::new(1, 0), |slf, ctx| slf.update_time(ctx)); } } @@ -434,7 +434,7 @@ impl Worker { } fn update_time(&self, ctx: &mut Context) { - utils::update_date(); + helpers::update_date(); ctx.run_later(Duration::new(1, 0), |slf, ctx| slf.update_time(ctx)); } } From 8c1487f7f2b2c77eb4ef52e2a053b0ec2c16b987 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Dec 2017 23:09:20 -0800 Subject: [PATCH 092/279] update tests --- src/helpers.rs | 4 ++-- src/server.rs | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/helpers.rs b/src/helpers.rs index b849ba5b0..f49e04cda 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -166,8 +166,8 @@ fn test_date_len() { #[test] fn test_date() { let mut buf1 = BytesMut::new(); - extend(&mut buf1); + date(&mut buf1); let mut buf2 = BytesMut::new(); - extend(&mut buf2); + date(&mut buf2); assert_eq!(buf1, buf2); } diff --git a/src/server.rs b/src/server.rs index 95e3ed2e5..4fdd6bd70 100644 --- a/src/server.rs +++ b/src/server.rs @@ -82,7 +82,7 @@ impl ServerSettings { /// An HTTP Server /// -/// `T` - async stream, anything that implements `AsyncRead` + `AsyncWrite`. +/// `T` - async stream, anything that implements `AsyncRead` + `AsyncWrite`. /// /// `A` - peer address /// @@ -121,7 +121,7 @@ impl HttpServer U: IntoIterator + 'static, V: IntoHttpHandler, { - /// Create new http server with vec of http handlers + /// Create new http server with application factory pub fn new(factory: F) -> Self where F: Sync + Send + 'static + Fn() -> U, { @@ -401,7 +401,6 @@ impl Handler, io::Error> for HttpServer } } - /// Http workers /// /// Worker accepts Socket objects via unbounded channel and start requests processing. From 355f54efe282d8a36978fc05af6155649a895f0a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Dec 2017 23:35:21 -0800 Subject: [PATCH 093/279] update api docs --- README.md | 2 +- src/lib.rs | 33 +++++++++++++++++++++++++++++++++ src/server.rs | 2 +- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index db7d57d2c..7933dd19a 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ fn main() { ## Features - * Supported HTTP/1 and HTTP/2 protocols + * Supported *HTTP/1.x* and *HTTP/2.0* protocols * Streaming and pipelining * Keep-alive and slow requests handling * [WebSockets](https://actix.github.io/actix-web/actix_web/ws/index.html) diff --git a/src/lib.rs b/src/lib.rs index ce50edcb2..062e62209 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,37 @@ //! Actix web is a small, fast, down-to-earth, open source rust web framework. +//! +//! ```rust,ignore +//! use actix_web::*; +//! +//! fn index(req: HttpRequest) -> String { +//! format!("Hello {}!", &req.match_info()["name"]) +//! } +//! +//! fn main() { +//! HttpServer::new( +//! || Application::new() +//! .resource("/{name}", |r| r.f(index))) +//! .serve::<_, ()>("127.0.0.1:8080"); +//! } +//! ``` +//! +//! ## Documentation +//! +//! * [User Guide](http://actix.github.io/actix-web/guide/) +//! * Cargo package: [actix-web](https://crates.io/crates/actix-web) +//! * Minimum supported Rust version: 1.20 or later +//! +//! ## Features +//! +//! * Supported *HTTP/1.x* and *HTTP/2.0* protocols +//! * Streaming and pipelining +//! * Keep-alive and slow requests handling +//! * `WebSockets` +//! * Transparent content compression/decompression (br, gzip, deflate) +//! * Configurable request routing +//! * Multipart streams +//! * Middlewares (`Logger`, `Session`, `DefaultHeaders`) +//! * Built on top of [Actix](https://github.com/actix/actix). #![cfg_attr(actix_nightly, feature( specialization, // for impl ErrorResponse for std::error::Error diff --git a/src/server.rs b/src/server.rs index 4fdd6bd70..c85f0a729 100644 --- a/src/server.rs +++ b/src/server.rs @@ -401,7 +401,7 @@ impl Handler, io::Error> for HttpServer } } -/// Http workers +/// Http worker /// /// Worker accepts Socket objects via unbounded channel and start requests processing. struct Worker { From c98d320f8cec5a45f8de0cc8903ff8ae2b0bb786 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 14 Dec 2017 09:43:42 -0800 Subject: [PATCH 094/279] rename FromRequest trait to Responder --- guide/src/qs_4.md | 14 ++++++------- guide/src/qs_4_5.md | 4 ++-- src/fs.rs | 22 ++++++++++---------- src/handler.rs | 50 ++++++++++++++++++++++++--------------------- src/httpcodes.rs | 6 +++--- src/httpresponse.rs | 42 ++++++++++++++++++------------------- src/lib.rs | 2 +- src/resource.rs | 4 ++-- src/route.rs | 4 ++-- 9 files changed, 76 insertions(+), 72 deletions(-) diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index addb1541f..53b58c770 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -4,14 +4,14 @@ A request handler can by any object that implements [`Handler` trait](../actix_web/dev/trait.Handler.html#implementors). Request handling happen in two stages. First handler object get called. Handle can return any object that implements -[`FromRequest` trait](../actix_web/trait.FromRequest.html#foreign-impls). -Then `from_request()` get called on returned object. And finally -result of the `from_request()` call get converted to `Reply` object. +[*Responder trait*](../actix_web/trait.Responder.html#foreign-impls). +Then `respond_to()` get called on returned object. And finally +result of the `respond_to()` call get converted to `Reply` object. -By default actix provides several `FromRequest` implementations for some standard types, +By default actix provides `Responder` implementations for some standard types, like `&'static str`, `String`, etc. For complete list of implementations check -[FromRequest documentation](../actix_web/trait.FromRequest.html#foreign-impls). +[Responder documentation](../actix_web/trait.Responder.html#foreign-impls). Examples of valid handlers: @@ -59,11 +59,11 @@ struct MyObj { } /// we have to convert Error into HttpResponse as well -impl FromRequest for MyObj { +impl Responder for MyObj { type Item = HttpResponse; type Error = Error; - fn from_request(self, req: HttpRequest) -> Result { + fn respond_to(self, req: HttpRequest) -> Result { let body = serde_json::to_string(&self)?; // Create response and set content type diff --git a/guide/src/qs_4_5.md b/guide/src/qs_4_5.md index 6c2863e4c..cbc69654c 100644 --- a/guide/src/qs_4_5.md +++ b/guide/src/qs_4_5.md @@ -5,11 +5,11 @@ and [`ResponseError` trait](../actix_web/error/trait.ResponseError.html) for handling handler's errors. Any error that implements `ResponseError` trait can be returned as error value. *Handler* can return *Result* object, actix by default provides -`FromRequest` implemenation for compatible result object. Here is implementation +`Responder` implemenation for compatible result object. Here is implementation definition: ```rust,ignore -impl> FromRequest for Result +impl> Responder for Result ``` And any error that implements `ResponseError` can be converted into `Error` object. diff --git a/src/fs.rs b/src/fs.rs index 736d9910e..78f3b4e72 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -10,7 +10,7 @@ use std::ops::{Deref, DerefMut}; use mime_guess::get_mime_type; use param::FromParam; -use handler::{Handler, FromRequest}; +use handler::{Handler, Responder}; use httprequest::HttpRequest; use httpresponse::HttpResponse; use httpcodes::HTTPOk; @@ -77,11 +77,11 @@ impl DerefMut for NamedFile { } } -impl FromRequest for NamedFile { +impl Responder for NamedFile { type Item = HttpResponse; type Error = io::Error; - fn from_request(mut self, _: HttpRequest) -> Result { + fn respond_to(mut self, _: HttpRequest) -> Result { let mut resp = HTTPOk.build(); if let Some(ext) = self.path().extension() { let mime = get_mime_type(&ext.to_string_lossy()); @@ -124,11 +124,11 @@ impl Directory { } } -impl FromRequest for Directory { +impl Responder for Directory { type Item = HttpResponse; type Error = io::Error; - fn from_request(self, req: HttpRequest) -> Result { + fn respond_to(self, req: HttpRequest) -> Result { let index_of = format!("Index of {}", req.path()); let mut body = String::new(); let base = Path::new(req.path()); @@ -176,14 +176,14 @@ pub enum FilesystemElement { Directory(Directory), } -impl FromRequest for FilesystemElement { +impl Responder for FilesystemElement { type Item = HttpResponse; type Error = io::Error; - fn from_request(self, req: HttpRequest) -> Result { + fn respond_to(self, req: HttpRequest) -> Result { match self { - FilesystemElement::File(file) => file.from_request(req), - FilesystemElement::Directory(dir) => dir.from_request(req), + FilesystemElement::File(file) => file.respond_to(req), + FilesystemElement::Directory(dir) => dir.respond_to(req), } } } @@ -294,7 +294,7 @@ mod tests { let _f: &File = &file; } { let _f: &mut File = &mut file; } - let resp = file.from_request(HttpRequest::default()).unwrap(); + let resp = file.respond_to(HttpRequest::default()).unwrap(); assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml") } @@ -312,7 +312,7 @@ mod tests { req.match_info_mut().add("tail", ""); st.show_index = true; - let resp = st.handle(req).from_request(HttpRequest::default()).unwrap(); + let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap(); assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/html; charset=utf-8"); assert!(resp.body().is_binary()); assert!(format!("{:?}", resp.body()).contains("README.md")); diff --git a/src/handler.rs b/src/handler.rs index f059e81c0..9d60f1f6d 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -18,26 +18,30 @@ use httpresponse::HttpResponse; pub trait Handler: 'static { /// The type of value that handler will return. - type Result: FromRequest; + type Result: Responder; /// Handle request fn handle(&self, req: HttpRequest) -> Self::Result; } -pub trait FromRequest { +/// Trait implemented by types that generate responses for clients. +/// +/// Types that implement this trait can be used as the return type of a handler. +pub trait Responder { /// The associated item which can be returned. type Item: Into; /// The associated error which can be returned. type Error: Into; - fn from_request(self, req: HttpRequest) -> Result; + /// Convert itself to `Reply` or `Error`. + fn respond_to(self, req: HttpRequest) -> Result; } /// Handler for Fn() impl Handler for F where F: Fn(HttpRequest) -> R + 'static, - R: FromRequest + 'static + R: Responder + 'static { type Result = R; @@ -93,20 +97,20 @@ impl Reply { } } -impl FromRequest for Reply { +impl Responder for Reply { type Item = Reply; type Error = Error; - fn from_request(self, _: HttpRequest) -> Result { + fn respond_to(self, _: HttpRequest) -> Result { Ok(self) } } -impl FromRequest for HttpResponse { +impl Responder for HttpResponse { type Item = Reply; type Error = Error; - fn from_request(self, _: HttpRequest) -> Result { + fn respond_to(self, _: HttpRequest) -> Result { Ok(Reply(ReplyItem::Message(Box::new(self)))) } } @@ -118,14 +122,14 @@ impl From for Reply { } } -impl> FromRequest for Result +impl> Responder for Result { - type Item = ::Item; + type Item = ::Item; type Error = Error; - fn from_request(self, req: HttpRequest) -> Result { + fn respond_to(self, req: HttpRequest) -> Result { match self { - Ok(val) => match val.from_request(req) { + Ok(val) => match val.respond_to(req) { Ok(val) => Ok(val), Err(err) => Err(err.into()), }, @@ -143,12 +147,12 @@ impl> From> for Reply { } } -impl>, S: 'static> FromRequest for HttpContext +impl>, S: 'static> Responder for HttpContext { type Item = Reply; type Error = Error; - fn from_request(self, _: HttpRequest) -> Result { + fn respond_to(self, _: HttpRequest) -> Result { Ok(Reply(ReplyItem::Actor(Box::new(self)))) } } @@ -160,12 +164,12 @@ impl>, S: 'static> From> fo } } -impl FromRequest for Box> +impl Responder for Box> { type Item = Reply; type Error = Error; - fn from_request(self, _: HttpRequest) -> Result { + fn respond_to(self, _: HttpRequest) -> Result { Ok(Reply(ReplyItem::Future(self))) } } @@ -179,7 +183,7 @@ pub(crate) trait RouteHandler: 'static { pub(crate) struct WrapHandler where H: Handler, - R: FromRequest, + R: Responder, S: 'static, { h: H, @@ -188,7 +192,7 @@ struct WrapHandler impl WrapHandler where H: Handler, - R: FromRequest, + R: Responder, S: 'static, { pub fn new(h: H) -> Self { @@ -198,12 +202,12 @@ impl WrapHandler impl RouteHandler for WrapHandler where H: Handler, - R: FromRequest + 'static, + R: Responder + 'static, S: 'static, { fn handle(&self, req: HttpRequest) -> Reply { let req2 = req.clone_without_state(); - match self.h.handle(req).from_request(req2) { + match self.h.handle(req).respond_to(req2) { Ok(reply) => reply.into(), Err(err) => Reply::response(err.into()), } @@ -265,11 +269,11 @@ impl RouteHandler for AsyncHandler /// ``` pub struct Json (pub T); -impl FromRequest for Json { +impl Responder for Json { type Item = HttpResponse; type Error = Error; - fn from_request(self, _: HttpRequest) -> Result { + fn respond_to(self, _: HttpRequest) -> Result { let body = serde_json::to_string(&self.0)?; Ok(HttpResponse::Ok() @@ -406,7 +410,7 @@ mod tests { #[test] fn test_json() { let json = Json(MyObj{name: "test"}); - let resp = json.from_request(HttpRequest::default()).unwrap(); + let resp = json.respond_to(HttpRequest::default()).unwrap(); assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "application/json"); } diff --git a/src/httpcodes.rs b/src/httpcodes.rs index f00a2a5f5..274167c99 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -3,7 +3,7 @@ use http::{StatusCode, Error as HttpError}; use body::Body; -use handler::{Reply, Handler, RouteHandler, FromRequest}; +use handler::{Reply, Handler, RouteHandler, Responder}; use httprequest::HttpRequest; use httpresponse::{HttpResponse, HttpResponseBuilder}; @@ -81,11 +81,11 @@ impl RouteHandler for StaticResponse { } } -impl FromRequest for StaticResponse { +impl Responder for StaticResponse { type Item = HttpResponse; type Error = HttpError; - fn from_request(self, _: HttpRequest) -> Result { + fn respond_to(self, _: HttpRequest) -> Result { self.build().body(Body::Empty) } } diff --git a/src/httpresponse.rs b/src/httpresponse.rs index a60411d27..a1b26caf9 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -12,7 +12,7 @@ use cookie::Cookie; use body::Body; use error::Error; -use handler::FromRequest; +use handler::Responder; use encoding::ContentEncoding; use httprequest::HttpRequest; @@ -431,11 +431,11 @@ impl From for HttpResponse { } } -impl FromRequest for HttpResponseBuilder { +impl Responder for HttpResponseBuilder { type Item = HttpResponse; type Error = HttpError; - fn from_request(mut self, _: HttpRequest) -> Result { + fn respond_to(mut self, _: HttpRequest) -> Result { self.finish() } } @@ -449,11 +449,11 @@ impl From<&'static str> for HttpResponse { } } -impl FromRequest for &'static str { +impl Responder for &'static str { type Item = HttpResponse; type Error = HttpError; - fn from_request(self, _: HttpRequest) -> Result { + fn respond_to(self, _: HttpRequest) -> Result { HttpResponse::build(StatusCode::OK) .content_type("text/plain; charset=utf-8") .body(self) @@ -469,11 +469,11 @@ impl From<&'static [u8]> for HttpResponse { } } -impl FromRequest for &'static [u8] { +impl Responder for &'static [u8] { type Item = HttpResponse; type Error = HttpError; - fn from_request(self, _: HttpRequest) -> Result { + fn respond_to(self, _: HttpRequest) -> Result { HttpResponse::build(StatusCode::OK) .content_type("application/octet-stream") .body(self) @@ -489,11 +489,11 @@ impl From for HttpResponse { } } -impl FromRequest for String { +impl Responder for String { type Item = HttpResponse; type Error = HttpError; - fn from_request(self, _: HttpRequest) -> Result { + fn respond_to(self, _: HttpRequest) -> Result { HttpResponse::build(StatusCode::OK) .content_type("text/plain; charset=utf-8") .body(self) @@ -509,11 +509,11 @@ impl<'a> From<&'a String> for HttpResponse { } } -impl<'a> FromRequest for &'a String { +impl<'a> Responder for &'a String { type Item = HttpResponse; type Error = HttpError; - fn from_request(self, _: HttpRequest) -> Result { + fn respond_to(self, _: HttpRequest) -> Result { HttpResponse::build(StatusCode::OK) .content_type("text/plain; charset=utf-8") .body(self) @@ -529,11 +529,11 @@ impl From for HttpResponse { } } -impl FromRequest for Bytes { +impl Responder for Bytes { type Item = HttpResponse; type Error = HttpError; - fn from_request(self, _: HttpRequest) -> Result { + fn respond_to(self, _: HttpRequest) -> Result { HttpResponse::build(StatusCode::OK) .content_type("application/octet-stream") .body(self) @@ -549,11 +549,11 @@ impl From for HttpResponse { } } -impl FromRequest for BytesMut { +impl Responder for BytesMut { type Item = HttpResponse; type Error = HttpError; - fn from_request(self, _: HttpRequest) -> Result { + fn respond_to(self, _: HttpRequest) -> Result { HttpResponse::build(StatusCode::OK) .content_type("application/octet-stream") .body(self) @@ -689,7 +689,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().binary().unwrap(), &Binary::from("test")); - let resp: HttpResponse = "test".from_request(req.clone()).ok().unwrap(); + let resp: HttpResponse = "test".respond_to(req.clone()).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), header::HeaderValue::from_static("text/plain; charset=utf-8")); @@ -703,7 +703,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().binary().unwrap(), &Binary::from(b"test".as_ref())); - let resp: HttpResponse = b"test".as_ref().from_request(req.clone()).ok().unwrap(); + let resp: HttpResponse = b"test".as_ref().respond_to(req.clone()).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), header::HeaderValue::from_static("application/octet-stream")); @@ -717,7 +717,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().binary().unwrap(), &Binary::from("test".to_owned())); - let resp: HttpResponse = "test".to_owned().from_request(req.clone()).ok().unwrap(); + let resp: HttpResponse = "test".to_owned().respond_to(req.clone()).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), header::HeaderValue::from_static("text/plain; charset=utf-8")); @@ -731,7 +731,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().binary().unwrap(), &Binary::from((&"test".to_owned()))); - let resp: HttpResponse = (&"test".to_owned()).from_request(req.clone()).ok().unwrap(); + let resp: HttpResponse = (&"test".to_owned()).respond_to(req.clone()).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), header::HeaderValue::from_static("text/plain; charset=utf-8")); @@ -747,7 +747,7 @@ mod tests { assert_eq!(resp.body().binary().unwrap(), &Binary::from(Bytes::from_static(b"test"))); let b = Bytes::from_static(b"test"); - let resp: HttpResponse = b.from_request(req.clone()).ok().unwrap(); + let resp: HttpResponse = b.respond_to(req.clone()).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), header::HeaderValue::from_static("application/octet-stream")); @@ -763,7 +763,7 @@ mod tests { assert_eq!(resp.body().binary().unwrap(), &Binary::from(BytesMut::from("test"))); let b = BytesMut::from("test"); - let resp: HttpResponse = b.from_request(req.clone()).ok().unwrap(); + let resp: HttpResponse = b.respond_to(req.clone()).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), header::HeaderValue::from_static("application/octet-stream")); diff --git a/src/lib.rs b/src/lib.rs index 062e62209..d9563e1fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -122,7 +122,7 @@ pub use application::Application; pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; pub use payload::{Payload, PayloadItem}; -pub use handler::{Reply, FromRequest, Json, NormalizePath}; +pub use handler::{Reply, Responder, Json, NormalizePath}; pub use route::Route; pub use resource::Resource; pub use server::HttpServer; diff --git a/src/resource.rs b/src/resource.rs index 523a30966..af6151474 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -5,7 +5,7 @@ use http::{Method, StatusCode}; use pred; use body::Body; use route::Route; -use handler::{Reply, Handler, FromRequest}; +use handler::{Reply, Handler, Responder}; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -120,7 +120,7 @@ impl Resource { /// ``` pub fn f(&mut self, handler: F) where F: Fn(HttpRequest) -> R + 'static, - R: FromRequest + 'static, + R: Responder + 'static, { self.routes.push(Route::default()); self.routes.last_mut().unwrap().f(handler) diff --git a/src/route.rs b/src/route.rs index daea3cb32..fa2c78130 100644 --- a/src/route.rs +++ b/src/route.rs @@ -2,7 +2,7 @@ use futures::Future; use error::Error; use pred::Predicate; -use handler::{Reply, Handler, FromRequest, RouteHandler, AsyncHandler, WrapHandler}; +use handler::{Reply, Handler, Responder, RouteHandler, AsyncHandler, WrapHandler}; use httpcodes::HTTPNotFound; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -58,7 +58,7 @@ impl Route { /// during route configuration, because it does not return reference to self. pub fn f(&mut self, handler: F) where F: Fn(HttpRequest) -> R + 'static, - R: FromRequest + 'static, + R: Responder + 'static, { self.handler = Box::new(WrapHandler::new(handler)); } From b61c2a0cf07d2b729aa0a9de841cf091cdb2d8bf Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 14 Dec 2017 11:20:45 -0800 Subject: [PATCH 095/279] handle keep-alive setting more efficient --- src/h1.rs | 17 ++++++++++------- src/h1writer.rs | 7 +------ src/h2.rs | 3 ++- src/h2writer.rs | 8 +------- src/middlewares/defaultheaders.rs | 25 ++++++++++++++++++++----- src/server.rs | 22 +++++++++++++++++----- 6 files changed, 51 insertions(+), 31 deletions(-) diff --git a/src/h1.rs b/src/h1.rs index fef40e56b..ba42584b3 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -94,8 +94,8 @@ impl Http1 pub fn poll(&mut self) -> Poll { // keep-alive timer - if let Some(ref mut timeout) = self.keepalive_timer { - match timeout.poll() { + if self.keepalive_timer.is_some() { + match self.keepalive_timer.as_mut().unwrap().poll() { Ok(Async::Ready(_)) => { trace!("Keep-alive timeout, close connection"); return Ok(Async::Ready(Http1Result::Done)) @@ -124,10 +124,12 @@ impl Http1 not_ready = false; // overide keep-alive state - if self.stream.keepalive() { - self.flags.insert(Flags::KEEPALIVE); - } else { - self.flags.remove(Flags::KEEPALIVE); + if self.settings.keep_alive_enabled() { + if self.stream.keepalive() { + self.flags.insert(Flags::KEEPALIVE); + } else { + self.flags.remove(Flags::KEEPALIVE); + } } self.stream.reset(); @@ -249,7 +251,8 @@ impl Http1 Ok(Async::NotReady) => { // start keep-alive timer, this is also slow request timeout if self.tasks.is_empty() { - if let Some(keep_alive) = self.settings.keep_alive() { + if self.settings.keep_alive_enabled() { + let keep_alive = self.settings.keep_alive(); if keep_alive > 0 && self.flags.contains(Flags::KEEPALIVE) { if self.keepalive_timer.is_none() { trace!("Start keep-alive timer"); diff --git a/src/h1writer.rs b/src/h1writer.rs index 956f752b3..ae4ef1644 100644 --- a/src/h1writer.rs +++ b/src/h1writer.rs @@ -2,7 +2,7 @@ use std::io; use futures::{Async, Poll}; use tokio_io::AsyncWrite; use http::Version; -use http::header::{HeaderValue, CONNECTION, CONTENT_TYPE, DATE}; +use http::header::{HeaderValue, CONNECTION, DATE}; use helpers; use body::Body; @@ -180,11 +180,6 @@ impl Writer for H1Writer { buffer.extend_from_slice(b"\r\n"); } - // default content-type - if !msg.headers().contains_key(CONTENT_TYPE) { - buffer.extend_from_slice(b"ContentType: application/octet-stream\r\n"); - } - // msg eof buffer.extend_from_slice(b"\r\n"); self.headers_size = buffer.len() as u32; diff --git a/src/h2.rs b/src/h2.rs index 9dd85a935..4b9b6c1d4 100644 --- a/src/h2.rs +++ b/src/h2.rs @@ -155,7 +155,8 @@ impl Http2 Ok(Async::NotReady) => { // start keep-alive timer if self.tasks.is_empty() { - if let Some(keep_alive) = self.settings.keep_alive() { + if self.settings.keep_alive_enabled() { + let keep_alive = self.settings.keep_alive(); if keep_alive > 0 && self.keepalive_timer.is_none() { trace!("Start keep-alive timer"); let mut timeout = Timeout::new( diff --git a/src/h2writer.rs b/src/h2writer.rs index 53a07b80f..afcca2da4 100644 --- a/src/h2writer.rs +++ b/src/h2writer.rs @@ -4,7 +4,7 @@ use futures::{Async, Poll}; use http2::{Reason, SendStream}; use http2::server::Respond; use http::{Version, HttpTryFrom, Response}; -use http::header::{HeaderValue, CONNECTION, CONTENT_TYPE, TRANSFER_ENCODING, DATE}; +use http::header::{HeaderValue, CONNECTION, TRANSFER_ENCODING, DATE}; use helpers; use body::Body; @@ -131,12 +131,6 @@ impl Writer for H2Writer { msg.headers_mut().insert(DATE, HeaderValue::try_from(&bytes[..]).unwrap()); } - // default content-type - if !msg.headers().contains_key(CONTENT_TYPE) { - msg.headers_mut().insert( - CONTENT_TYPE, HeaderValue::from_static("application/octet-stream")); - } - let mut resp = Response::new(()); *resp.status_mut() = msg.status(); *resp.version_mut() = Version::HTTP_2; diff --git a/src/middlewares/defaultheaders.rs b/src/middlewares/defaultheaders.rs index 3335847e0..3e9dc278a 100644 --- a/src/middlewares/defaultheaders.rs +++ b/src/middlewares/defaultheaders.rs @@ -1,6 +1,6 @@ //! Default response headers use http::{HeaderMap, HttpTryFrom}; -use http::header::{HeaderName, HeaderValue}; +use http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -27,22 +27,30 @@ use middlewares::{Response, Middleware}; /// .finish(); /// } /// ``` -pub struct DefaultHeaders(HeaderMap); +pub struct DefaultHeaders{ + ct: bool, + headers: HeaderMap, +} impl DefaultHeaders { pub fn build() -> DefaultHeadersBuilder { - DefaultHeadersBuilder{headers: Some(HeaderMap::new())} + DefaultHeadersBuilder{ct: false, headers: Some(HeaderMap::new())} } } impl Middleware for DefaultHeaders { fn response(&self, _: &mut HttpRequest, mut resp: Box) -> Response { - for (key, value) in self.0.iter() { + for (key, value) in self.headers.iter() { if !resp.headers().contains_key(key) { resp.headers_mut().insert(key, value.clone()); } } + // default content-type + if self.ct && !resp.headers().contains_key(CONTENT_TYPE) { + resp.headers_mut().insert( + CONTENT_TYPE, HeaderValue::from_static("application/octet-stream")); + } Response::Done(resp) } } @@ -50,6 +58,7 @@ impl Middleware for DefaultHeaders { /// Structure that follows the builder pattern for building `DefaultHeaders` middleware. #[derive(Debug)] pub struct DefaultHeadersBuilder { + ct: bool, headers: Option, } @@ -76,10 +85,16 @@ impl DefaultHeadersBuilder { self } + /// Set *CONTENT-TYPE* header if response does not contain this header. + pub fn content_type(&mut self) -> &mut Self { + self.ct = true; + self + } + /// Finishes building and returns the built `DefaultHeaders` middleware. pub fn finish(&mut self) -> DefaultHeaders { let headers = self.headers.take().expect("cannot reuse middleware builder"); - DefaultHeaders(headers) + DefaultHeaders{ ct: self.ct, headers: headers } } } diff --git a/src/server.rs b/src/server.rs index c85f0a729..bb9552a94 100644 --- a/src/server.rs +++ b/src/server.rs @@ -171,7 +171,7 @@ impl HttpServer for app in &mut apps { app.server_settings(settings.clone()); } - self.h = Some(Rc::new(WorkerSettings{h: apps, keep_alive: self.keep_alive})); + self.h = Some(Rc::new(WorkerSettings::new(apps, self.keep_alive))); // start server Ok(HttpServer::create(move |ctx| { @@ -411,23 +411,35 @@ struct Worker { pub(crate) struct WorkerSettings { h: Vec, - keep_alive: Option, + enabled: bool, + keep_alive: u64, } impl WorkerSettings { + fn new(h: Vec, keep_alive: Option) -> WorkerSettings { + WorkerSettings { + h: h, + enabled: if let Some(ka) = keep_alive { ka > 0 } else { false }, + keep_alive: keep_alive.unwrap_or(0), + } + } + pub fn handlers(&self) -> &Vec { &self.h } - pub fn keep_alive(&self) -> Option { + pub fn keep_alive(&self) -> u64 { self.keep_alive } + pub fn keep_alive_enabled(&self) -> bool { + self.enabled + } } impl Worker { fn new(h: Vec, handler: StreamHandlerType, keep_alive: Option) -> Worker { Worker { - h: Rc::new(WorkerSettings{h: h, keep_alive: keep_alive}), + h: Rc::new(WorkerSettings::new(h, keep_alive)), handler: handler, } } @@ -455,7 +467,7 @@ impl Handler> for Worker fn handle(&mut self, msg: IoStream, _: &mut Context) -> Response> { - if self.h.keep_alive.is_none() && + if !self.h.keep_alive_enabled() && msg.io.set_keepalive(Some(Duration::new(75, 0))).is_err() { error!("Can not set socket keep-alive option"); From c37565cc4aa7c53aa39fa0a5f909945a7c5bd44d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 14 Dec 2017 19:34:31 -0800 Subject: [PATCH 096/279] various server optimizations --- src/encoding.rs | 86 ++++++++++++----------- src/h1.rs | 102 +++++++++++++++++++-------- src/h1writer.rs | 17 +++-- src/h2writer.rs | 7 +- src/helpers.rs | 170 +++++++++++++++++++++++++++++++++++++++++++++ src/httprequest.rs | 94 +++++++++++++++---------- src/param.rs | 4 ++ src/pipeline.rs | 22 +++--- src/server.rs | 10 +++ 9 files changed, 385 insertions(+), 127 deletions(-) diff --git a/src/encoding.rs b/src/encoding.rs index 653894a96..7918e20d9 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -14,6 +14,7 @@ use brotli2::write::{BrotliDecoder, BrotliEncoder}; use bytes::{Bytes, BytesMut, BufMut, Writer}; use helpers; +use helpers::SharedBytes; use body::{Body, Binary}; use error::PayloadError; use httprequest::HttpMessage; @@ -337,15 +338,15 @@ impl PayloadWriter for EncodedPayload { pub(crate) struct PayloadEncoder(ContentEncoder); -impl Default for PayloadEncoder { - fn default() -> PayloadEncoder { - PayloadEncoder(ContentEncoder::Identity(TransferEncoding::eof())) - } -} - impl PayloadEncoder { - pub fn new(req: &HttpMessage, resp: &mut HttpResponse) -> PayloadEncoder { + pub fn empty(bytes: SharedBytes) -> PayloadEncoder { + PayloadEncoder(ContentEncoder::Identity(TransferEncoding::eof(bytes))) + } + + pub fn new(buf: SharedBytes, req: &HttpMessage, resp: &mut HttpResponse) + -> PayloadEncoder + { let version = resp.version().unwrap_or_else(|| req.version); let mut body = resp.replace_body(Body::Empty); let has_body = match body { @@ -390,11 +391,11 @@ impl PayloadEncoder { error!("Chunked transfer is enabled but body is set to Empty"); } resp.headers_mut().insert(CONTENT_LENGTH, HeaderValue::from_static("0")); - TransferEncoding::eof() + TransferEncoding::eof(buf) }, Body::Binary(ref mut bytes) => { if compression { - let transfer = TransferEncoding::eof(); + let transfer = TransferEncoding::eof(SharedBytes::default()); let mut enc = match encoding { ContentEncoding::Deflate => ContentEncoder::Deflate( DeflateEncoder::new(transfer, Compression::Default)), @@ -414,11 +415,11 @@ impl PayloadEncoder { CONTENT_LENGTH, helpers::convert_into_header(b.len())); *bytes = Binary::from(b); encoding = ContentEncoding::Identity; - TransferEncoding::eof() + TransferEncoding::eof(buf) } else { resp.headers_mut().insert( CONTENT_LENGTH, helpers::convert_into_header(bytes.len())); - TransferEncoding::eof() + TransferEncoding::eof(buf) } } Body::Streaming(_) | Body::StreamingContext => { @@ -429,26 +430,26 @@ impl PayloadEncoder { } if version == Version::HTTP_2 { resp.headers_mut().remove(TRANSFER_ENCODING); - TransferEncoding::eof() + TransferEncoding::eof(buf) } else { resp.headers_mut().insert( TRANSFER_ENCODING, HeaderValue::from_static("chunked")); - TransferEncoding::chunked() + TransferEncoding::chunked(buf) } } else if let Some(len) = resp.headers().get(CONTENT_LENGTH) { // Content-Length if let Ok(s) = len.to_str() { if let Ok(len) = s.parse::() { - TransferEncoding::length(len) + TransferEncoding::length(len, buf) } else { debug!("illegal Content-Length: {:?}", len); - TransferEncoding::eof() + TransferEncoding::eof(buf) } } else { - TransferEncoding::eof() + TransferEncoding::eof(buf) } } else { - TransferEncoding::eof() + TransferEncoding::eof(buf) } } Body::Upgrade(_) | Body::UpgradeContext => { @@ -462,7 +463,7 @@ impl PayloadEncoder { encoding = ContentEncoding::Identity; resp.headers_mut().remove(CONTENT_ENCODING); } - TransferEncoding::eof() + TransferEncoding::eof(buf) } }; resp.replace_body(body); @@ -540,13 +541,13 @@ impl ContentEncoder { pub fn get_ref(&self) -> &BytesMut { match *self { ContentEncoder::Br(ref encoder) => - &encoder.get_ref().buffer, + encoder.get_ref().buffer.get_ref(), ContentEncoder::Deflate(ref encoder) => - &encoder.get_ref().buffer, + encoder.get_ref().buffer.get_ref(), ContentEncoder::Gzip(ref encoder) => - &encoder.get_ref().buffer, + encoder.get_ref().buffer.get_ref(), ContentEncoder::Identity(ref encoder) => - &encoder.buffer, + encoder.buffer.get_ref(), } } @@ -554,20 +555,21 @@ impl ContentEncoder { pub fn get_mut(&mut self) -> &mut BytesMut { match *self { ContentEncoder::Br(ref mut encoder) => - &mut encoder.get_mut().buffer, + encoder.get_mut().buffer.get_mut(), ContentEncoder::Deflate(ref mut encoder) => - &mut encoder.get_mut().buffer, + encoder.get_mut().buffer.get_mut(), ContentEncoder::Gzip(ref mut encoder) => - &mut encoder.get_mut().buffer, + encoder.get_mut().buffer.get_mut(), ContentEncoder::Identity(ref mut encoder) => - &mut encoder.buffer, + encoder.buffer.get_mut(), } } #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] #[inline(always)] pub fn write_eof(&mut self) -> Result<(), io::Error> { - let encoder = mem::replace(self, ContentEncoder::Identity(TransferEncoding::eof())); + let encoder = mem::replace( + self, ContentEncoder::Identity(TransferEncoding::eof(SharedBytes::default()))); match encoder { ContentEncoder::Br(encoder) => { @@ -639,7 +641,7 @@ impl ContentEncoder { } } ContentEncoder::Identity(ref mut encoder) => { - encoder.write_all(data)?; + encoder.encode(data); Ok(()) } } @@ -650,7 +652,7 @@ impl ContentEncoder { #[derive(Debug, Clone)] pub(crate) struct TransferEncoding { kind: TransferEncodingKind, - buffer: BytesMut, + buffer: SharedBytes, } #[derive(Debug, PartialEq, Clone)] @@ -670,26 +672,26 @@ enum TransferEncodingKind { impl TransferEncoding { #[inline] - pub fn eof() -> TransferEncoding { + pub fn eof(bytes: SharedBytes) -> TransferEncoding { TransferEncoding { kind: TransferEncodingKind::Eof, - buffer: BytesMut::new(), + buffer: bytes, } } #[inline] - pub fn chunked() -> TransferEncoding { + pub fn chunked(bytes: SharedBytes) -> TransferEncoding { TransferEncoding { kind: TransferEncodingKind::Chunked(false), - buffer: BytesMut::new(), + buffer: bytes, } } #[inline] - pub fn length(len: u64) -> TransferEncoding { + pub fn length(len: u64, bytes: SharedBytes) -> TransferEncoding { TransferEncoding { kind: TransferEncodingKind::Length(len), - buffer: BytesMut::new(), + buffer: bytes, } } @@ -709,7 +711,7 @@ impl TransferEncoding { pub fn encode(&mut self, msg: &[u8]) -> bool { match self.kind { TransferEncodingKind::Eof => { - self.buffer.extend_from_slice(msg); + self.buffer.get_mut().extend_from_slice(msg); msg.is_empty() }, TransferEncodingKind::Chunked(ref mut eof) => { @@ -719,11 +721,11 @@ impl TransferEncoding { if msg.is_empty() { *eof = true; - self.buffer.extend_from_slice(b"0\r\n\r\n"); + self.buffer.get_mut().extend_from_slice(b"0\r\n\r\n"); } else { - write!(self.buffer, "{:X}\r\n", msg.len()).unwrap(); - self.buffer.extend_from_slice(msg); - self.buffer.extend_from_slice(b"\r\n"); + write!(self.buffer.get_mut(), "{:X}\r\n", msg.len()).unwrap(); + self.buffer.get_mut().extend_from_slice(msg); + self.buffer.get_mut().extend_from_slice(b"\r\n"); } *eof }, @@ -733,7 +735,7 @@ impl TransferEncoding { } let max = cmp::min(*remaining, msg.len() as u64); trace!("sized write = {}", max); - self.buffer.extend_from_slice(msg[..max as usize].as_ref()); + self.buffer.get_mut().extend_from_slice(msg[..max as usize].as_ref()); *remaining -= max as u64; trace!("encoded {} bytes, remaining = {}", max, remaining); @@ -750,7 +752,7 @@ impl TransferEncoding { TransferEncodingKind::Chunked(ref mut eof) => { if !*eof { *eof = true; - self.buffer.extend_from_slice(b"0\r\n\r\n"); + self.buffer.get_mut().extend_from_slice(b"0\r\n\r\n"); } }, } diff --git a/src/h1.rs b/src/h1.rs index ba42584b3..8343a99d2 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -1,4 +1,4 @@ -use std::{self, io, ptr}; +use std::{self, io}; use std::rc::Rc; use std::net::SocketAddr; use std::time::Duration; @@ -16,14 +16,15 @@ use tokio_core::reactor::Timeout; use pipeline::Pipeline; use encoding::PayloadType; use channel::{HttpHandler, HttpHandlerTask}; -use h1writer::H1Writer; +use h1writer::{Writer, H1Writer}; use server::WorkerSettings; use httpcodes::HTTPNotFound; use httprequest::HttpRequest; use error::{ParseError, PayloadError, ResponseError}; use payload::{Payload, PayloadWriter, DEFAULT_BUFFER_SIZE}; -const INIT_BUFFER_SIZE: usize = 8192; +const LW_BUFFER_SIZE: usize = 4096; +const HW_BUFFER_SIZE: usize = 16_384; const MAX_BUFFER_SIZE: usize = 131_072; const MAX_HEADERS: usize = 100; const MAX_PIPELINED_MESSAGES: usize = 16; @@ -78,10 +79,11 @@ impl Http1 H: HttpHandler + 'static { pub fn new(h: Rc>, stream: T, addr: Option) -> Self { + let bytes = h.get_shared_bytes(); Http1{ flags: Flags::KEEPALIVE, settings: h, addr: addr, - stream: H1Writer::new(stream), + stream: H1Writer::new(stream, bytes), reader: Reader::new(), read_buf: BytesMut::new(), tasks: VecDeque::new(), @@ -92,6 +94,18 @@ impl Http1 (self.settings, self.stream.into_inner(), self.addr, self.read_buf.freeze()) } + fn poll_completed(&mut self) -> Result { + // check stream state + match self.stream.poll_completed() { + Ok(Async::Ready(_)) => Ok(false), + Ok(Async::NotReady) => Ok(true), + Err(err) => { + debug!("Error sending data: {}", err); + Err(()) + } + } + } + pub fn poll(&mut self) -> Poll { // keep-alive timer if self.keepalive_timer.is_some() { @@ -116,6 +130,10 @@ impl Http1 if !io && !item.flags.contains(EntryFlags::EOF) { if item.flags.contains(EntryFlags::ERROR) { + // check stream state + if let Ok(Async::NotReady) = self.stream.poll_completed() { + return Ok(Async::NotReady) + } return Err(()) } @@ -146,6 +164,12 @@ impl Http1 // it is not possible to recover from error // during pipe handling, so just drop connection error!("Unhandled error: {}", err); + item.flags.insert(EntryFlags::ERROR); + + // check stream state, we still can have valid data in buffer + if let Ok(Async::NotReady) = self.stream.poll_completed() { + return Ok(Async::NotReady) + } return Err(()) } } @@ -178,6 +202,10 @@ impl Http1 // no keep-alive if !self.flags.contains(Flags::KEEPALIVE) && self.tasks.is_empty() { + // check stream state + if self.poll_completed()? { + return Ok(Async::NotReady) + } if self.flags.contains(Flags::H2) { return Ok(Async::Ready(Http1Result::Switch)) } else { @@ -188,7 +216,9 @@ impl Http1 // read incoming data while !self.flags.contains(Flags::ERROR) && !self.flags.contains(Flags::H2) && self.tasks.len() < MAX_PIPELINED_MESSAGES { - match self.reader.parse(self.stream.get_mut(), &mut self.read_buf) { + match self.reader.parse(self.stream.get_mut(), + &mut self.read_buf, &self.settings) + { Ok(Async::Ready(Item::Http1(mut req))) => { not_ready = false; @@ -264,10 +294,16 @@ impl Http1 self.keepalive_timer = Some(to); } } else { + // check stream state + if self.poll_completed()? { + return Ok(Async::NotReady) + } // keep-alive disable, drop connection return Ok(Async::Ready(Http1Result::Done)) } } else { + // check stream state + self.poll_completed()?; // keep-alive unset, rely on operating system return Ok(Async::NotReady) } @@ -279,6 +315,11 @@ impl Http1 // check for parse error if self.tasks.is_empty() { + // check stream state + if self.poll_completed()? { + return Ok(Async::NotReady) + } + if self.flags.contains(Flags::H2) { return Ok(Async::Ready(Http1Result::Switch)) } @@ -288,6 +329,7 @@ impl Http1 } if not_ready { + self.poll_completed()?; return Ok(Async::NotReady) } } @@ -358,7 +400,9 @@ impl Reader { } } - pub fn parse(&mut self, io: &mut T, buf: &mut BytesMut) -> Poll + pub fn parse(&mut self, io: &mut T, + buf: &mut BytesMut, + settings: &WorkerSettings) -> Poll where T: AsyncRead { loop { @@ -394,7 +438,7 @@ impl Reader { } loop { - match Reader::parse_message(buf).map_err(ReaderError::Error)? { + match Reader::parse_message(buf, settings).map_err(ReaderError::Error)? { Message::Http1(msg, decoder) => { if let Some(payload) = decoder { self.payload = Some(payload); @@ -465,15 +509,9 @@ impl Reader { } fn read_from_io(&mut self, io: &mut T, buf: &mut BytesMut) - -> Poll - { - if buf.remaining_mut() < INIT_BUFFER_SIZE { - buf.reserve(INIT_BUFFER_SIZE); - unsafe { // Zero out unused memory - let b = buf.bytes_mut(); - let len = b.len(); - ptr::write_bytes(b.as_mut_ptr(), 0, len); - } + -> Poll { + if buf.remaining_mut() < LW_BUFFER_SIZE { + buf.reserve(HW_BUFFER_SIZE); } unsafe { let n = match io.read(buf.bytes_mut()) { @@ -490,7 +528,9 @@ impl Reader { } } - fn parse_message(buf: &mut BytesMut) -> Result { + fn parse_message(buf: &mut BytesMut, settings: &WorkerSettings) + -> Result + { if buf.is_empty() { return Ok(Message::NotReady); } @@ -537,13 +577,14 @@ impl Reader { let uri = Uri::from_shared(path).map_err(ParseError::Uri)?; // convert headers - let mut headers = HeaderMap::with_capacity(headers_len); + let msg = settings.get_http_message(); + msg.get_mut().headers.reserve(headers_len); for header in headers_indices[..headers_len].iter() { if let Ok(name) = HeaderName::try_from(slice.slice(header.name.0, header.name.1)) { if let Ok(value) = HeaderValue::try_from( slice.slice(header.value.0, header.value.1)) { - headers.append(name, value); + msg.get_mut().headers.append(name, value); } else { return Err(ParseError::Header) } @@ -552,25 +593,27 @@ impl Reader { } } - let decoder = if upgrade(&method, &headers) { + let decoder = if upgrade(&method, &msg.get_mut().headers) { Decoder::eof() } else { - let has_len = headers.contains_key(header::CONTENT_LENGTH); + let has_len = msg.get_mut().headers.contains_key(header::CONTENT_LENGTH); // Chunked encoding - if chunked(&headers)? { + if chunked(&msg.get_mut().headers)? { if has_len { return Err(ParseError::Header) } Decoder::chunked() } else { if !has_len { - let msg = HttpRequest::new(method, uri, version, headers, None); - return Ok(Message::Http1(msg, None)) + msg.get_mut().uri = uri; + msg.get_mut().method = method; + msg.get_mut().version = version; + return Ok(Message::Http1(HttpRequest::from_message(msg), None)) } // Content-Length - let len = headers.get(header::CONTENT_LENGTH).unwrap(); + let len = msg.get_mut().headers.get(header::CONTENT_LENGTH).unwrap(); if let Ok(s) = len.to_str() { if let Ok(len) = s.parse::() { Decoder::length(len) @@ -587,11 +630,14 @@ impl Reader { let (psender, payload) = Payload::new(false); let info = PayloadInfo { - tx: PayloadType::new(&headers, psender), + tx: PayloadType::new(&msg.get_mut().headers, psender), decoder: decoder, }; - let msg = HttpRequest::new(method, uri, version, headers, Some(payload)); - Ok(Message::Http1(msg, Some(info))) + msg.get_mut().uri = uri; + msg.get_mut().method = method; + msg.get_mut().version = version; + msg.get_mut().payload = Some(payload); + Ok(Message::Http1(HttpRequest::from_message(msg), Some(info))) } } diff --git a/src/h1writer.rs b/src/h1writer.rs index ae4ef1644..aa1489b7d 100644 --- a/src/h1writer.rs +++ b/src/h1writer.rs @@ -6,6 +6,7 @@ use http::header::{HeaderValue, CONNECTION, DATE}; use helpers; use body::Body; +use helpers::SharedBytes; use encoding::PayloadEncoder; use httprequest::HttpMessage; use httpresponse::HttpResponse; @@ -31,7 +32,7 @@ pub trait Writer { fn write_eof(&mut self) -> Result; - fn poll_complete(&mut self) -> Poll<(), io::Error>; + fn poll_completed(&mut self) -> Poll<(), io::Error>; } bitflags! { @@ -49,17 +50,19 @@ pub(crate) struct H1Writer { encoder: PayloadEncoder, written: u64, headers_size: u32, + buffer: SharedBytes, } impl H1Writer { - pub fn new(stream: T) -> H1Writer { + pub fn new(stream: T, buf: SharedBytes) -> H1Writer { H1Writer { flags: Flags::empty(), stream: stream, - encoder: PayloadEncoder::default(), + encoder: PayloadEncoder::empty(buf.clone()), written: 0, headers_size: 0, + buffer: buf, } } @@ -125,7 +128,7 @@ impl Writer for H1Writer { // prepare task self.flags.insert(Flags::STARTED); - self.encoder = PayloadEncoder::new(req, msg); + self.encoder = PayloadEncoder::new(self.buffer.clone(), req, msg); if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { self.flags.insert(Flags::KEEPALIVE); } @@ -148,9 +151,9 @@ impl Writer for H1Writer { { let mut buffer = self.encoder.get_mut(); if let Body::Binary(ref bytes) = *msg.body() { - buffer.reserve(150 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len()); + buffer.reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len()); } else { - buffer.reserve(150 + msg.headers().len() * AVERAGE_HEADER_SIZE); + buffer.reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE); } match version { @@ -229,7 +232,7 @@ impl Writer for H1Writer { } } - fn poll_complete(&mut self) -> Poll<(), io::Error> { + fn poll_completed(&mut self) -> Poll<(), io::Error> { match self.write_to_stream() { Ok(WriterState::Done) => Ok(Async::Ready(())), Ok(WriterState::Pause) => Ok(Async::NotReady), diff --git a/src/h2writer.rs b/src/h2writer.rs index afcca2da4..e022432d7 100644 --- a/src/h2writer.rs +++ b/src/h2writer.rs @@ -8,6 +8,7 @@ use http::header::{HeaderValue, CONNECTION, TRANSFER_ENCODING, DATE}; use helpers; use body::Body; +use helpers::SharedBytes; use encoding::PayloadEncoder; use httprequest::HttpMessage; use httpresponse::HttpResponse; @@ -38,7 +39,7 @@ impl H2Writer { H2Writer { respond: respond, stream: None, - encoder: PayloadEncoder::default(), + encoder: PayloadEncoder::empty(SharedBytes::default()), flags: Flags::empty(), written: 0, } @@ -115,7 +116,7 @@ impl Writer for H2Writer { // prepare response self.flags.insert(Flags::STARTED); - self.encoder = PayloadEncoder::new(req, msg); + self.encoder = PayloadEncoder::new(SharedBytes::default(), req, msg); if let Body::Empty = *msg.body() { self.flags.insert(Flags::EOF); } @@ -193,7 +194,7 @@ impl Writer for H2Writer { } } - fn poll_complete(&mut self) -> Poll<(), io::Error> { + fn poll_completed(&mut self) -> Poll<(), io::Error> { match self.write_to_stream() { Ok(WriterState::Done) => Ok(Async::Ready(())), Ok(WriterState::Pause) => Ok(Async::NotReady), diff --git a/src/helpers.rs b/src/helpers.rs index f49e04cda..e7733a1ec 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,10 +1,15 @@ use std::{str, mem, ptr, slice}; use std::cell::RefCell; use std::fmt::{self, Write}; +use std::rc::Rc; +use std::ops::{Deref, DerefMut}; +use std::collections::VecDeque; use time; use bytes::BytesMut; use http::header::HeaderValue; +use httprequest::HttpMessage; + // "Sun, 06 Nov 1994 08:49:37 GMT".len() pub const DATE_VALUE_LENGTH: usize = 29; @@ -51,6 +56,171 @@ impl fmt::Write for CachedDate { } } +/// Internal use only! unsafe +#[derive(Debug)] +pub(crate) struct SharedBytesPool(RefCell>>); + +impl SharedBytesPool { + pub fn new() -> SharedBytesPool { + SharedBytesPool(RefCell::new(VecDeque::with_capacity(128))) + } + + pub fn get_bytes(&self) -> Rc { + if let Some(bytes) = self.0.borrow_mut().pop_front() { + bytes + } else { + Rc::new(BytesMut::new()) + } + } + + pub fn release_bytes(&self, mut bytes: Rc) { + if self.0.borrow().len() < 128 { + Rc::get_mut(&mut bytes).unwrap().take(); + self.0.borrow_mut().push_front(bytes); + } + } +} + +#[derive(Debug)] +pub(crate) struct SharedBytes( + Option>, Option>); + +impl Drop for SharedBytes { + fn drop(&mut self) { + if let Some(ref pool) = self.1 { + if let Some(bytes) = self.0.take() { + if Rc::strong_count(&bytes) == 1 { + pool.release_bytes(bytes); + } + } + } + } +} + +impl SharedBytes { + + pub fn new(bytes: Rc, pool: Rc) -> SharedBytes { + SharedBytes(Some(bytes), Some(pool)) + } + + #[inline] + #[allow(mutable_transmutes)] + #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))] + pub fn get_mut(&self) -> &mut BytesMut { + let r: &BytesMut = self.0.as_ref().unwrap().as_ref(); + unsafe{mem::transmute(r)} + } + + #[inline] + pub fn get_ref(&self) -> &BytesMut { + self.0.as_ref().unwrap() + } +} + +impl Default for SharedBytes { + fn default() -> Self { + SharedBytes(Some(Rc::new(BytesMut::new())), None) + } +} + +impl Clone for SharedBytes { + fn clone(&self) -> SharedBytes { + SharedBytes(self.0.clone(), self.1.clone()) + } +} + +/// Internal use only! unsafe +pub(crate) struct SharedMessagePool(RefCell>>); + +impl SharedMessagePool { + pub fn new() -> SharedMessagePool { + SharedMessagePool(RefCell::new(VecDeque::with_capacity(128))) + } + + pub fn get(&self) -> Rc { + if let Some(msg) = self.0.borrow_mut().pop_front() { + msg + } else { + Rc::new(HttpMessage::default()) + } + } + + pub fn release(&self, mut msg: Rc) { + if self.0.borrow().len() < 128 { + Rc::get_mut(&mut msg).unwrap().reset(); + self.0.borrow_mut().push_front(msg); + } + } +} + +pub(crate) struct SharedHttpMessage( + Option>, Option>); + +impl Drop for SharedHttpMessage { + fn drop(&mut self) { + if let Some(ref pool) = self.1 { + if let Some(msg) = self.0.take() { + if Rc::strong_count(&msg) == 1 { + pool.release(msg); + } + } + } + } +} + +impl Deref for SharedHttpMessage { + type Target = HttpMessage; + + fn deref(&self) -> &HttpMessage { + self.get_ref() + } +} + +impl DerefMut for SharedHttpMessage { + + fn deref_mut(&mut self) -> &mut HttpMessage { + self.get_mut() + } +} + +impl Clone for SharedHttpMessage { + + fn clone(&self) -> SharedHttpMessage { + SharedHttpMessage(self.0.clone(), self.1.clone()) + } +} + +impl Default for SharedHttpMessage { + + fn default() -> SharedHttpMessage { + SharedHttpMessage(Some(Rc::new(HttpMessage::default())), None) + } +} + +impl SharedHttpMessage { + + pub fn from_message(msg: HttpMessage) -> SharedHttpMessage { + SharedHttpMessage(Some(Rc::new(msg)), None) + } + + pub fn new(msg: Rc, pool: Rc) -> SharedHttpMessage { + SharedHttpMessage(Some(msg), Some(pool)) + } + + #[inline(always)] + #[allow(mutable_transmutes)] + #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] + pub fn get_mut(&self) -> &mut HttpMessage { + let r: &HttpMessage = self.0.as_ref().unwrap().as_ref(); + unsafe{mem::transmute(r)} + } + + #[inline] + pub fn get_ref(&self) -> &HttpMessage { + self.0.as_ref().unwrap() + } +} + const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\ 2021222324252627282930313233343536373839\ diff --git a/src/httprequest.rs b/src/httprequest.rs index 80aa24409..b2adeb03a 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -15,6 +15,7 @@ use param::Params; use router::Router; use payload::Payload; use multipart::Multipart; +use helpers::SharedHttpMessage; use error::{ParseError, PayloadError, UrlGenerationError, MultipartError, CookieParseError, HttpRangeError, UrlencodedError}; @@ -69,10 +70,20 @@ impl HttpMessage { self.version != Version::HTTP_10 } } + + pub(crate) fn reset(&mut self) { + self.headers.clear(); + self.extensions.clear(); + self.params.clear(); + self.cookies.take(); + self.addr.take(); + self.payload.take(); + self.info.take(); + } } /// An HTTP Request -pub struct HttpRequest(Rc, Option>, Option>); +pub struct HttpRequest(SharedHttpMessage, Option>, Option>); impl HttpRequest<()> { /// Construct a new Request. @@ -81,7 +92,7 @@ impl HttpRequest<()> { version: Version, headers: HeaderMap, payload: Option) -> HttpRequest { HttpRequest( - Rc::new(HttpMessage { + SharedHttpMessage::from_message(HttpMessage { method: method, uri: uri, version: version, @@ -98,6 +109,10 @@ impl HttpRequest<()> { ) } + pub(crate) fn from_message(msg: SharedHttpMessage) -> HttpRequest { + HttpRequest(msg, None, None) + } + /// Construct a new Request. #[inline] #[cfg(test)] @@ -106,7 +121,7 @@ impl HttpRequest<()> { use std::str::FromStr; HttpRequest( - Rc::new(HttpMessage { + SharedHttpMessage::from_message(HttpMessage { method: Method::GET, uri: Uri::from_str(path).unwrap(), version: Version::HTTP_11, @@ -133,7 +148,7 @@ impl HttpRequest { /// Construct new http request without state. pub fn clone_without_state(&self) -> HttpRequest { - HttpRequest(Rc::clone(&self.0), None, None) + HttpRequest(self.0.clone(), None, None) } // get mutable reference for inner message @@ -142,10 +157,15 @@ impl HttpRequest { #[allow(mutable_transmutes)] #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))] fn as_mut(&self) -> &mut HttpMessage { - let r: &HttpMessage = self.0.as_ref(); - unsafe{mem::transmute(r)} + self.0.get_mut() } + #[inline] + fn as_ref(&self) -> &HttpMessage { + self.0.get_ref() + } + + #[inline] pub(crate) fn get_inner(&mut self) -> &mut HttpMessage { self.as_mut() } @@ -173,22 +193,22 @@ impl HttpRequest { /// Read the Request Uri. #[inline] - pub fn uri(&self) -> &Uri { &self.0.uri } + pub fn uri(&self) -> &Uri { &self.as_ref().uri } /// Read the Request method. #[inline] - pub fn method(&self) -> &Method { &self.0.method } + pub fn method(&self) -> &Method { &self.as_ref().method } /// Read the Request Version. #[inline] pub fn version(&self) -> Version { - self.0.version + self.as_ref().version } /// Read the Request Headers. #[inline] pub fn headers(&self) -> &HeaderMap { - &self.0.headers + &self.as_ref().headers } #[doc(hidden)] @@ -200,17 +220,17 @@ impl HttpRequest { /// The target path of this Request. #[inline] pub fn path(&self) -> &str { - self.0.uri.path() + self.as_ref().uri.path() } /// Get *ConnectionInfo* for currect request. pub fn connection_info(&self) -> &ConnectionInfo { - if self.0.info.is_none() { + if self.as_ref().info.is_none() { let info: ConnectionInfo<'static> = unsafe{ mem::transmute(ConnectionInfo::new(self))}; self.as_mut().info = Some(info); } - self.0.info.as_ref().unwrap() + self.as_ref().info.as_ref().unwrap() } pub fn url_for(&self, name: &str, elements: U) -> Result @@ -237,7 +257,7 @@ impl HttpRequest { #[inline] pub fn peer_addr(&self) -> Option<&SocketAddr> { - self.0.addr.as_ref() + self.as_ref().addr.as_ref() } #[inline] @@ -248,7 +268,7 @@ impl HttpRequest { /// Return a new iterator that yields pairs of `Cow` for query parameters pub fn query(&self) -> HashMap { let mut q: HashMap = HashMap::new(); - if let Some(query) = self.0.uri.query().as_ref() { + if let Some(query) = self.as_ref().uri.query().as_ref() { for (key, val) in form_urlencoded::parse(query.as_ref()) { q.insert(key.to_string(), val.to_string()); } @@ -261,7 +281,7 @@ impl HttpRequest { /// E.g., id=10 #[inline] pub fn query_string(&self) -> &str { - if let Some(query) = self.0.uri.query().as_ref() { + if let Some(query) = self.as_ref().uri.query().as_ref() { query } else { "" @@ -271,7 +291,7 @@ impl HttpRequest { /// Load request cookies. #[inline] pub fn cookies(&self) -> Result<&Vec>, CookieParseError> { - if self.0.cookies.is_none() { + if self.as_ref().cookies.is_none() { let msg = self.as_mut(); let mut cookies = Vec::new(); if let Some(val) = msg.headers.get(header::COOKIE) { @@ -283,7 +303,7 @@ impl HttpRequest { } msg.cookies = Some(cookies) } - Ok(self.0.cookies.as_ref().unwrap()) + Ok(self.as_ref().cookies.as_ref().unwrap()) } /// Return request cookie. @@ -304,7 +324,7 @@ impl HttpRequest { /// for matching storing that segment of the request url in the Params object. #[inline] pub fn match_info(&self) -> &Params { - unsafe{ mem::transmute(&self.0.params) } + unsafe{ mem::transmute(&self.as_ref().params) } } /// Set request Params. @@ -315,25 +335,25 @@ impl HttpRequest { /// Checks if a connection should be kept alive. pub fn keep_alive(&self) -> bool { - if let Some(conn) = self.0.headers.get(header::CONNECTION) { + if let Some(conn) = self.headers().get(header::CONNECTION) { if let Ok(conn) = conn.to_str() { - if self.0.version == Version::HTTP_10 && conn.contains("keep-alive") { + if self.as_ref().version == Version::HTTP_10 && conn.contains("keep-alive") { true } else { - self.0.version == Version::HTTP_11 && + self.as_ref().version == Version::HTTP_11 && !(conn.contains("close") || conn.contains("upgrade")) } } else { false } } else { - self.0.version != Version::HTTP_10 + self.as_ref().version != Version::HTTP_10 } } /// Read the request content type pub fn content_type(&self) -> &str { - if let Some(content_type) = self.0.headers.get(header::CONTENT_TYPE) { + if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { if let Ok(content_type) = content_type.to_str() { return content_type } @@ -343,17 +363,17 @@ impl HttpRequest { /// Check if request requires connection upgrade pub(crate) fn upgrade(&self) -> bool { - if let Some(conn) = self.0.headers.get(header::CONNECTION) { + if let Some(conn) = self.as_ref().headers.get(header::CONNECTION) { if let Ok(s) = conn.to_str() { return s.to_lowercase().contains("upgrade") } } - self.0.method == Method::CONNECT + self.as_ref().method == Method::CONNECT } /// Check if request has chunked transfer encoding pub fn chunked(&self) -> Result { - if let Some(encodings) = self.0.headers.get(header::TRANSFER_ENCODING) { + if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) { if let Ok(s) = encodings.to_str() { Ok(s.to_lowercase().contains("chunked")) } else { @@ -367,7 +387,7 @@ impl HttpRequest { /// Parses Range HTTP header string as per RFC 2616. /// `size` is full size of response (file). pub fn range(&self, size: u64) -> Result, HttpRangeError> { - if let Some(range) = self.0.headers.get(header::RANGE) { + if let Some(range) = self.headers().get(header::RANGE) { HttpRange::parse(unsafe{str::from_utf8_unchecked(range.as_bytes())}, size) .map_err(|e| e.into()) } else { @@ -378,7 +398,7 @@ impl HttpRequest { /// Returns reference to the associated http payload. #[inline] pub fn payload(&self) -> Option<&Payload> { - self.0.payload.as_ref() + self.as_ref().payload.as_ref() } /// Returns mutable reference to the associated http payload. @@ -397,7 +417,7 @@ impl HttpRequest { /// /// Content-type: multipart/form-data; pub fn multipart(&mut self) -> Result { - let boundary = Multipart::boundary(&self.0.headers)?; + let boundary = Multipart::boundary(self.headers())?; if let Some(payload) = self.take_payload() { Ok(Multipart::new(boundary, payload)) } else { @@ -434,7 +454,7 @@ impl HttpRequest { } // check content type - let t = if let Some(content_type) = self.0.headers.get(header::CONTENT_TYPE) { + let t = if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { if let Ok(content_type) = content_type.to_str() { content_type.to_lowercase() == "application/x-www-form-urlencoded" } else { @@ -460,29 +480,29 @@ impl Default for HttpRequest<()> { /// Construct default request fn default() -> HttpRequest { - HttpRequest(Rc::new(HttpMessage::default()), None, None) + HttpRequest(SharedHttpMessage::default(), None, None) } } impl Clone for HttpRequest { fn clone(&self) -> HttpRequest { - HttpRequest(Rc::clone(&self.0), self.1.clone(), None) + HttpRequest(self.0.clone(), self.1.clone(), None) } } impl fmt::Debug for HttpRequest { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let res = write!(f, "\nHttpRequest {:?} {}:{}\n", - self.0.version, self.0.method, self.0.uri); + self.as_ref().version, self.as_ref().method, self.as_ref().uri); if !self.query_string().is_empty() { let _ = write!(f, " query: ?{:?}\n", self.query_string()); } if !self.match_info().is_empty() { - let _ = write!(f, " params: {:?}\n", self.0.params); + let _ = write!(f, " params: {:?}\n", self.as_ref().params); } let _ = write!(f, " headers:\n"); - for key in self.0.headers.keys() { - let vals: Vec<_> = self.0.headers.get_all(key).iter().collect(); + for key in self.as_ref().headers.keys() { + let vals: Vec<_> = self.as_ref().headers.get_all(key).iter().collect(); if vals.len() > 1 { let _ = write!(f, " {:?}: {:?}\n", key, vals); } else { diff --git a/src/param.rs b/src/param.rs index b948ac187..5aaa4f849 100644 --- a/src/param.rs +++ b/src/param.rs @@ -30,6 +30,10 @@ impl<'a> Default for Params<'a> { impl<'a> Params<'a> { + pub(crate) fn clear(&mut self) { + self.0.clear(); + } + pub(crate) fn add(&mut self, name: &'a str, value: &'a str) { self.0.push((name, value)); } diff --git a/src/pipeline.rs b/src/pipeline.rs index 9c7aa60d4..48f80c13b 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -755,16 +755,18 @@ impl ProcessResponse { } } - // flush io - match io.poll_complete() { - Ok(Async::Ready(_)) => - self.running.resume(), - Ok(Async::NotReady) => - return Err(PipelineState::Response(self)), - Err(err) => { - debug!("Error sending data: {}", err); - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init(info, self.resp)) + // flush io but only if we need to + if self.running == RunningState::Paused || !self.drain.0.is_empty() { + match io.poll_completed() { + Ok(Async::Ready(_)) => + self.running.resume(), + Ok(Async::NotReady) => + return Err(PipelineState::Response(self)), + Err(err) => { + debug!("Error sending data: {}", err); + info.error = Some(err.into()); + return Ok(FinishingMiddlewares::init(info, self.resp)) + } } } diff --git a/src/server.rs b/src/server.rs index bb9552a94..bf85b2ac7 100644 --- a/src/server.rs +++ b/src/server.rs @@ -413,6 +413,8 @@ pub(crate) struct WorkerSettings { h: Vec, enabled: bool, keep_alive: u64, + bytes: Rc, + messages: Rc, } impl WorkerSettings { @@ -421,6 +423,8 @@ impl WorkerSettings { h: h, enabled: if let Some(ka) = keep_alive { ka > 0 } else { false }, keep_alive: keep_alive.unwrap_or(0), + bytes: Rc::new(helpers::SharedBytesPool::new()), + messages: Rc::new(helpers::SharedMessagePool::new()), } } @@ -433,6 +437,12 @@ impl WorkerSettings { pub fn keep_alive_enabled(&self) -> bool { self.enabled } + pub fn get_shared_bytes(&self) -> helpers::SharedBytes { + helpers::SharedBytes::new(self.bytes.get_bytes(), Rc::clone(&self.bytes)) + } + pub fn get_http_message(&self) -> helpers::SharedHttpMessage { + helpers::SharedHttpMessage::new(self.messages.get(), Rc::clone(&self.messages)) + } } impl Worker { From a2dff8a0b9b75daf8d0aa47c2b508b7f9ca7b692 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 14 Dec 2017 20:12:28 -0800 Subject: [PATCH 097/279] update readme --- README.md | 31 +++++++++++++++++++++++++++++++ guide/src/qs_1.md | 2 +- guide/src/qs_3.md | 2 +- guide/src/qs_3_5.md | 6 +++--- guide/src/qs_4.md | 5 ++--- 5 files changed, 38 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7933dd19a..2715da10c 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,37 @@ and tls connection. [rfc section 3.4](https://http2.github.io/http2-spec/#rfc.se [tls example](https://github.com/actix/actix-web/tree/master/examples/tls) +## Benchmarks + +This is totally unscientific and probably pretty useless. In real world business +logic would dominate on performance side. But in any case. i took several web frameworks +for rust and used theirs *hello world* example. All projects are compiled with +`--release` parameter. I didnt test single thread performance for iron and rocket. +As a testing tool i used `wrk` and following commands + +`wrk -t20 -c100 -d10s http://127.0.0.1:8080/` + +`wrk -t20 -c100 -d10s http://127.0.0.1:8080/ -s ./pipeline.lua --latency -- / 128` + +I ran all tests on localhost on MacBook Pro late 2017. It has 4 cpu and 8 logical cpus. +Each result is best of five runs. All measurements are req/sec. + + Name | 1 thread | 1 pipeline | 3 thread | 3 pipeline | 8 thread | 8 pipeline | +--- | --- | --- | --- | --- | --- | +Actix | 81400 | 710200 | 121000 | 1684000 | 106300 | 2206000 | +Gotham | 61000 | 178000 | | | | | +Iron | | | | | 94500 | 78000 | +Rocket | | | | | 95500 | failed | +Shio | 71800 | 317800 | | | | | +tokio-minihttp | 106900 | 1047000 | | | | | + +Some notes on results. Iron and Rocket got tested with 8 threads, +which showed best results. Gothan and tokio-minihttp seem does not support +multithreading, or at least i couldn't figured out. I manually enabled pipelining +for *Shio* and Gotham*. While shio seems support multithreading, but it showed +absolutly same results for any how number of threads (maybe macos?) +Rocket completely failed in pipelined tests. + ## Examples * [Basic](https://github.com/actix/actix-web/tree/master/examples/basic.rs) diff --git a/guide/src/qs_1.md b/guide/src/qs_1.md index 09e83b586..cfef105e2 100644 --- a/guide/src/qs_1.md +++ b/guide/src/qs_1.md @@ -1,4 +1,4 @@ -# Quickstart +# Quick start Before you can start writing a actix web application, you’ll need a version of Rust installed. We recommend you use rustup to install or configure such a version. diff --git a/guide/src/qs_3.md b/guide/src/qs_3.md index 909ba2922..40c6dfb95 100644 --- a/guide/src/qs_3.md +++ b/guide/src/qs_3.md @@ -6,7 +6,7 @@ websocket protcol handling, multipart streams, etc. All actix web server is built around `Application` instance. It is used for registering routes for resources, middlewares. -Also it stores application specific state that is shared accross all handlers +Also it stores application specific state that is shared across all handlers within same application. Application acts as namespace for all routes, i.e all routes for specific application diff --git a/guide/src/qs_3_5.md b/guide/src/qs_3_5.md index 5bb06fdf4..36f843fb5 100644 --- a/guide/src/qs_3_5.md +++ b/guide/src/qs_3_5.md @@ -5,7 +5,7 @@ Http server automatically starts number of http workers, by default this number is equal to number of logical cpu in the system. This number -could be overriden with `HttpServer::threads()` method. +could be overridden with `HttpServer::threads()` method. ```rust # extern crate actix_web; @@ -53,7 +53,7 @@ fn main() { } ``` -Note on *HTTP/2.0* protocol over tls without prior knowlage, it requires +Note on *HTTP/2.0* protocol over tls without prior knowledge, it requires [tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only `openssl` has `alpn ` support. @@ -62,7 +62,7 @@ for concrete example. ## Keep-Alive -Actix can wait for requesta on a keep-alive connection. *Keep alive* +Actix can wait for requests on a keep-alive connection. *Keep alive* connection behavior is defined by server settings. * `Some(75)` - enable 75 sec *keep alive* timer according request and response settings. diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index 53b58c770..077c71fca 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -41,9 +41,8 @@ fn index(req: HttpRequest) -> Box> { ## Response with custom type -To return custom type directly from handler function `FromResponse` trait should be -implemented for this type. Let's create response for custom type that -serializes to `application/json` response: +To return custom type directly from handler function type needs to implement `Responder` trait. +Let's create response for custom type that serializes to `application/json` response: ```rust # extern crate actix; From 2b0994e448b4175d57a0ab6ff3dfbed26120d238 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 14 Dec 2017 20:29:49 -0800 Subject: [PATCH 098/279] update tests --- README.md | 14 ++++---- src/application.rs | 2 +- src/h1.rs | 82 ++++++++++++++++++++++++++++------------------ src/server.rs | 2 +- 4 files changed, 59 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 2715da10c..fbc08be60 100644 --- a/README.md +++ b/README.md @@ -74,14 +74,14 @@ As a testing tool i used `wrk` and following commands I ran all tests on localhost on MacBook Pro late 2017. It has 4 cpu and 8 logical cpus. Each result is best of five runs. All measurements are req/sec. - Name | 1 thread | 1 pipeline | 3 thread | 3 pipeline | 8 thread | 8 pipeline | ---- | --- | --- | --- | --- | --- | -Actix | 81400 | 710200 | 121000 | 1684000 | 106300 | 2206000 | -Gotham | 61000 | 178000 | | | | | -Iron | | | | | 94500 | 78000 | -Rocket | | | | | 95500 | failed | +Name | 1 thread | 1 pipeline | 3 thread | 3 pipeline | 8 thread | 8 pipeline +---- | -------- | ---------- | -------- | ---------- | -------- | ---------- +Actix | 81400 | 710200 | 121000 | 1684000 | 106300 | 2206000 +Gotham | 61000 | 178000 | | | | +Iron | | | | | 94500 | 78000 +Rocket | | | | | 95500 | failed Shio | 71800 | 317800 | | | | | -tokio-minihttp | 106900 | 1047000 | | | | | +tokio-minihttp | 106900 | 1047000 | | | | Some notes on results. Iron and Rocket got tested with 8 threads, which showed best results. Gothan and tokio-minihttp seem does not support diff --git a/src/application.rs b/src/application.rs index b714067e2..381ecac9e 100644 --- a/src/application.rs +++ b/src/application.rs @@ -11,7 +11,7 @@ use middlewares::Middleware; use server::ServerSettings; /// Application -pub struct HttpApplication { +pub struct HttpApplication { state: Rc, prefix: String, default: Resource, diff --git a/src/h1.rs b/src/h1.rs index 8343a99d2..d326b19e6 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -959,6 +959,8 @@ mod tests { use tokio_io::AsyncRead; use http::{Version, Method}; use super::*; + use application::HttpApplication; + use server::WorkerSettings; struct Buffer { buf: Bytes, @@ -1006,13 +1008,14 @@ mod tests { } macro_rules! parse_ready { - ($e:expr) => ( - match Reader::new().parse($e, &mut BytesMut::new()) { + ($e:expr) => ({ + let settings = WorkerSettings::::new(Vec::new(), None); + match Reader::new().parse($e, &mut BytesMut::new(), &settings) { Ok(Async::Ready(Item::Http1(req))) => req, Ok(_) => panic!("Eof during parsing http request"), Err(err) => panic!("Error during parsing http request: {:?}", err), } - ) + }) } macro_rules! reader_parse_ready { @@ -1028,7 +1031,9 @@ mod tests { macro_rules! expect_parse_err { ($e:expr) => ({ let mut buf = BytesMut::new(); - match Reader::new().parse($e, &mut buf) { + let settings = WorkerSettings::::new(Vec::new(), None); + + match Reader::new().parse($e, &mut buf, &settings) { Err(err) => match err { ReaderError::Error(_) => (), _ => panic!("Parse error expected"), @@ -1044,9 +1049,10 @@ mod tests { fn test_parse() { let mut buf = Buffer::new("GET /test HTTP/1.1\r\n\r\n"); let mut readbuf = BytesMut::new(); + let settings = WorkerSettings::::new(Vec::new(), None); let mut reader = Reader::new(); - match reader.parse(&mut buf, &mut readbuf) { + match reader.parse(&mut buf, &mut readbuf, &settings) { Ok(Async::Ready(Item::Http1(req))) => { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); @@ -1061,15 +1067,16 @@ mod tests { fn test_parse_partial() { let mut buf = Buffer::new("PUT /test HTTP/1"); let mut readbuf = BytesMut::new(); + let settings = WorkerSettings::::new(Vec::new(), None); let mut reader = Reader::new(); - match reader.parse(&mut buf, &mut readbuf) { + match reader.parse(&mut buf, &mut readbuf, &settings) { Ok(Async::NotReady) => (), _ => panic!("Error"), } buf.feed_data(".1\r\n\r\n"); - match reader.parse(&mut buf, &mut readbuf) { + match reader.parse(&mut buf, &mut readbuf, &settings) { Ok(Async::Ready(Item::Http1(req))) => { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::PUT); @@ -1084,9 +1091,10 @@ mod tests { fn test_parse_post() { let mut buf = Buffer::new("POST /test2 HTTP/1.0\r\n\r\n"); let mut readbuf = BytesMut::new(); + let settings = WorkerSettings::::new(Vec::new(), None); let mut reader = Reader::new(); - match reader.parse(&mut buf, &mut readbuf) { + match reader.parse(&mut buf, &mut readbuf, &settings) { Ok(Async::Ready(Item::Http1(req))) => { assert_eq!(req.version(), Version::HTTP_10); assert_eq!(*req.method(), Method::POST); @@ -1101,9 +1109,10 @@ mod tests { fn test_parse_body() { let mut buf = Buffer::new("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); let mut readbuf = BytesMut::new(); + let settings = WorkerSettings::::new(Vec::new(), None); let mut reader = Reader::new(); - match reader.parse(&mut buf, &mut readbuf) { + match reader.parse(&mut buf, &mut readbuf, &settings) { Ok(Async::Ready(Item::Http1(mut req))) => { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); @@ -1119,9 +1128,10 @@ mod tests { let mut buf = Buffer::new( "\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); let mut readbuf = BytesMut::new(); + let settings = WorkerSettings::::new(Vec::new(), None); let mut reader = Reader::new(); - match reader.parse(&mut buf, &mut readbuf) { + match reader.parse(&mut buf, &mut readbuf, &settings) { Ok(Async::Ready(Item::Http1(mut req))) => { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); @@ -1136,12 +1146,13 @@ mod tests { fn test_parse_partial_eof() { let mut buf = Buffer::new("GET /test HTTP/1.1\r\n"); let mut readbuf = BytesMut::new(); + let settings = WorkerSettings::::new(Vec::new(), None); let mut reader = Reader::new(); - not_ready!{ reader.parse(&mut buf, &mut readbuf) } + not_ready!{ reader.parse(&mut buf, &mut readbuf, &settings) } buf.feed_data("\r\n"); - match reader.parse(&mut buf, &mut readbuf) { + match reader.parse(&mut buf, &mut readbuf, &settings) { Ok(Async::Ready(Item::Http1(req))) => { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); @@ -1156,18 +1167,19 @@ mod tests { fn test_headers_split_field() { let mut buf = Buffer::new("GET /test HTTP/1.1\r\n"); let mut readbuf = BytesMut::new(); + let settings = WorkerSettings::::new(Vec::new(), None); let mut reader = Reader::new(); - not_ready!{ reader.parse(&mut buf, &mut readbuf) } + not_ready!{ reader.parse(&mut buf, &mut readbuf, &settings) } buf.feed_data("t"); - not_ready!{ reader.parse(&mut buf, &mut readbuf) } + not_ready!{ reader.parse(&mut buf, &mut readbuf, &settings) } buf.feed_data("es"); - not_ready!{ reader.parse(&mut buf, &mut readbuf) } + not_ready!{ reader.parse(&mut buf, &mut readbuf, &settings) } buf.feed_data("t: value\r\n\r\n"); - match reader.parse(&mut buf, &mut readbuf) { + match reader.parse(&mut buf, &mut readbuf, &settings) { Ok(Async::Ready(Item::Http1(req))) => { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); @@ -1186,9 +1198,10 @@ mod tests { Set-Cookie: c1=cookie1\r\n\ Set-Cookie: c2=cookie2\r\n\r\n"); let mut readbuf = BytesMut::new(); + let settings = WorkerSettings::::new(Vec::new(), None); let mut reader = Reader::new(); - match reader.parse(&mut buf, &mut readbuf) { + match reader.parse(&mut buf, &mut readbuf, &settings) { Ok(Async::Ready(Item::Http1(req))) => { let val: Vec<_> = req.headers().get_all("Set-Cookie") .iter().map(|v| v.to_str().unwrap().to_owned()).collect(); @@ -1420,14 +1433,15 @@ mod tests { "GET /test HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n"); let mut readbuf = BytesMut::new(); + let settings = WorkerSettings::::new(Vec::new(), None); let mut reader = Reader::new(); - let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf)); + let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert!(req.chunked().unwrap()); assert!(!req.payload().unwrap().eof()); buf.feed_data("4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); - not_ready!(reader.parse(&mut buf, &mut readbuf)); + not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert!(!req.payload().unwrap().eof()); assert_eq!(req.payload_mut().unwrap().readall().unwrap().as_ref(), b"dataline"); assert!(req.payload().unwrap().eof()); @@ -1439,10 +1453,11 @@ mod tests { "GET /test HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n"); let mut readbuf = BytesMut::new(); + let settings = WorkerSettings::::new(Vec::new(), None); let mut reader = Reader::new(); - let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf)); + let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert!(req.chunked().unwrap()); assert!(!req.payload().unwrap().eof()); @@ -1451,7 +1466,7 @@ mod tests { POST /test2 HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n"); - let req2 = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf)); + let req2 = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert_eq!(*req2.method(), Method::POST); assert!(req2.chunked().unwrap()); assert!(!req2.payload().unwrap().eof()); @@ -1466,28 +1481,29 @@ mod tests { "GET /test HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n"); let mut readbuf = BytesMut::new(); + let settings = WorkerSettings::::new(Vec::new(), None); let mut reader = Reader::new(); - let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf)); + let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert!(req.chunked().unwrap()); assert!(!req.payload().unwrap().eof()); buf.feed_data("4\r\ndata\r"); - not_ready!(reader.parse(&mut buf, &mut readbuf)); + not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); buf.feed_data("\n4"); - not_ready!(reader.parse(&mut buf, &mut readbuf)); + not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); buf.feed_data("\r"); - not_ready!(reader.parse(&mut buf, &mut readbuf)); + not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); buf.feed_data("\n"); - not_ready!(reader.parse(&mut buf, &mut readbuf)); + not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); buf.feed_data("li"); - not_ready!(reader.parse(&mut buf, &mut readbuf)); + not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); buf.feed_data("ne\r\n0\r\n"); - not_ready!(reader.parse(&mut buf, &mut readbuf)); + not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); //buf.feed_data("test: test\r\n"); //not_ready!(reader.parse(&mut buf, &mut readbuf)); @@ -1496,7 +1512,7 @@ mod tests { assert!(!req.payload().unwrap().eof()); buf.feed_data("\r\n"); - not_ready!(reader.parse(&mut buf, &mut readbuf)); + not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert!(req.payload().unwrap().eof()); } @@ -1506,14 +1522,15 @@ mod tests { "GET /test HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n"); let mut readbuf = BytesMut::new(); + let settings = WorkerSettings::::new(Vec::new(), None); let mut reader = Reader::new(); - let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf)); + let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert!(req.chunked().unwrap()); assert!(!req.payload().unwrap().eof()); buf.feed_data("4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") - not_ready!(reader.parse(&mut buf, &mut readbuf)); + not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert!(!req.payload().unwrap().eof()); assert_eq!(req.payload_mut().unwrap().readall().unwrap().as_ref(), b"dataline"); assert!(req.payload().unwrap().eof()); @@ -1540,9 +1557,10 @@ mod tests { fn test_http2_prefix() { let mut buf = Buffer::new("PRI * HTTP/2.0\r\n\r\n"); let mut readbuf = BytesMut::new(); + let settings = WorkerSettings::::new(Vec::new(), None); let mut reader = Reader::new(); - match reader.parse(&mut buf, &mut readbuf) { + match reader.parse(&mut buf, &mut readbuf, &settings) { Ok(Async::Ready(Item::Http2)) => (), Ok(_) | Err(_) => panic!("Error during parsing http request"), } diff --git a/src/server.rs b/src/server.rs index bf85b2ac7..363a268ac 100644 --- a/src/server.rs +++ b/src/server.rs @@ -418,7 +418,7 @@ pub(crate) struct WorkerSettings { } impl WorkerSettings { - fn new(h: Vec, keep_alive: Option) -> WorkerSettings { + pub(crate) fn new(h: Vec, keep_alive: Option) -> WorkerSettings { WorkerSettings { h: h, enabled: if let Some(ka) = keep_alive { ka > 0 } else { false }, From 106f43e874360bdb9f0afd187a027b27c5b4aea1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 14 Dec 2017 20:48:31 -0800 Subject: [PATCH 099/279] better SharedBytes usage for h2 --- README.md | 12 ++++++------ src/encoding.rs | 2 +- src/h2.rs | 14 +++++++++----- src/h2writer.rs | 8 +++++--- src/helpers.rs | 4 ++++ src/httprequest.rs | 1 - 6 files changed, 25 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index fbc08be60..edade3a03 100644 --- a/README.md +++ b/README.md @@ -76,12 +76,12 @@ Each result is best of five runs. All measurements are req/sec. Name | 1 thread | 1 pipeline | 3 thread | 3 pipeline | 8 thread | 8 pipeline ---- | -------- | ---------- | -------- | ---------- | -------- | ---------- -Actix | 81400 | 710200 | 121000 | 1684000 | 106300 | 2206000 -Gotham | 61000 | 178000 | | | | -Iron | | | | | 94500 | 78000 -Rocket | | | | | 95500 | failed -Shio | 71800 | 317800 | | | | | -tokio-minihttp | 106900 | 1047000 | | | | +Actix | 81.400 | 710.200 | 121.000 | 1.684.000 | 106.300 | 2.206.000 +Gotham | 61..000 | 178.000 | | | | +Iron | | | | | 94.500 | 78.000 +Rocket | | | | | 95.500 | failed +Shio | 71.800 | 317.800 | | | | | +tokio-minihttp | 106.900 | 1.047.000 | | | | Some notes on results. Iron and Rocket got tested with 8 threads, which showed best results. Gothan and tokio-minihttp seem does not support diff --git a/src/encoding.rs b/src/encoding.rs index 7918e20d9..b632a1a3c 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -569,7 +569,7 @@ impl ContentEncoder { #[inline(always)] pub fn write_eof(&mut self) -> Result<(), io::Error> { let encoder = mem::replace( - self, ContentEncoder::Identity(TransferEncoding::eof(SharedBytes::default()))); + self, ContentEncoder::Identity(TransferEncoding::eof(SharedBytes::empty()))); match encoder { ContentEncoder::Br(encoder) => { diff --git a/src/h2.rs b/src/h2.rs index 4b9b6c1d4..5a3e81ac2 100644 --- a/src/h2.rs +++ b/src/h2.rs @@ -246,11 +246,15 @@ impl Entry { // Payload and Content-Encoding let (psender, payload) = Payload::new(false); - let mut req = HttpRequest::new( - parts.method, parts.uri, parts.version, parts.headers, Some(payload)); + let msg = settings.get_http_message(); + msg.get_mut().uri = parts.uri; + msg.get_mut().method = parts.method; + msg.get_mut().version = parts.version; + msg.get_mut().headers = parts.headers; + msg.get_mut().payload = Some(payload); + msg.get_mut().addr = addr; - // set remote addr - req.set_peer_addr(addr); + let mut req = HttpRequest::from_message(msg); // Payload sender let psender = PayloadType::new(req.headers(), psender); @@ -270,7 +274,7 @@ impl Entry { Entry {task: task.unwrap_or_else(|| Pipeline::error(HTTPNotFound)), payload: psender, recv: recv, - stream: H2Writer::new(resp), + stream: H2Writer::new(resp, settings.get_shared_bytes()), flags: EntryFlags::empty(), capacity: 0, } diff --git a/src/h2writer.rs b/src/h2writer.rs index e022432d7..1c19010c1 100644 --- a/src/h2writer.rs +++ b/src/h2writer.rs @@ -31,17 +31,19 @@ pub(crate) struct H2Writer { encoder: PayloadEncoder, flags: Flags, written: u64, + buffer: SharedBytes, } impl H2Writer { - pub fn new(respond: Respond) -> H2Writer { + pub fn new(respond: Respond, buf: SharedBytes) -> H2Writer { H2Writer { respond: respond, stream: None, - encoder: PayloadEncoder::empty(SharedBytes::default()), + encoder: PayloadEncoder::empty(buf.clone()), flags: Flags::empty(), written: 0, + buffer: buf, } } @@ -116,7 +118,7 @@ impl Writer for H2Writer { // prepare response self.flags.insert(Flags::STARTED); - self.encoder = PayloadEncoder::new(SharedBytes::default(), req, msg); + self.encoder = PayloadEncoder::new(self.buffer.clone(), req, msg); if let Body::Empty = *msg.body() { self.flags.insert(Flags::EOF); } diff --git a/src/helpers.rs b/src/helpers.rs index e7733a1ec..57ecb70d0 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -99,6 +99,10 @@ impl Drop for SharedBytes { impl SharedBytes { + pub fn empty() -> Self { + SharedBytes(None, None) + } + pub fn new(bytes: Rc, pool: Rc) -> SharedBytes { SharedBytes(Some(bytes), Some(pool)) } diff --git a/src/httprequest.rs b/src/httprequest.rs index b2adeb03a..b06740ebb 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -154,7 +154,6 @@ impl HttpRequest { // get mutable reference for inner message // mutable reference should not be returned as result for request's method #[inline] - #[allow(mutable_transmutes)] #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))] fn as_mut(&self) -> &mut HttpMessage { self.0.get_mut() From d77156c16ce46c1b59c2cd77b2bbfb75412efb84 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 14 Dec 2017 20:49:09 -0800 Subject: [PATCH 100/279] fix readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index edade3a03..74a799c44 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ Each result is best of five runs. All measurements are req/sec. Name | 1 thread | 1 pipeline | 3 thread | 3 pipeline | 8 thread | 8 pipeline ---- | -------- | ---------- | -------- | ---------- | -------- | ---------- Actix | 81.400 | 710.200 | 121.000 | 1.684.000 | 106.300 | 2.206.000 -Gotham | 61..000 | 178.000 | | | | +Gotham | 61.000 | 178.000 | | | | Iron | | | | | 94.500 | 78.000 Rocket | | | | | 95.500 | failed Shio | 71.800 | 317.800 | | | | | From 4913e7d3c2d82ff47ae0bdd7baf2d28cab59bcfd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 14 Dec 2017 22:22:27 -0800 Subject: [PATCH 101/279] cleanup --- README.md | 2 +- src/h1.rs | 9 ++++----- src/h2writer.rs | 3 ++- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 74a799c44..6fbe080a9 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ Each result is best of five runs. All measurements are req/sec. Name | 1 thread | 1 pipeline | 3 thread | 3 pipeline | 8 thread | 8 pipeline ---- | -------- | ---------- | -------- | ---------- | -------- | ---------- -Actix | 81.400 | 710.200 | 121.000 | 1.684.000 | 106.300 | 2.206.000 +Actix | 87.200 | 813.200 | 122.100 | 1.877.000 | 107.400 | 2.390.000 Gotham | 61.000 | 178.000 | | | | Iron | | | | | 94.500 | 78.000 Rocket | | | | | 95.500 | failed diff --git a/src/h1.rs b/src/h1.rs index d326b19e6..7687810c3 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -539,13 +539,12 @@ impl Reader { } // Parse http message - let mut headers_indices = [HeaderIndices { - name: (0, 0), - value: (0, 0) - }; MAX_HEADERS]; + let mut headers_indices: [HeaderIndices; MAX_HEADERS] = + unsafe{std::mem::uninitialized()}; let (len, method, path, version, headers_len) = { - let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS]; + let mut headers: [httparse::Header; MAX_HEADERS] = + unsafe{std::mem::uninitialized()}; let mut req = httparse::Request::new(&mut headers); match try!(req.parse(buf)) { httparse::Status::Complete(len) => { diff --git a/src/h2writer.rs b/src/h2writer.rs index 1c19010c1..5874d6601 100644 --- a/src/h2writer.rs +++ b/src/h2writer.rs @@ -131,7 +131,8 @@ impl Writer for H2Writer { if !msg.headers().contains_key(DATE) { let mut bytes = BytesMut::with_capacity(29); helpers::date(&mut bytes); - msg.headers_mut().insert(DATE, HeaderValue::try_from(&bytes[..]).unwrap()); + msg.headers_mut().insert( + DATE, HeaderValue::try_from(bytes.freeze()).unwrap()); } let mut resp = Response::new(()); From 71c37bde5aed843ca81c8a1f44a042f7c6d5bd46 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 15 Dec 2017 05:44:10 -0800 Subject: [PATCH 102/279] Update README.md --- README.md | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/README.md b/README.md index 6fbe080a9..c6ceee9c3 100644 --- a/README.md +++ b/README.md @@ -37,28 +37,6 @@ fn main() { * Middlewares (Logger, Session, DefaultHeaders) * Built on top of [Actix](https://github.com/actix/actix). -## HTTP/2 - -Actix web automatically upgrades connection to `http/2` if possible. - -### Negotiation - -`HTTP/2` protocol over tls without prior knowlage requires -[tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only -`rust-openssl` supports alpn. - -```toml -[dependencies] -actix-web = { git = "https://github.com/actix/actix-web", features=["alpn"] } -``` - -Upgrade to `http/2` schema described in -[rfc section 3.2](https://http2.github.io/http2-spec/#rfc.section.3.2) is not supported. -Starting `http/2` with prior knowledge is supported for both clear text connection -and tls connection. [rfc section 3.4](https://http2.github.io/http2-spec/#rfc.section.3.4) - -[tls example](https://github.com/actix/actix-web/tree/master/examples/tls) - ## Benchmarks This is totally unscientific and probably pretty useless. In real world business From c3d5e4301ac53badd70a35becbe9dd5fb9f2c713 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 15 Dec 2017 13:10:12 -0800 Subject: [PATCH 103/279] cleanup h1 parse --- README.md | 4 +- src/h1.rs | 164 +++++++++++++++++++++++++----------------------------- 2 files changed, 78 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index c6ceee9c3..4026830f7 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Each result is best of five runs. All measurements are req/sec. Name | 1 thread | 1 pipeline | 3 thread | 3 pipeline | 8 thread | 8 pipeline ---- | -------- | ---------- | -------- | ---------- | -------- | ---------- -Actix | 87.200 | 813.200 | 122.100 | 1.877.000 | 107.400 | 2.390.000 +Actix | 89.100 | 815.200 | 122.100 | 1.877.000 | 107.400 | 2.350.000 Gotham | 61.000 | 178.000 | | | | Iron | | | | | 94.500 | 78.000 Rocket | | | | | 95.500 | failed @@ -65,7 +65,7 @@ Some notes on results. Iron and Rocket got tested with 8 threads, which showed best results. Gothan and tokio-minihttp seem does not support multithreading, or at least i couldn't figured out. I manually enabled pipelining for *Shio* and Gotham*. While shio seems support multithreading, but it showed -absolutly same results for any how number of threads (maybe macos?) +absolutly same results for any how number of threads (maybe macos problem?) Rocket completely failed in pipelined tests. ## Examples diff --git a/src/h1.rs b/src/h1.rs index 7687810c3..91801e836 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -215,10 +215,10 @@ impl Http1 // read incoming data while !self.flags.contains(Flags::ERROR) && !self.flags.contains(Flags::H2) && - self.tasks.len() < MAX_PIPELINED_MESSAGES { - match self.reader.parse(self.stream.get_mut(), - &mut self.read_buf, &self.settings) - { + self.tasks.len() < MAX_PIPELINED_MESSAGES + { + match self.reader.parse(self.stream.get_mut(), + &mut self.read_buf, &self.settings) { Ok(Async::Ready(Item::Http1(mut req))) => { not_ready = false; @@ -405,77 +405,58 @@ impl Reader { settings: &WorkerSettings) -> Poll where T: AsyncRead { - loop { - match self.decode(buf)? { - Decoding::Paused => return Ok(Async::NotReady), - Decoding::Ready => { - self.payload = None; - break - }, - Decoding::NotReady => { - match self.read_from_io(io, buf) { - Ok(Async::Ready(0)) => { - if let Some(ref mut payload) = self.payload { - payload.tx.set_error(PayloadError::Incomplete); - } - // http channel should not deal with payload errors - return Err(ReaderError::Payload) - } - Ok(Async::Ready(_)) => { - continue - } - Ok(Async::NotReady) => break, - Err(err) => { - if let Some(ref mut payload) = self.payload { - payload.tx.set_error(err.into()); - } - // http channel should not deal with payload errors - return Err(ReaderError::Payload) - } + // read payload + if self.payload.is_some() { + match self.read_from_io(io, buf) { + Ok(Async::Ready(0)) => { + if let Some(ref mut payload) = self.payload { + payload.tx.set_error(PayloadError::Incomplete); } + // http channel should not deal with payload errors + return Err(ReaderError::Payload) + }, + Err(err) => { + if let Some(ref mut payload) = self.payload { + payload.tx.set_error(err.into()); + } + // http channel should not deal with payload errors + return Err(ReaderError::Payload) } + _ => (), + } + match self.decode(buf)? { + Decoding::Ready => self.payload = None, + Decoding::Paused | Decoding::NotReady => return Ok(Async::NotReady), } } + // if buf is empty parse_message will always return NotReady, let's avoid that + let read = if buf.is_empty() { + match self.read_from_io(io, buf) { + Ok(Async::Ready(0)) => { + debug!("Ignored premature client disconnection"); + return Err(ReaderError::Disconnect); + }, + Ok(Async::Ready(_)) => (), + Ok(Async::NotReady) => + return Ok(Async::NotReady), + Err(err) => + return Err(ReaderError::Error(err.into())) + } + false + } else { + true + }; + loop { match Reader::parse_message(buf, settings).map_err(ReaderError::Error)? { Message::Http1(msg, decoder) => { + // process payload if let Some(payload) = decoder { self.payload = Some(payload); - - loop { - match self.decode(buf)? { - Decoding::Paused => - break, - Decoding::Ready => { - self.payload = None; - break - }, - Decoding::NotReady => { - match self.read_from_io(io, buf) { - Ok(Async::Ready(0)) => { - trace!("parse eof"); - if let Some(ref mut payload) = self.payload { - payload.tx.set_error( - PayloadError::Incomplete); - } - // http channel should deal with payload errors - return Err(ReaderError::Payload) - } - Ok(Async::Ready(_)) => { - continue - } - Ok(Async::NotReady) => break, - Err(err) => { - if let Some(ref mut payload) = self.payload { - payload.tx.set_error(err.into()); - } - // http channel should deal with payload errors - return Err(ReaderError::Payload) - } - } - } - } + match self.decode(buf)? { + Decoding::Paused | Decoding::NotReady => (), + Decoding::Ready => self.payload = None, } } self.h1 = true; @@ -489,42 +470,49 @@ impl Reader { }, Message::NotReady => { if buf.capacity() >= MAX_BUFFER_SIZE { - debug!("MAX_BUFFER_SIZE reached, closing"); + error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); return Err(ReaderError::Error(ParseError::TooLarge)); } + if read { + match self.read_from_io(io, buf) { + Ok(Async::Ready(0)) => { + debug!("Ignored premature client disconnection"); + return Err(ReaderError::Disconnect); + }, + Ok(Async::Ready(_)) => (), + Ok(Async::NotReady) => + return Ok(Async::NotReady), + Err(err) => + return Err(ReaderError::Error(err.into())) + } + } else { + return Ok(Async::NotReady) + } }, } - match self.read_from_io(io, buf) { - Ok(Async::Ready(0)) => { - debug!("Ignored premature client disconnection"); - return Err(ReaderError::Disconnect); - }, - Ok(Async::Ready(_)) => (), - Ok(Async::NotReady) => - return Ok(Async::NotReady), - Err(err) => - return Err(ReaderError::Error(err.into())) - } } } fn read_from_io(&mut self, io: &mut T, buf: &mut BytesMut) - -> Poll { - if buf.remaining_mut() < LW_BUFFER_SIZE { - buf.reserve(HW_BUFFER_SIZE); - } + -> Poll + { unsafe { - let n = match io.read(buf.bytes_mut()) { - Ok(n) => n, + if buf.remaining_mut() < LW_BUFFER_SIZE { + buf.reserve(HW_BUFFER_SIZE); + } + match io.read(buf.bytes_mut()) { + Ok(n) => { + buf.advance_mut(n); + Ok(Async::Ready(n)) + }, Err(e) => { if e.kind() == io::ErrorKind::WouldBlock { - return Ok(Async::NotReady); + Ok(Async::NotReady) + } else { + Err(e) } - return Err(e) } - }; - buf.advance_mut(n); - Ok(Async::Ready(n)) + } } } From 1ddcce7b76105a13fb5233b7876ef97684a5eb8e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 15 Dec 2017 16:24:15 -0800 Subject: [PATCH 104/279] hide httpresponse box --- README.md | 2 +- src/context.rs | 4 +- src/handler.rs | 10 +- src/httpresponse.rs | 180 ++++++++++++++++++++++-------- src/middlewares/defaultheaders.rs | 2 +- src/middlewares/mod.rs | 6 +- src/middlewares/session.rs | 8 +- src/pipeline.rs | 42 +++---- 8 files changed, 172 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 4026830f7..fb162fc55 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Each result is best of five runs. All measurements are req/sec. Name | 1 thread | 1 pipeline | 3 thread | 3 pipeline | 8 thread | 8 pipeline ---- | -------- | ---------- | -------- | ---------- | -------- | ---------- -Actix | 89.100 | 815.200 | 122.100 | 1.877.000 | 107.400 | 2.350.000 +Actix | 89.300 | 871.200 | 122.100 | 1.877.000 | 107.400 | 2.560.000 Gotham | 61.000 | 178.000 | | | | Iron | | | | | 94.500 | 78.000 Rocket | | | | | 95.500 | failed diff --git a/src/context.rs b/src/context.rs index f792370f5..c9f770147 100644 --- a/src/context.rs +++ b/src/context.rs @@ -25,7 +25,7 @@ pub(crate) trait IoContext: 'static { #[derive(Debug)] pub(crate) enum Frame { - Message(Box), + Message(HttpResponse), Payload(Option), Drain(Rc>), } @@ -141,7 +141,7 @@ impl HttpContext where A: Actor { Body::StreamingContext | Body::UpgradeContext => self.streaming = true, _ => (), } - self.stream.push_back(Frame::Message(Box::new(resp))) + self.stream.push_back(Frame::Message(resp)) } /// Write payload diff --git a/src/handler.rs b/src/handler.rs index 9d60f1f6d..6ea28dec4 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -54,7 +54,7 @@ impl Handler for F pub struct Reply(ReplyItem); pub(crate) enum ReplyItem { - Message(Box), + Message(HttpResponse), Actor(Box), Future(Box>), } @@ -80,7 +80,7 @@ impl Reply { /// Send response #[inline] pub fn response>(response: R) -> Reply { - Reply(ReplyItem::Message(Box::new(response.into()))) + Reply(ReplyItem::Message(response.into())) } #[inline] @@ -111,14 +111,14 @@ impl Responder for HttpResponse { type Error = Error; fn respond_to(self, _: HttpRequest) -> Result { - Ok(Reply(ReplyItem::Message(Box::new(self)))) + Ok(Reply(ReplyItem::Message(self))) } } impl From for Reply { fn from(resp: HttpResponse) -> Reply { - Reply(ReplyItem::Message(Box::new(resp))) + Reply(ReplyItem::Message(resp)) } } @@ -142,7 +142,7 @@ impl> From> for Reply { fn from(res: Result) -> Self { match res { Ok(val) => val, - Err(err) => Reply(ReplyItem::Message(Box::new(err.into().into()))), + Err(err) => Reply(ReplyItem::Message(err.into().into())), } } } diff --git a/src/httpresponse.rs b/src/httpresponse.rs index a1b26caf9..e98606335 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -1,6 +1,8 @@ //! Pieces pertaining to the HTTP response. use std::{mem, str, fmt}; +use std::cell::RefCell; use std::convert::Into; +use std::collections::VecDeque; use cookie::CookieJar; use bytes::{Bytes, BytesMut}; @@ -27,8 +29,8 @@ pub enum ConnectionType { Upgrade, } -/// An HTTP Response -pub struct HttpResponse { +#[derive(Debug)] +struct InnerHttpResponse { version: Option, headers: HeaderMap, status: StatusCode, @@ -41,22 +43,11 @@ pub struct HttpResponse { error: Option, } -impl HttpResponse { +impl InnerHttpResponse { - /// Create http response builder with specific status. #[inline] - pub fn build(status: StatusCode) -> HttpResponseBuilder { - HttpResponseBuilder { - response: Some(HttpResponse::new(status, Body::Empty)), - err: None, - cookies: None, - } - } - - /// Constructs a response - #[inline] - pub fn new(status: StatusCode, body: Body) -> HttpResponse { - HttpResponse { + fn new(status: StatusCode, body: Body) -> InnerHttpResponse { + InnerHttpResponse { version: None, headers: HeaderMap::with_capacity(8), status: status, @@ -70,81 +61,178 @@ impl HttpResponse { } } +} + +impl Default for InnerHttpResponse { + + fn default() -> InnerHttpResponse { + InnerHttpResponse::new(StatusCode::OK, Body::Empty) + } +} + +/// Internal use only! unsafe +struct Pool(VecDeque>); + +thread_local!(static POOL: RefCell = RefCell::new(Pool::new())); + +impl Pool { + fn new() -> Pool { + Pool(VecDeque::with_capacity(128)) + } + + fn get() -> Box { + POOL.with(|pool| { + if let Some(resp) = pool.borrow_mut().0.pop_front() { + resp + } else { + Box::new(InnerHttpResponse::default()) + } + }) + } + + #[cfg_attr(feature = "cargo-clippy", allow(boxed_local))] + fn release(mut inner: Box) { + POOL.with(|pool| { + if pool.borrow().0.len() < 128 { + inner.version.take(); + inner.headers.clear(); + inner.chunked = false; + inner.reason.take(); + inner.body = Body::Empty; + inner.encoding = ContentEncoding::Auto; + inner.connection_type.take(); + inner.response_size = 0; + inner.error.take(); + pool.borrow_mut().0.push_front(inner); + } + }) + } +} + +/// An HTTP Response +pub struct HttpResponse(Option>); + +impl Drop for HttpResponse { + fn drop(&mut self) { + if let Some(inner) = self.0.take() { + Pool::release(inner) + } + } +} + +impl HttpResponse { + + #[inline(always)] + fn get_ref(&self) -> &InnerHttpResponse { + self.0.as_ref().unwrap() + } + + #[inline(always)] + fn get_mut(&mut self) -> &mut InnerHttpResponse { + self.0.as_mut().unwrap() + } + + #[inline] + fn from_inner(inner: Box) -> HttpResponse { + HttpResponse(Some(inner)) + } + + /// Create http response builder with specific status. + #[inline] + pub fn build(status: StatusCode) -> HttpResponseBuilder { + let mut inner = Pool::get(); + inner.status = status; + HttpResponseBuilder { + response: Some(inner), + err: None, + cookies: None, + } + } + + /// Constructs a response + #[inline] + pub fn new(status: StatusCode, body: Body) -> HttpResponse { + let mut inner = Pool::get(); + inner.status = status; + inner.body = body; + HttpResponse(Some(inner)) + } + /// Constructs a error response #[inline] pub fn from_error(error: Error) -> HttpResponse { let mut resp = error.cause().error_response(); - resp.error = Some(error); + resp.get_mut().error = Some(error); resp } /// The source `error` for this response #[inline] pub fn error(&self) -> Option<&Error> { - self.error.as_ref() + self.get_ref().error.as_ref() } /// Get the HTTP version of this response. #[inline] pub fn version(&self) -> Option { - self.version + self.get_ref().version } /// Get the headers from the response. #[inline] pub fn headers(&self) -> &HeaderMap { - &self.headers + &self.get_ref().headers } /// Get a mutable reference to the headers. #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.headers + &mut self.get_mut().headers } /// Get the status from the server. #[inline] pub fn status(&self) -> StatusCode { - self.status + self.get_ref().status } /// Set the `StatusCode` for this response. #[inline] pub fn status_mut(&mut self) -> &mut StatusCode { - &mut self.status + &mut self.get_mut().status } /// Get custom reason for the response. #[inline] pub fn reason(&self) -> &str { - if let Some(reason) = self.reason { + if let Some(reason) = self.get_ref().reason { reason } else { - self.status.canonical_reason().unwrap_or("") + self.get_ref().status.canonical_reason().unwrap_or("") } } /// Set the custom reason for the response. #[inline] pub fn set_reason(&mut self, reason: &'static str) -> &mut Self { - self.reason = Some(reason); + self.get_mut().reason = Some(reason); self } /// Set connection type pub fn set_connection_type(&mut self, conn: ConnectionType) -> &mut Self { - self.connection_type = Some(conn); + self.get_mut().connection_type = Some(conn); self } /// Connection upgrade status pub fn upgrade(&self) -> bool { - self.connection_type == Some(ConnectionType::Upgrade) + self.get_ref().connection_type == Some(ConnectionType::Upgrade) } /// Keep-alive status for this connection pub fn keep_alive(&self) -> Option { - if let Some(ct) = self.connection_type { + if let Some(ct) = self.get_ref().connection_type { match ct { ConnectionType::KeepAlive => Some(true), ConnectionType::Close | ConnectionType::Upgrade => Some(false), @@ -156,54 +244,55 @@ impl HttpResponse { /// is chunked encoding enabled pub fn chunked(&self) -> bool { - self.chunked + self.get_ref().chunked } /// Content encoding pub fn content_encoding(&self) -> &ContentEncoding { - &self.encoding + &self.get_ref().encoding } /// Set content encoding pub fn set_content_encoding(&mut self, enc: ContentEncoding) -> &mut Self { - self.encoding = enc; + self.get_mut().encoding = enc; self } /// Get body os this response pub fn body(&self) -> &Body { - &self.body + &self.get_ref().body } /// Set a body pub fn set_body>(&mut self, body: B) { - self.body = body.into(); + self.get_mut().body = body.into(); } /// Set a body and return previous body value pub fn replace_body>(&mut self, body: B) -> Body { - mem::replace(&mut self.body, body.into()) + mem::replace(&mut self.get_mut().body, body.into()) } /// Size of response in bytes, excluding HTTP headers pub fn response_size(&self) -> u64 { - self.response_size + self.get_ref().response_size } /// Set content encoding pub(crate) fn set_response_size(&mut self, size: u64) { - self.response_size = size; + self.get_mut().response_size = size; } } impl fmt::Debug for HttpResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let res = write!(f, "\nHttpResponse {:?} {}{}\n", - self.version, self.status, self.reason.unwrap_or("")); - let _ = write!(f, " encoding: {:?}\n", self.encoding); + self.get_ref().version, self.get_ref().status, + self.get_ref().reason.unwrap_or("")); + let _ = write!(f, " encoding: {:?}\n", self.get_ref().encoding); let _ = write!(f, " headers:\n"); - for key in self.headers.keys() { - let vals: Vec<_> = self.headers.get_all(key).iter().collect(); + for key in self.get_ref().headers.keys() { + let vals: Vec<_> = self.get_ref().headers.get_all(key).iter().collect(); if vals.len() > 1 { let _ = write!(f, " {:?}: {:?}\n", key, vals); } else { @@ -220,7 +309,7 @@ impl fmt::Debug for HttpResponse { /// builder-like pattern. #[derive(Debug)] pub struct HttpResponseBuilder { - response: Option, + response: Option>, err: Option, cookies: Option, } @@ -381,7 +470,7 @@ impl HttpResponseBuilder { } } response.body = body.into(); - Ok(response) + Ok(HttpResponse::from_inner(response)) } /// Set a json body and generate `HttpResponse` @@ -406,8 +495,9 @@ impl HttpResponseBuilder { } } -fn parts<'a>(parts: &'a mut Option, err: &Option) - -> Option<&'a mut HttpResponse> +#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))] +fn parts<'a>(parts: &'a mut Option>, err: &Option) + -> Option<&'a mut Box> { if err.is_some() { return None diff --git a/src/middlewares/defaultheaders.rs b/src/middlewares/defaultheaders.rs index 3e9dc278a..6766e1fa4 100644 --- a/src/middlewares/defaultheaders.rs +++ b/src/middlewares/defaultheaders.rs @@ -40,7 +40,7 @@ impl DefaultHeaders { impl Middleware for DefaultHeaders { - fn response(&self, _: &mut HttpRequest, mut resp: Box) -> Response { + fn response(&self, _: &mut HttpRequest, mut resp: HttpResponse) -> Response { for (key, value) in self.headers.iter() { if !resp.headers().contains_key(key) { resp.headers_mut().insert(key, value.clone()); diff --git a/src/middlewares/mod.rs b/src/middlewares/mod.rs index d5d88fc78..b9798c97b 100644 --- a/src/middlewares/mod.rs +++ b/src/middlewares/mod.rs @@ -21,7 +21,7 @@ pub enum Started { Err(Error), /// New http response got generated. If middleware generates response /// handler execution halts. - Response(Box), + Response(HttpResponse), /// Execution completed, runs future to completion. Future(Box, Error=Error>>), } @@ -31,7 +31,7 @@ pub enum Response { /// Moddleware error Err(Error), /// New http response got generated - Done(Box), + Done(HttpResponse), /// Result is a future that resolves to a new http response Future(Box>), } @@ -56,7 +56,7 @@ pub trait Middleware { /// Method is called when handler returns response, /// but before sending http message to peer. - fn response(&self, req: &mut HttpRequest, resp: Box) -> Response { + fn response(&self, req: &mut HttpRequest, resp: HttpResponse) -> Response { Response::Done(resp) } diff --git a/src/middlewares/session.rs b/src/middlewares/session.rs index d38fb0682..a807b0c03 100644 --- a/src/middlewares/session.rs +++ b/src/middlewares/session.rs @@ -107,7 +107,7 @@ impl> Middleware for SessionStorage { Started::Future(Box::new(fut)) } - fn response(&self, req: &mut HttpRequest, resp: Box) -> Response { + fn response(&self, req: &mut HttpRequest, resp: HttpResponse) -> Response { if let Some(s_box) = req.extensions().remove::>() { s_box.0.write(resp) } else { @@ -129,7 +129,7 @@ pub trait SessionImpl: 'static { fn clear(&mut self); /// Write session to storage backend. - fn write(&self, resp: Box) -> Response; + fn write(&self, resp: HttpResponse) -> Response; } /// Session's storage backend trait definition. @@ -155,7 +155,7 @@ impl SessionImpl for DummySessionImpl { fn set(&mut self, key: &str, value: String) {} fn remove(&mut self, key: &str) {} fn clear(&mut self) {} - fn write(&self, resp: Box) -> Response { + fn write(&self, resp: HttpResponse) -> Response { Response::Done(resp) } } @@ -205,7 +205,7 @@ impl SessionImpl for CookieSession { self.state.clear() } - fn write(&self, mut resp: Box) -> Response { + fn write(&self, mut resp: HttpResponse) -> Response { if self.changed { let _ = self.inner.set_cookie(&mut resp, &self.state); } diff --git a/src/pipeline.rs b/src/pipeline.rs index 48f80c13b..2073ff608 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -142,7 +142,7 @@ impl Pipeline<()> { pub fn error>(err: R) -> Box { Box::new(Pipeline( PipelineInfo::new( - HttpRequest::default()), ProcessResponse::init(Box::new(err.into())))) + HttpRequest::default()), ProcessResponse::init(err.into()))) } } @@ -347,15 +347,15 @@ impl StartMiddlewares { fut: Some(fut)}), Ok(Async::Ready(resp)) => { if let Some(resp) = resp { - return RunMiddlewares::init(info, Box::new(resp)); + return RunMiddlewares::init(info, resp); } info.count += 1; } Err(err) => - return ProcessResponse::init(Box::new(err.into())), + return ProcessResponse::init(err.into()), }, Started::Err(err) => - return ProcessResponse::init(Box::new(err.into())), + return ProcessResponse::init(err.into()), } } } @@ -370,7 +370,7 @@ impl StartMiddlewares { Ok(Async::Ready(resp)) => { info.count += 1; if let Some(resp) = resp { - return Ok(RunMiddlewares::init(info, Box::new(resp))); + return Ok(RunMiddlewares::init(info, resp)); } if info.count == len { let reply = (unsafe{&*self.hnd})(info.req.clone()); @@ -388,13 +388,13 @@ impl StartMiddlewares { continue 'outer }, Started::Err(err) => - return Ok(ProcessResponse::init(Box::new(err.into()))) + return Ok(ProcessResponse::init(err.into())) } } } } Err(err) => - return Ok(ProcessResponse::init(Box::new(err.into()))) + return Ok(ProcessResponse::init(err.into())) } } } @@ -442,14 +442,14 @@ impl WaitingResponse { Ok(Async::Ready(None)) => { error!("Unexpected eof"); let err: Error = UnexpectedTaskFrame.into(); - return Ok(ProcessResponse::init(Box::new(err.into()))) + return Ok(ProcessResponse::init(err.into())) }, Ok(Async::NotReady) => { self.stream = PipelineResponse::Context(context); return Err(PipelineState::Handler(self)) }, Err(err) => - return Ok(ProcessResponse::init(Box::new(err.into()))) + return Ok(ProcessResponse::init(err.into())) } } }, @@ -460,9 +460,9 @@ impl WaitingResponse { Err(PipelineState::Handler(self)) } Ok(Async::Ready(response)) => - Ok(RunMiddlewares::init(info, Box::new(response))), + Ok(RunMiddlewares::init(info, response)), Err(err) => - Ok(ProcessResponse::init(Box::new(err.into()))), + Ok(ProcessResponse::init(err.into())), } } PipelineResponse::None => { @@ -482,7 +482,7 @@ struct RunMiddlewares { impl RunMiddlewares { - fn init(info: &mut PipelineInfo, mut resp: Box) -> PipelineState + fn init(info: &mut PipelineInfo, mut resp: HttpResponse) -> PipelineState { if info.count == 0 { return ProcessResponse::init(resp); @@ -494,7 +494,7 @@ impl RunMiddlewares { resp = match info.mws[curr].response(info.req_mut(), resp) { Response::Err(err) => { info.count = curr + 1; - return ProcessResponse::init(Box::new(err.into())) + return ProcessResponse::init(err.into()) } Response::Done(r) => { curr += 1; @@ -522,10 +522,10 @@ impl RunMiddlewares { return Ok(PipelineState::RunMiddlewares(self)), Ok(Async::Ready(resp)) => { self.curr += 1; - Box::new(resp) + resp } Err(err) => - return Ok(ProcessResponse::init(Box::new(err.into()))), + return Ok(ProcessResponse::init(err.into())), }; loop { @@ -534,7 +534,7 @@ impl RunMiddlewares { } else { match info.mws[self.curr].response(info.req_mut(), resp) { Response::Err(err) => - return Ok(ProcessResponse::init(Box::new(err.into()))), + return Ok(ProcessResponse::init(err.into())), Response::Done(r) => { self.curr += 1; resp = r @@ -551,7 +551,7 @@ impl RunMiddlewares { } struct ProcessResponse { - resp: Box, + resp: HttpResponse, iostate: IOState, running: RunningState, drain: DrainVec, @@ -608,7 +608,7 @@ impl Drop for DrainVec { impl ProcessResponse { - fn init(resp: Box) -> PipelineState + fn init(resp: HttpResponse) -> PipelineState { PipelineState::Response( ProcessResponse{ resp: resp, @@ -790,14 +790,14 @@ impl ProcessResponse { /// Middlewares start executor struct FinishingMiddlewares { - resp: Box, + resp: HttpResponse, fut: Option>>, _s: PhantomData, } impl FinishingMiddlewares { - fn init(info: &mut PipelineInfo, resp: Box) -> PipelineState { + fn init(info: &mut PipelineInfo, resp: HttpResponse) -> PipelineState { if info.count == 0 { Completed::init(info) } else { @@ -899,7 +899,7 @@ mod tests { let mut ctx = HttpContext::new(req.clone(), MyActor); let addr: Address<_> = ctx.address(); let mut info = PipelineInfo::new(req); - info.context = Some(Box::new(ctx)); + info.context = Some(ctx); let mut state = Completed::init(&mut info).completed().unwrap(); let st = state.poll(&mut info).ok().unwrap(); From a8b2f1b82127eb6eacc1caf2db00559ee02e20d7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 15 Dec 2017 18:49:11 -0800 Subject: [PATCH 105/279] update tests --- src/middlewares/defaultheaders.rs | 4 ++-- src/pipeline.rs | 2 +- tests/test_server.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/middlewares/defaultheaders.rs b/src/middlewares/defaultheaders.rs index 6766e1fa4..6968c8978 100644 --- a/src/middlewares/defaultheaders.rs +++ b/src/middlewares/defaultheaders.rs @@ -112,14 +112,14 @@ mod tests { let mut req = HttpRequest::default(); let resp = HttpResponse::Ok().finish().unwrap(); - let resp = match mw.response(&mut req, Box::new(resp)) { + let resp = match mw.response(&mut req, resp) { Response::Done(resp) => resp, _ => panic!(), }; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish().unwrap(); - let resp = match mw.response(&mut req, Box::new(resp)) { + let resp = match mw.response(&mut req, resp) { Response::Done(resp) => resp, _ => panic!(), }; diff --git a/src/pipeline.rs b/src/pipeline.rs index 2073ff608..91a387534 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -899,7 +899,7 @@ mod tests { let mut ctx = HttpContext::new(req.clone(), MyActor); let addr: Address<_> = ctx.address(); let mut info = PipelineInfo::new(req); - info.context = Some(ctx); + info.context = Some(Box::new(ctx)); let mut state = Completed::init(&mut info).completed().unwrap(); let st = state.poll(&mut info).ok().unwrap(); diff --git a/tests/test_server.rs b/tests/test_server.rs index 65a0a38e1..53dacbbaa 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -59,7 +59,7 @@ impl middlewares::Middleware for MiddlewareTest { middlewares::Started::Done } - fn response(&self, _: &mut HttpRequest, resp: Box) -> middlewares::Response { + fn response(&self, _: &mut HttpRequest, resp: HttpResponse) -> middlewares::Response { self.response.store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed); middlewares::Response::Done(resp) } From 1daf50095abb06606709c40673f9154c1b7def01 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 15 Dec 2017 20:00:12 -0800 Subject: [PATCH 106/279] cleanup response --- src/handler.rs | 5 ++ src/httpresponse.rs | 187 ++++++++++++++++++++++---------------------- src/pipeline.rs | 39 ++++----- 3 files changed, 116 insertions(+), 115 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 6ea28dec4..f0fbb1ea3 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -110,6 +110,7 @@ impl Responder for HttpResponse { type Item = Reply; type Error = Error; + #[inline] fn respond_to(self, _: HttpRequest) -> Result { Ok(Reply(ReplyItem::Message(self))) } @@ -117,6 +118,7 @@ impl Responder for HttpResponse { impl From for Reply { + #[inline] fn from(resp: HttpResponse) -> Reply { Reply(ReplyItem::Message(resp)) } @@ -152,6 +154,7 @@ impl>, S: 'static> Responder for HttpContext< type Item = Reply; type Error = Error; + #[inline] fn respond_to(self, _: HttpRequest) -> Result { Ok(Reply(ReplyItem::Actor(Box::new(self)))) } @@ -159,6 +162,7 @@ impl>, S: 'static> Responder for HttpContext< impl>, S: 'static> From> for Reply { + #[inline] fn from(ctx: HttpContext) -> Reply { Reply(ReplyItem::Actor(Box::new(ctx))) } @@ -169,6 +173,7 @@ impl Responder for Box> type Item = Reply; type Error = Error; + #[inline] fn respond_to(self, _: HttpRequest) -> Result { Ok(Reply(ReplyItem::Future(self))) } diff --git a/src/httpresponse.rs b/src/httpresponse.rs index e98606335..b7f9d8301 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -29,86 +29,6 @@ pub enum ConnectionType { Upgrade, } -#[derive(Debug)] -struct InnerHttpResponse { - version: Option, - headers: HeaderMap, - status: StatusCode, - reason: Option<&'static str>, - body: Body, - chunked: bool, - encoding: ContentEncoding, - connection_type: Option, - response_size: u64, - error: Option, -} - -impl InnerHttpResponse { - - #[inline] - fn new(status: StatusCode, body: Body) -> InnerHttpResponse { - InnerHttpResponse { - version: None, - headers: HeaderMap::with_capacity(8), - status: status, - reason: None, - body: body, - chunked: false, - encoding: ContentEncoding::Auto, - connection_type: None, - response_size: 0, - error: None, - } - } - -} - -impl Default for InnerHttpResponse { - - fn default() -> InnerHttpResponse { - InnerHttpResponse::new(StatusCode::OK, Body::Empty) - } -} - -/// Internal use only! unsafe -struct Pool(VecDeque>); - -thread_local!(static POOL: RefCell = RefCell::new(Pool::new())); - -impl Pool { - fn new() -> Pool { - Pool(VecDeque::with_capacity(128)) - } - - fn get() -> Box { - POOL.with(|pool| { - if let Some(resp) = pool.borrow_mut().0.pop_front() { - resp - } else { - Box::new(InnerHttpResponse::default()) - } - }) - } - - #[cfg_attr(feature = "cargo-clippy", allow(boxed_local))] - fn release(mut inner: Box) { - POOL.with(|pool| { - if pool.borrow().0.len() < 128 { - inner.version.take(); - inner.headers.clear(); - inner.chunked = false; - inner.reason.take(); - inner.body = Body::Empty; - inner.encoding = ContentEncoding::Auto; - inner.connection_type.take(); - inner.response_size = 0; - inner.error.take(); - pool.borrow_mut().0.push_front(inner); - } - }) - } -} - /// An HTTP Response pub struct HttpResponse(Option>); @@ -123,27 +43,22 @@ impl Drop for HttpResponse { impl HttpResponse { #[inline(always)] + #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] fn get_ref(&self) -> &InnerHttpResponse { self.0.as_ref().unwrap() } #[inline(always)] + #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] fn get_mut(&mut self) -> &mut InnerHttpResponse { self.0.as_mut().unwrap() } - #[inline] - fn from_inner(inner: Box) -> HttpResponse { - HttpResponse(Some(inner)) - } - /// Create http response builder with specific status. #[inline] pub fn build(status: StatusCode) -> HttpResponseBuilder { - let mut inner = Pool::get(); - inner.status = status; HttpResponseBuilder { - response: Some(inner), + response: Some(Pool::get(status)), err: None, cookies: None, } @@ -152,10 +67,7 @@ impl HttpResponse { /// Constructs a response #[inline] pub fn new(status: StatusCode, body: Body) -> HttpResponse { - let mut inner = Pool::get(); - inner.status = status; - inner.body = body; - HttpResponse(Some(inner)) + HttpResponse(Some(Pool::with_body(status, body))) } /// Constructs a error response @@ -470,7 +382,7 @@ impl HttpResponseBuilder { } } response.body = body.into(); - Ok(HttpResponse::from_inner(response)) + Ok(HttpResponse(Some(response))) } /// Set a json body and generate `HttpResponse` @@ -495,6 +407,7 @@ impl HttpResponseBuilder { } } +#[inline] #[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))] fn parts<'a>(parts: &'a mut Option>, err: &Option) -> Option<&'a mut Box> @@ -525,6 +438,7 @@ impl Responder for HttpResponseBuilder { type Item = HttpResponse; type Error = HttpError; + #[inline] fn respond_to(mut self, _: HttpRequest) -> Result { self.finish() } @@ -650,6 +564,93 @@ impl Responder for BytesMut { } } +#[derive(Debug)] +struct InnerHttpResponse { + version: Option, + headers: HeaderMap, + status: StatusCode, + reason: Option<&'static str>, + body: Body, + chunked: bool, + encoding: ContentEncoding, + connection_type: Option, + response_size: u64, + error: Option, +} + +impl InnerHttpResponse { + + #[inline] + fn new(status: StatusCode, body: Body) -> InnerHttpResponse { + InnerHttpResponse { + version: None, + headers: HeaderMap::with_capacity(8), + status: status, + reason: None, + body: body, + chunked: false, + encoding: ContentEncoding::Auto, + connection_type: None, + response_size: 0, + error: None, + } + } + +} + +/// Internal use only! unsafe +struct Pool(VecDeque>); + +thread_local!(static POOL: RefCell = RefCell::new(Pool::new())); + +impl Pool { + fn new() -> Pool { + Pool(VecDeque::with_capacity(128)) + } + + fn get(status: StatusCode) -> Box { + POOL.with(|pool| { + if let Some(mut resp) = pool.borrow_mut().0.pop_front() { + resp.body = Body::Empty; + resp.status = status; + resp + } else { + Box::new(InnerHttpResponse::new(status, Body::Empty)) + } + }) + } + + fn with_body(status: StatusCode, body: Body) -> Box { + POOL.with(|pool| { + if let Some(mut resp) = pool.borrow_mut().0.pop_front() { + resp.status = status; + resp.body = Body::Empty; + resp + } else { + Box::new(InnerHttpResponse::new(status, body)) + } + }) + } + + #[cfg_attr(feature = "cargo-clippy", allow(boxed_local))] + fn release(mut inner: Box) { + POOL.with(|pool| { + let v = &mut pool.borrow_mut().0; + if v.len() < 128 { + inner.headers.clear(); + inner.version = None; + inner.chunked = false; + inner.reason = None; + inner.encoding = ContentEncoding::Auto; + inner.connection_type = None; + inner.response_size = 0; + inner.error = None; + v.push_front(inner); + } + }) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/pipeline.rs b/src/pipeline.rs index 91a387534..c04968dc1 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -408,19 +408,19 @@ struct WaitingResponse { impl WaitingResponse { + #[inline] fn init(info: &mut PipelineInfo, reply: Reply) -> PipelineState { - let stream = match reply.into() { + match reply.into() { ReplyItem::Message(resp) => - return RunMiddlewares::init(info, resp), + RunMiddlewares::init(info, resp), ReplyItem::Actor(ctx) => - PipelineResponse::Context(ctx), + PipelineState::Handler( + WaitingResponse { stream: PipelineResponse::Context(ctx), _s: PhantomData }), ReplyItem::Future(fut) => - PipelineResponse::Response(fut), - }; - - PipelineState::Handler( - WaitingResponse { stream: stream, _s: PhantomData }) + PipelineState::Handler( + WaitingResponse { stream: PipelineResponse::Response(fut), _s: PhantomData }), + } } fn poll(mut self, info: &mut PipelineInfo) -> Result, PipelineState> { @@ -587,15 +587,6 @@ enum IOState { Done, } -impl IOState { - fn is_done(&self) -> bool { - match *self { - IOState::Done => true, - _ => false - } - } -} - struct DrainVec(Vec>>); impl Drop for DrainVec { @@ -608,6 +599,7 @@ impl Drop for DrainVec { impl ProcessResponse { + #[inline] fn init(resp: HttpResponse) -> PipelineState { PipelineState::Response( @@ -779,11 +771,12 @@ impl ProcessResponse { } // response is completed - if self.iostate.is_done() { - self.resp.set_response_size(io.written()); - Ok(FinishingMiddlewares::init(info, self.resp)) - } else { - Err(PipelineState::Response(self)) + match self.iostate { + IOState::Done => { + self.resp.set_response_size(io.written()); + Ok(FinishingMiddlewares::init(info, self.resp)) + } + _ => Err(PipelineState::Response(self)) } } } @@ -850,6 +843,7 @@ struct Completed(PhantomData); impl Completed { + #[inline] fn init(info: &mut PipelineInfo) -> PipelineState { if info.context.is_none() { PipelineState::None @@ -858,6 +852,7 @@ impl Completed { } } + #[inline] fn poll(self, info: &mut PipelineInfo) -> Result, PipelineState> { match info.poll_context() { Ok(Async::NotReady) => Ok(PipelineState::Completed(Completed(PhantomData))), From ed8bd3d6a3a177fa6ec9f09087764877442c9898 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 15 Dec 2017 22:49:48 -0800 Subject: [PATCH 107/279] h1 cleanups --- README.md | 2 +- src/h1.rs | 130 ++++++++++++++++---------------------------- src/h1writer.rs | 3 +- src/helpers.rs | 19 ++++--- src/httprequest.rs | 45 ++++++--------- src/httpresponse.rs | 14 ++--- 6 files changed, 83 insertions(+), 130 deletions(-) diff --git a/README.md b/README.md index fb162fc55..fcb40d8fe 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Each result is best of five runs. All measurements are req/sec. Name | 1 thread | 1 pipeline | 3 thread | 3 pipeline | 8 thread | 8 pipeline ---- | -------- | ---------- | -------- | ---------- | -------- | ---------- -Actix | 89.300 | 871.200 | 122.100 | 1.877.000 | 107.400 | 2.560.000 +Actix | 91.200 | 912.000 | 122.100 | 2.083.000 | 107.400 | 2.650.000 Gotham | 61.000 | 178.000 | | | | Iron | | | | | 94.500 | 78.000 Rocket | | | | | 95.500 | failed diff --git a/src/h1.rs b/src/h1.rs index 91801e836..fa1a1f2e4 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -20,6 +20,7 @@ use h1writer::{Writer, H1Writer}; use server::WorkerSettings; use httpcodes::HTTPNotFound; use httprequest::HttpRequest; +use helpers::SharedHttpMessage; use error::{ParseError, PayloadError, ResponseError}; use payload::{Payload, PayloadWriter, DEFAULT_BUFFER_SIZE}; @@ -527,60 +528,60 @@ impl Reader { } // Parse http message - let mut headers_indices: [HeaderIndices; MAX_HEADERS] = - unsafe{std::mem::uninitialized()}; - - let (len, method, path, version, headers_len) = { + let msg = { + let bytes_ptr = buf.as_ref().as_ptr() as usize; let mut headers: [httparse::Header; MAX_HEADERS] = unsafe{std::mem::uninitialized()}; - let mut req = httparse::Request::new(&mut headers); - match try!(req.parse(buf)) { - httparse::Status::Complete(len) => { - let method = Method::try_from(req.method.unwrap()) - .map_err(|_| ParseError::Method)?; - let path = req.path.unwrap(); - let bytes_ptr = buf.as_ref().as_ptr() as usize; - let path_start = path.as_ptr() as usize - bytes_ptr; - let path_end = path_start + path.len(); - let path = (path_start, path_end); - let version = if req.version.unwrap() == 1 { - Version::HTTP_11 - } else { - Version::HTTP_10 - }; + let (len, method, path, version, headers_len) = { + let b = unsafe{ let b: &[u8] = buf; std::mem::transmute(b) }; + let mut req = httparse::Request::new(&mut headers); + match req.parse(b)? { + httparse::Status::Complete(len) => { + let method = Method::try_from(req.method.unwrap()) + .map_err(|_| ParseError::Method)?; + let path = req.path.unwrap(); + let path_start = path.as_ptr() as usize - bytes_ptr; + let path_end = path_start + path.len(); + let path = (path_start, path_end); - record_header_indices(buf.as_ref(), req.headers, &mut headers_indices); - let headers_len = req.headers.len(); - (len, method, path, version, headers_len) + let version = if req.version.unwrap() == 1 { + Version::HTTP_11 + } else { + Version::HTTP_10 + }; + (len, method, path, version, req.headers.len()) + } + httparse::Status::Partial => return Ok(Message::NotReady), } - httparse::Status::Partial => return Ok(Message::NotReady), - } - }; + }; - let slice = buf.split_to(len).freeze(); - let path = slice.slice(path.0, path.1); - // path was found to be utf8 by httparse - let uri = Uri::from_shared(path).map_err(ParseError::Uri)?; + let slice = buf.split_to(len).freeze(); - // convert headers - let msg = settings.get_http_message(); - msg.get_mut().headers.reserve(headers_len); - for header in headers_indices[..headers_len].iter() { - if let Ok(name) = HeaderName::try_from(slice.slice(header.name.0, header.name.1)) { - if let Ok(value) = HeaderValue::try_from( - slice.slice(header.value.0, header.value.1)) - { + // convert headers + let msg = settings.get_http_message(); + for header in headers[..headers_len].iter() { + if let Ok(name) = HeaderName::try_from(header.name) { + let v_start = header.value.as_ptr() as usize - bytes_ptr; + let v_end = v_start + header.value.len(); + let value = unsafe { + HeaderValue::from_shared_unchecked(slice.slice(v_start, v_end)) }; msg.get_mut().headers.append(name, value); } else { return Err(ParseError::Header) } - } else { - return Err(ParseError::Header) } - } - let decoder = if upgrade(&method, &msg.get_mut().headers) { + let path = slice.slice(path.0, path.1); + let uri = Uri::from_shared(path).map_err(ParseError::Uri)?; + + msg.get_mut().uri = uri; + msg.get_mut().method = method; + msg.get_mut().version = version; + msg + }; + + let decoder = if upgrade(&msg) { Decoder::eof() } else { let has_len = msg.get_mut().headers.contains_key(header::CONTENT_LENGTH); @@ -593,9 +594,6 @@ impl Reader { Decoder::chunked() } else { if !has_len { - msg.get_mut().uri = uri; - msg.get_mut().method = method; - msg.get_mut().version = version; return Ok(Message::Http1(HttpRequest::from_message(msg), None)) } @@ -620,45 +618,21 @@ impl Reader { tx: PayloadType::new(&msg.get_mut().headers, psender), decoder: decoder, }; - msg.get_mut().uri = uri; - msg.get_mut().method = method; - msg.get_mut().version = version; msg.get_mut().payload = Some(payload); Ok(Message::Http1(HttpRequest::from_message(msg), Some(info))) } } -#[derive(Clone, Copy)] -struct HeaderIndices { - name: (usize, usize), - value: (usize, usize), -} - -fn record_header_indices(bytes: &[u8], - headers: &[httparse::Header], - indices: &mut [HeaderIndices]) -{ - let bytes_ptr = bytes.as_ptr() as usize; - for (header, indices) in headers.iter().zip(indices.iter_mut()) { - let name_start = header.name.as_ptr() as usize - bytes_ptr; - let name_end = name_start + header.name.len(); - indices.name = (name_start, name_end); - let value_start = header.value.as_ptr() as usize - bytes_ptr; - let value_end = value_start + header.value.len(); - indices.value = (value_start, value_end); - } -} - /// Check if request is UPGRADE -fn upgrade(method: &Method, headers: &HeaderMap) -> bool { - if let Some(conn) = headers.get(header::CONNECTION) { +fn upgrade(msg: &SharedHttpMessage) -> bool { + if let Some(conn) = msg.get_ref().headers.get(header::CONNECTION) { if let Ok(s) = conn.to_str() { s.to_lowercase().contains("upgrade") } else { - *method == Method::CONNECT + msg.get_ref().method == Method::CONNECT } } else { - *method == Method::CONNECT + msg.get_ref().method == Method::CONNECT } } @@ -735,18 +709,6 @@ enum ChunkedState { End, } -impl Decoder { - /*pub fn is_eof(&self) -> bool { - trace!("is_eof? {:?}", self); - match self.kind { - Kind::Length(0) | - Kind::Chunked(ChunkedState::End, _) | - Kind::Eof(true) => true, - _ => false, - } - }*/ -} - impl Decoder { pub fn decode(&mut self, body: &mut BytesMut) -> Poll, io::Error> { match self.kind { diff --git a/src/h1writer.rs b/src/h1writer.rs index aa1489b7d..dd868d7dc 100644 --- a/src/h1writer.rs +++ b/src/h1writer.rs @@ -168,8 +168,7 @@ impl Writer for H1Writer { buffer.extend_from_slice(b"\r\n"); for (key, value) in msg.headers() { - let t: &[u8] = key.as_ref(); - buffer.extend_from_slice(t); + buffer.extend_from_slice(key.as_str().as_bytes()); buffer.extend_from_slice(b": "); buffer.extend_from_slice(value.as_ref()); buffer.extend_from_slice(b"\r\n"); diff --git a/src/helpers.rs b/src/helpers.rs index 57ecb70d0..1873df9f7 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -74,9 +74,10 @@ impl SharedBytesPool { } pub fn release_bytes(&self, mut bytes: Rc) { - if self.0.borrow().len() < 128 { + let v = &mut self.0.borrow_mut(); + if v.len() < 128 { Rc::get_mut(&mut bytes).unwrap().take(); - self.0.borrow_mut().push_front(bytes); + v.push_front(bytes); } } } @@ -107,9 +108,9 @@ impl SharedBytes { SharedBytes(Some(bytes), Some(pool)) } - #[inline] + #[inline(always)] #[allow(mutable_transmutes)] - #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))] + #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] pub fn get_mut(&self) -> &mut BytesMut { let r: &BytesMut = self.0.as_ref().unwrap().as_ref(); unsafe{mem::transmute(r)} @@ -150,9 +151,10 @@ impl SharedMessagePool { } pub fn release(&self, mut msg: Rc) { - if self.0.borrow().len() < 128 { + let v = &mut self.0.borrow_mut(); + if v.len() < 128 { Rc::get_mut(&mut msg).unwrap().reset(); - self.0.borrow_mut().push_front(msg); + v.push_front(msg); } } } @@ -219,7 +221,8 @@ impl SharedHttpMessage { unsafe{mem::transmute(r)} } - #[inline] + #[inline(always)] + #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] pub fn get_ref(&self) -> &HttpMessage { self.0.as_ref().unwrap() } @@ -234,7 +237,7 @@ const DEC_DIGITS_LUT: &[u8] = pub(crate) fn convert_u16(mut n: u16, bytes: &mut BytesMut) { let mut buf: [u8; 39] = unsafe { mem::uninitialized() }; - let mut curr = buf.len() as isize; + let mut curr: isize = 39; let buf_ptr = buf.as_mut_ptr(); let lut_ptr = DEC_DIGITS_LUT.as_ptr(); diff --git a/src/httprequest.rs b/src/httprequest.rs index b06740ebb..0fab3c342 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -40,7 +40,7 @@ impl Default for HttpMessage { method: Method::GET, uri: Uri::default(), version: Version::HTTP_11, - headers: HeaderMap::new(), + headers: HeaderMap::with_capacity(16), params: Params::default(), cookies: None, addr: None, @@ -54,6 +54,7 @@ impl Default for HttpMessage { impl HttpMessage { /// Checks if a connection should be kept alive. + #[inline] pub fn keep_alive(&self) -> bool { if let Some(conn) = self.headers.get(header::CONNECTION) { if let Ok(conn) = conn.to_str() { @@ -71,14 +72,15 @@ impl HttpMessage { } } + #[inline] pub(crate) fn reset(&mut self) { self.headers.clear(); self.extensions.clear(); self.params.clear(); - self.cookies.take(); - self.addr.take(); - self.payload.take(); - self.info.take(); + self.cookies = None; + self.addr = None; + self.payload = None; + self.info = None; } } @@ -109,6 +111,8 @@ impl HttpRequest<()> { ) } + #[inline(always)] + #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] pub(crate) fn from_message(msg: SharedHttpMessage) -> HttpRequest { HttpRequest(msg, None, None) } @@ -138,6 +142,7 @@ impl HttpRequest<()> { ) } + #[inline] /// Construct new http request with state. pub fn with_state(self, state: Rc, router: Router) -> HttpRequest { HttpRequest(self.0, Some(state), Some(router)) @@ -146,6 +151,7 @@ impl HttpRequest<()> { impl HttpRequest { + #[inline] /// Construct new http request without state. pub fn clone_without_state(&self) -> HttpRequest { HttpRequest(self.0.clone(), None, None) @@ -153,13 +159,14 @@ impl HttpRequest { // get mutable reference for inner message // mutable reference should not be returned as result for request's method - #[inline] - #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))] + #[inline(always)] + #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] fn as_mut(&self) -> &mut HttpMessage { self.0.get_mut() } - #[inline] + #[inline(always)] + #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] fn as_ref(&self) -> &HttpMessage { self.0.get_ref() } @@ -183,11 +190,7 @@ impl HttpRequest { #[doc(hidden)] pub fn prefix_len(&self) -> usize { - if let Some(router) = self.router() { - router.prefix().len() - } else { - 0 - } + if let Some(router) = self.router() { router.prefix().len() } else { 0 } } /// Read the Request Uri. @@ -288,7 +291,6 @@ impl HttpRequest { } /// Load request cookies. - #[inline] pub fn cookies(&self) -> Result<&Vec>, CookieParseError> { if self.as_ref().cookies.is_none() { let msg = self.as_mut(); @@ -334,20 +336,7 @@ impl HttpRequest { /// Checks if a connection should be kept alive. pub fn keep_alive(&self) -> bool { - if let Some(conn) = self.headers().get(header::CONNECTION) { - if let Ok(conn) = conn.to_str() { - if self.as_ref().version == Version::HTTP_10 && conn.contains("keep-alive") { - true - } else { - self.as_ref().version == Version::HTTP_11 && - !(conn.contains("close") || conn.contains("upgrade")) - } - } else { - false - } - } else { - self.as_ref().version != Version::HTTP_10 - } + self.as_ref().keep_alive() } /// Read the request content type diff --git a/src/httpresponse.rs b/src/httpresponse.rs index b7f9d8301..f8b410877 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -584,7 +584,7 @@ impl InnerHttpResponse { fn new(status: StatusCode, body: Body) -> InnerHttpResponse { InnerHttpResponse { version: None, - headers: HeaderMap::with_capacity(8), + headers: HeaderMap::with_capacity(16), status: status, reason: None, body: body, @@ -595,19 +595,17 @@ impl InnerHttpResponse { error: None, } } - } /// Internal use only! unsafe struct Pool(VecDeque>); -thread_local!(static POOL: RefCell = RefCell::new(Pool::new())); +thread_local!(static POOL: RefCell = + RefCell::new(Pool(VecDeque::with_capacity(128)))); impl Pool { - fn new() -> Pool { - Pool(VecDeque::with_capacity(128)) - } + #[inline] fn get(status: StatusCode) -> Box { POOL.with(|pool| { if let Some(mut resp) = pool.borrow_mut().0.pop_front() { @@ -620,6 +618,7 @@ impl Pool { }) } + #[inline] fn with_body(status: StatusCode, body: Body) -> Box { POOL.with(|pool| { if let Some(mut resp) = pool.borrow_mut().0.pop_front() { @@ -632,7 +631,8 @@ impl Pool { }) } - #[cfg_attr(feature = "cargo-clippy", allow(boxed_local))] + #[inline(always)] + #[cfg_attr(feature = "cargo-clippy", allow(boxed_local, inline_always))] fn release(mut inner: Box) { POOL.with(|pool| { let v = &mut pool.borrow_mut().0; From b1f33e29ec79a766e369bc3d32305175fa5152a4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 16 Dec 2017 07:29:15 -0800 Subject: [PATCH 108/279] simplify content-length calculation --- README.md | 2 +- src/encoding.rs | 19 +++++-------------- src/h1.rs | 6 +----- src/h1writer.rs | 36 +++++++++++++++++++++++++----------- src/h2writer.rs | 19 ++++++++++++++++--- src/helpers.rs | 7 +++---- src/ws.rs | 2 +- 7 files changed, 52 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index fcb40d8fe..473b517d2 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Each result is best of five runs. All measurements are req/sec. Name | 1 thread | 1 pipeline | 3 thread | 3 pipeline | 8 thread | 8 pipeline ---- | -------- | ---------- | -------- | ---------- | -------- | ---------- -Actix | 91.200 | 912.000 | 122.100 | 2.083.000 | 107.400 | 2.650.000 +Actix | 91.200 | 950.000 | 122.100 | 2.083.000 | 107.400 | 2.730.000 Gotham | 61.000 | 178.000 | | | | Iron | | | | | 94.500 | 78.000 Rocket | | | | | 95.500 | failed diff --git a/src/encoding.rs b/src/encoding.rs index b632a1a3c..85cd7fd6a 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -13,10 +13,9 @@ use flate2::write::{GzEncoder, DeflateDecoder, DeflateEncoder}; use brotli2::write::{BrotliDecoder, BrotliEncoder}; use bytes::{Bytes, BytesMut, BufMut, Writer}; -use helpers; -use helpers::SharedBytes; use body::{Body, Binary}; use error::PayloadError; +use helpers::SharedBytes; use httprequest::HttpMessage; use httpresponse::HttpResponse; use payload::{PayloadSender, PayloadWriter}; @@ -390,7 +389,7 @@ impl PayloadEncoder { if resp.chunked() { error!("Chunked transfer is enabled but body is set to Empty"); } - resp.headers_mut().insert(CONTENT_LENGTH, HeaderValue::from_static("0")); + resp.headers_mut().remove(CONTENT_LENGTH); TransferEncoding::eof(buf) }, Body::Binary(ref mut bytes) => { @@ -409,18 +408,12 @@ impl PayloadEncoder { // TODO return error! let _ = enc.write(bytes.as_ref()); let _ = enc.write_eof(); - let b = enc.get_mut().take(); - resp.headers_mut().insert( - CONTENT_LENGTH, helpers::convert_into_header(b.len())); - *bytes = Binary::from(b); + *bytes = Binary::from(enc.get_mut().take()); encoding = ContentEncoding::Identity; - TransferEncoding::eof(buf) - } else { - resp.headers_mut().insert( - CONTENT_LENGTH, helpers::convert_into_header(bytes.len())); - TransferEncoding::eof(buf) } + resp.headers_mut().remove(CONTENT_LENGTH); + TransferEncoding::eof(buf) } Body::Streaming(_) | Body::StreamingContext => { if resp.chunked() { @@ -734,11 +727,9 @@ impl TransferEncoding { return *remaining == 0 } let max = cmp::min(*remaining, msg.len() as u64); - trace!("sized write = {}", max); self.buffer.get_mut().extend_from_slice(msg[..max as usize].as_ref()); *remaining -= max as u64; - trace!("encoded {} bytes, remaining = {}", max, remaining); *remaining == 0 }, } diff --git a/src/h1.rs b/src/h1.rs index fa1a1f2e4..78e060e1e 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -435,7 +435,7 @@ impl Reader { let read = if buf.is_empty() { match self.read_from_io(io, buf) { Ok(Async::Ready(0)) => { - debug!("Ignored premature client disconnection"); + // debug!("Ignored premature client disconnection"); return Err(ReaderError::Disconnect); }, Ok(Async::Ready(_)) => (), @@ -713,7 +713,6 @@ impl Decoder { pub fn decode(&mut self, body: &mut BytesMut) -> Poll, io::Error> { match self.kind { Kind::Length(ref mut remaining) => { - trace!("Sized read, remaining={:?}", remaining); if *remaining == 0 { Ok(Async::Ready(None)) } else { @@ -794,7 +793,6 @@ impl ChunkedState { } } fn read_size(rdr: &mut BytesMut, size: &mut u64) -> Poll { - trace!("Read chunk hex size"); let radix = 16; match byte!(rdr) { b @ b'0'...b'9' => { @@ -833,14 +831,12 @@ impl ChunkedState { } } fn read_extension(rdr: &mut BytesMut) -> Poll { - trace!("read_extension"); match byte!(rdr) { b'\r' => Ok(Async::Ready(ChunkedState::SizeLf)), _ => Ok(Async::Ready(ChunkedState::Extension)), // no supported extensions } } fn read_size_lf(rdr: &mut BytesMut, size: &mut u64) -> Poll { - trace!("Chunk size is {:?}", size); match byte!(rdr) { b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)), b'\n' if *size == 0 => Ok(Async::Ready(ChunkedState::EndCr)), diff --git a/src/h1writer.rs b/src/h1writer.rs index dd868d7dc..3b2415fda 100644 --- a/src/h1writer.rs +++ b/src/h1writer.rs @@ -2,7 +2,7 @@ use std::io; use futures::{Async, Poll}; use tokio_io::AsyncWrite; use http::Version; -use http::header::{HeaderValue, CONNECTION, DATE}; +use http::header::{HeaderValue, CONNECTION, DATE, CONTENT_LENGTH}; use helpers; use body::Body; @@ -124,7 +124,7 @@ impl Writer for H1Writer { fn start(&mut self, req: &mut HttpMessage, msg: &mut HttpResponse) -> Result { - trace!("Prepare response with status: {:?}", msg.status()); + //trace!("Prepare response with status: {:?}", msg.status()); // prepare task self.flags.insert(Flags::STARTED); @@ -146,11 +146,12 @@ impl Writer for H1Writer { } else if version >= Version::HTTP_11 { msg.headers_mut().insert(CONNECTION, HeaderValue::from_static("close")); } + let body = msg.replace_body(Body::Empty); // render message { let mut buffer = self.encoder.get_mut(); - if let Body::Binary(ref bytes) = *msg.body() { + if let Body::Binary(ref bytes) = body { buffer.reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len()); } else { buffer.reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE); @@ -174,6 +175,20 @@ impl Writer for H1Writer { buffer.extend_from_slice(b"\r\n"); } + match body { + Body::Empty => { + buffer.extend_from_slice(CONTENT_LENGTH.as_str().as_bytes()); + buffer.extend_from_slice(b": 0\r\n"); + } + Body::Binary(ref bytes) => { + buffer.extend_from_slice(CONTENT_LENGTH.as_str().as_bytes()); + buffer.extend_from_slice(b": "); + helpers::convert_usize(bytes.len(), &mut buffer); + buffer.extend_from_slice(b"\r\n"); + } + _ => () + } + // using helpers::date is quite a lot faster if !msg.headers().contains_key(DATE) { buffer.reserve(helpers::DATE_VALUE_LENGTH + 8); @@ -187,14 +202,13 @@ impl Writer for H1Writer { self.headers_size = buffer.len() as u32; } - trace!("Response: {:?}", msg); + // trace!("Response: {:?}", msg); - if msg.body().is_binary() { - let body = msg.replace_body(Body::Empty); - if let Body::Binary(bytes) = body { - self.encoder.write(bytes.as_ref())?; - return Ok(WriterState::Done) - } + if let Body::Binary(bytes) = body { + self.encoder.write(bytes.as_ref())?; + return Ok(WriterState::Done) + } else { + msg.replace_body(body); } Ok(WriterState::Done) } @@ -221,7 +235,7 @@ impl Writer for H1Writer { self.encoder.write_eof()?; if !self.encoder.is_eof() { - //debug!("last payload item, but it is not EOF "); + // debug!("last payload item, but it is not EOF "); Err(io::Error::new(io::ErrorKind::Other, "Last payload item, but eof is not reached")) } else if self.encoder.len() > MAX_WRITE_BUFFER_SIZE { diff --git a/src/h2writer.rs b/src/h2writer.rs index 5874d6601..d3091c6f3 100644 --- a/src/h2writer.rs +++ b/src/h2writer.rs @@ -4,7 +4,7 @@ use futures::{Async, Poll}; use http2::{Reason, SendStream}; use http2::server::Respond; use http::{Version, HttpTryFrom, Response}; -use http::header::{HeaderValue, CONNECTION, TRANSFER_ENCODING, DATE}; +use http::header::{HeaderValue, CONNECTION, TRANSFER_ENCODING, DATE, CONTENT_LENGTH}; use helpers; use body::Body; @@ -114,7 +114,7 @@ impl Writer for H2Writer { fn start(&mut self, req: &mut HttpMessage, msg: &mut HttpResponse) -> Result { - trace!("Prepare response with status: {:?}", msg.status()); + // trace!("Prepare response with status: {:?}", msg.status()); // prepare response self.flags.insert(Flags::STARTED); @@ -142,6 +142,19 @@ impl Writer for H2Writer { resp.headers_mut().insert(key, value.clone()); } + match *msg.body() { + Body::Binary(ref bytes) => { + let mut val = BytesMut::new(); + helpers::convert_usize(bytes.len(), &mut val); + resp.headers_mut().insert( + CONTENT_LENGTH, HeaderValue::try_from(val.freeze()).unwrap()); + } + Body::Empty => { + resp.headers_mut().insert(CONTENT_LENGTH, HeaderValue::from_static("0")); + }, + _ => (), + } + match self.respond.send_response(resp, self.flags.contains(Flags::EOF)) { Ok(stream) => self.stream = Some(stream), @@ -149,7 +162,7 @@ impl Writer for H2Writer { return Err(io::Error::new(io::ErrorKind::Other, "err")), } - trace!("Response: {:?}", msg); + // trace!("Response: {:?}", msg); if msg.body().is_binary() { if let Body::Binary(bytes) = msg.replace_body(Body::Empty) { diff --git a/src/helpers.rs b/src/helpers.rs index 1873df9f7..30b41c8dd 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -6,7 +6,6 @@ use std::ops::{Deref, DerefMut}; use std::collections::VecDeque; use time; use bytes::BytesMut; -use http::header::HeaderValue; use httprequest::HttpMessage; @@ -285,7 +284,7 @@ pub(crate) fn convert_u16(mut n: u16, bytes: &mut BytesMut) { } } -pub(crate) fn convert_into_header(mut n: usize) -> HeaderValue { +pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) { let mut curr: isize = 39; let mut buf: [u8; 39] = unsafe { mem::uninitialized() }; let buf_ptr = buf.as_mut_ptr(); @@ -330,8 +329,8 @@ pub(crate) fn convert_into_header(mut n: usize) -> HeaderValue { } unsafe { - HeaderValue::from_bytes( - slice::from_raw_parts(buf_ptr.offset(curr), buf.len() - curr as usize)).unwrap() + bytes.extend_from_slice( + slice::from_raw_parts(buf_ptr.offset(curr), buf.len() - curr as usize)); } } diff --git a/src/ws.rs b/src/ws.rs index d30e525ae..324a304af 100644 --- a/src/ws.rs +++ b/src/ws.rs @@ -220,7 +220,7 @@ impl Stream for WsStream { loop { match wsframe::Frame::parse(&mut self.buf) { Ok(Some(frame)) => { - trace!("WsFrame {}", frame); + // trace!("WsFrame {}", frame); let (_finished, opcode, payload) = frame.unpack(); match opcode { From 91ffab8f6ea13ef9f26d80c622bc181918054c1d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 16 Dec 2017 07:30:53 -0800 Subject: [PATCH 109/279] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 473b517d2..74dbfb403 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ fn main() { HttpServer::new( || Application::new() .resource("/{name}", |r| r.f(index))) - .serve::<_, ()>("127.0.0.1:8080"); + .serve("127.0.0.1:8080"); } ``` From 167717d20e820461fe423321d4048052526e90fd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 16 Dec 2017 11:22:39 -0800 Subject: [PATCH 110/279] update readme --- README.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 74dbfb403..7240c26af 100644 --- a/README.md +++ b/README.md @@ -39,18 +39,18 @@ fn main() { ## Benchmarks -This is totally unscientific and probably pretty useless. In real world business -logic would dominate on performance side. But in any case. i took several web frameworks -for rust and used theirs *hello world* example. All projects are compiled with -`--release` parameter. I didnt test single thread performance for iron and rocket. +This is totally unscientific and probably pretty useless. In real world, business +logic would dominate on performance side. I took several web frameworks +for rust and used *hello world* examples for tests. All projects are compiled with +`--release` parameter. I didnt test single thread performance for *iron* and *rocket*. As a testing tool i used `wrk` and following commands `wrk -t20 -c100 -d10s http://127.0.0.1:8080/` `wrk -t20 -c100 -d10s http://127.0.0.1:8080/ -s ./pipeline.lua --latency -- / 128` -I ran all tests on localhost on MacBook Pro late 2017. It has 4 cpu and 8 logical cpus. -Each result is best of five runs. All measurements are req/sec. +I ran all tests on my MacBook Pro with 2.9Gh i7 with 4 physical cpus and 8 logical cpus. +Each result is best of five runs. All measurements are *req/sec*. Name | 1 thread | 1 pipeline | 3 thread | 3 pipeline | 8 thread | 8 pipeline ---- | -------- | ---------- | -------- | ---------- | -------- | ---------- @@ -61,12 +61,13 @@ Rocket | | | | | 95.500 | failed Shio | 71.800 | 317.800 | | | | | tokio-minihttp | 106.900 | 1.047.000 | | | | -Some notes on results. Iron and Rocket got tested with 8 threads, -which showed best results. Gothan and tokio-minihttp seem does not support -multithreading, or at least i couldn't figured out. I manually enabled pipelining -for *Shio* and Gotham*. While shio seems support multithreading, but it showed -absolutly same results for any how number of threads (maybe macos problem?) -Rocket completely failed in pipelined tests. +I got best performance for sync frameworks with 8 threads, other number of +threads always gave me worse performance. *Iron* could handle piplined +requests with lower performace. Interestingly, *Rocket* completely failed in pipelined test. +*Gothan* seems does not support multithreading, or at least i couldn't figured out. +I manually enabled pipelining for *Shio* and *Gotham*. While *shio* seems support +multithreading, but it result absolutly same results for any how number of threads +(maybe macos problem?). ## Examples From 9821c6ea9022b4dc93f8953390984e133e145186 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 16 Dec 2017 11:39:56 -0800 Subject: [PATCH 111/279] update readme --- README.md | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/README.md b/README.md index 7240c26af..e3c402e2f 100644 --- a/README.md +++ b/README.md @@ -39,35 +39,7 @@ fn main() { ## Benchmarks -This is totally unscientific and probably pretty useless. In real world, business -logic would dominate on performance side. I took several web frameworks -for rust and used *hello world* examples for tests. All projects are compiled with -`--release` parameter. I didnt test single thread performance for *iron* and *rocket*. -As a testing tool i used `wrk` and following commands - -`wrk -t20 -c100 -d10s http://127.0.0.1:8080/` - -`wrk -t20 -c100 -d10s http://127.0.0.1:8080/ -s ./pipeline.lua --latency -- / 128` - -I ran all tests on my MacBook Pro with 2.9Gh i7 with 4 physical cpus and 8 logical cpus. -Each result is best of five runs. All measurements are *req/sec*. - -Name | 1 thread | 1 pipeline | 3 thread | 3 pipeline | 8 thread | 8 pipeline ----- | -------- | ---------- | -------- | ---------- | -------- | ---------- -Actix | 91.200 | 950.000 | 122.100 | 2.083.000 | 107.400 | 2.730.000 -Gotham | 61.000 | 178.000 | | | | -Iron | | | | | 94.500 | 78.000 -Rocket | | | | | 95.500 | failed -Shio | 71.800 | 317.800 | | | | | -tokio-minihttp | 106.900 | 1.047.000 | | | | - -I got best performance for sync frameworks with 8 threads, other number of -threads always gave me worse performance. *Iron* could handle piplined -requests with lower performace. Interestingly, *Rocket* completely failed in pipelined test. -*Gothan* seems does not support multithreading, or at least i couldn't figured out. -I manually enabled pipelining for *Shio* and *Gotham*. While *shio* seems support -multithreading, but it result absolutly same results for any how number of threads -(maybe macos problem?). +Some basic benchmarks could be found in this [respository](https://github.com/fafhrd91/benchmarks). ## Examples From 1a51f75eccb14513733f5b40bd8e5739633ec99a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Dec 2017 10:03:37 -0800 Subject: [PATCH 112/279] update readme --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e3c402e2f..56cbff26a 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,10 @@ fn main() { * Transparent content compression/decompression (br, gzip, deflate) * Configurable request routing * Multipart streams - * Middlewares (Logger, Session, DefaultHeaders) + * Middlewares ( + [Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging), + [Session](https://actix.github.io/actix-web/guide/qs_10.html#user-sessions), + [DefaultHeaders](https://actix.github.io/actix-web/guide/qs_10.html#default-headers)) * Built on top of [Actix](https://github.com/actix/actix). ## Benchmarks From 4b421b44a281e3d4c2181ef3f2c356d9d67174a1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Dec 2017 10:08:44 -0800 Subject: [PATCH 113/279] add mit license --- Cargo.toml | 2 +- LICENSE => LICENSE-APACHE | 0 LICENSE-MIT | 25 +++++++++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) rename LICENSE => LICENSE-APACHE (100%) create mode 100644 LICENSE-MIT diff --git a/Cargo.toml b/Cargo.toml index 0133e25b4..a86dee33c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://github.com/actix/actix-web.git" documentation = "https://docs.rs/actix-web/" categories = ["network-programming", "asynchronous", "web-programming::http-server", "web-programming::websocket"] -license = "Apache-2.0" +license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] build = "build.rs" diff --git a/LICENSE b/LICENSE-APACHE similarity index 100% rename from LICENSE rename to LICENSE-APACHE diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 000000000..410ce45a4 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2017 Nikilay 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. From 27d92f3a238283fdf4b1b09bec95e96363288042 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Dec 2017 12:35:04 -0800 Subject: [PATCH 114/279] refactor server bind and start process --- Cargo.toml | 6 +- README.md | 6 +- examples/basic.rs | 3 +- examples/state.rs | 3 +- examples/websocket.rs | 3 +- guide/src/qs_2.md | 3 +- guide/src/qs_4.md | 3 +- src/lib.rs | 2 +- src/server.rs | 206 ++++++++++++++++++++++++------------------ tests/test_server.rs | 7 +- 10 files changed, 139 insertions(+), 103 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a86dee33c..5d80682d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.3.0" authors = ["Nikolay Kim "] description = "Actix web framework" readme = "README.md" -keywords = ["http", "http2", "web", "async", "futures"] +keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://github.com/actix/actix-web" repository = "https://github.com/actix/actix-web.git" documentation = "https://docs.rs/actix-web/" @@ -74,8 +74,6 @@ tokio-openssl = { version="0.1", optional = true } [dependencies.actix] version = "^0.3.1" -#path = "../actix" -#git = "https://github.com/actix/actix.git" default-features = false features = [] @@ -96,4 +94,4 @@ version_check = "0.1" [profile.release] lto = true opt-level = 3 -debug = true +# debug = true diff --git a/README.md b/README.md index 56cbff26a..532c3692b 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ fn main() { HttpServer::new( || Application::new() .resource("/{name}", |r| r.f(index))) - .serve("127.0.0.1:8080"); + .bind("127.0.0.1:8080")? + .start(); } ``` @@ -34,8 +35,7 @@ fn main() { * Transparent content compression/decompression (br, gzip, deflate) * Configurable request routing * Multipart streams - * Middlewares ( - [Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging), + * Middlewares ([Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging), [Session](https://actix.github.io/actix-web/guide/qs_10.html#user-sessions), [DefaultHeaders](https://actix.github.io/actix-web/guide/qs_10.html#default-headers)) * Built on top of [Actix](https://github.com/actix/actix). diff --git a/examples/basic.rs b/examples/basic.rs index 22dfaba37..9c182817c 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -93,7 +93,8 @@ fn main() { .header("LOCATION", "/index.html") .body(Body::Empty) }))) - .serve::<_, ()>("127.0.0.1:8080").unwrap(); + .bind("127.0.0.1:8080").unwrap() + .start().unwrap(); println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); diff --git a/examples/state.rs b/examples/state.rs index aef09fc28..c36ac19d2 100644 --- a/examples/state.rs +++ b/examples/state.rs @@ -70,7 +70,8 @@ fn main() { r.method(Method::GET).f(|req| ws::start(req, MyWebSocket{counter: 0}))) // register simple handler, handle all methods .resource("/", |r| r.f(index))) - .serve::<_, ()>("127.0.0.1:8080").unwrap(); + .bind("127.0.0.1:8080").unwrap() + .start().unwrap(); println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); diff --git a/examples/websocket.rs b/examples/websocket.rs index cbcf91c11..8f62ef296 100644 --- a/examples/websocket.rs +++ b/examples/websocket.rs @@ -70,7 +70,8 @@ fn main() { .resource("/{tail:.*}", |r| r.h(fs::StaticFiles::new("tail", "examples/static/", true)))) // start http server on 127.0.0.1:8080 - .serve::<_, ()>("127.0.0.1:8080").unwrap(); + .bind("127.0.0.1:8080").unwrap() + .start().unwrap(); println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md index 0c29f5278..b76855c85 100644 --- a/guide/src/qs_2.md +++ b/guide/src/qs_2.md @@ -81,7 +81,8 @@ fn main() { HttpServer::new( || Application::new() .resource("/", |r| r.f(index))) - .serve::<_, ()>("127.0.0.1:8088").unwrap(); + .bind("127.0.0.1:8088").unwrap() + .start(); println!("Started http server: 127.0.0.1:8088"); # actix::Arbiter::system().send(actix::msgs::SystemExit(0)); diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index 077c71fca..25528c45d 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -82,7 +82,8 @@ fn main() { HttpServer::new( || Application::new() .resource("/", |r| r.method(Method::GET).f(index))) - .serve::<_, ()>("127.0.0.1:8088").unwrap(); + .bind("127.0.0.1:8088").unwrap() + .start(); println!("Started http server: 127.0.0.1:8088"); # actix::Arbiter::system().send(actix::msgs::SystemExit(0)); diff --git a/src/lib.rs b/src/lib.rs index d9563e1fc..92ed2ea52 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,7 @@ //! HttpServer::new( //! || Application::new() //! .resource("/{name}", |r| r.f(index))) -//! .serve::<_, ()>("127.0.0.1:8080"); +//! .serve("127.0.0.1:8080"); //! } //! ``` //! diff --git a/src/server.rs b/src/server.rs index 363a268ac..f787621f5 100644 --- a/src/server.rs +++ b/src/server.rs @@ -3,6 +3,7 @@ use std::rc::Rc; use std::sync::Arc; use std::time::Duration; use std::marker::PhantomData; +use std::collections::HashMap; use actix::dev::*; use futures::Stream; @@ -94,9 +95,11 @@ pub struct HttpServer io: PhantomData, addr: PhantomData, threads: usize, + backlog: i32, keep_alive: Option, factory: Arc U + Send + Sync>, workers: Vec>>, + sockets: HashMap, } impl Actor for HttpServer { @@ -129,9 +132,11 @@ impl HttpServer io: PhantomData, addr: PhantomData, threads: num_cpus::get(), + backlog: 2048, keep_alive: None, factory: Arc::new(factory), workers: Vec::new(), + sockets: HashMap::new(), } } @@ -143,6 +148,18 @@ impl HttpServer self } + /// Set the maximum number of pending connections. + /// + /// This refers to the number of clients that can be waiting to be served. + /// Exceeding this number results in the client getting an error when + /// attempting to connect. It should only affect servers under significant load. + /// + /// Generally set in the 64-2048 range. Default value is 2048. + pub fn backlog(mut self, num: i32) -> Self { + self.backlog = num; + self + } + /// Set server keep-alive setting. /// /// By default keep alive is enabled. @@ -160,10 +177,22 @@ impl HttpServer /// Start listening for incomming connections from a stream. /// /// This method uses only one thread for handling incoming connections. - pub fn serve_incoming(mut self, stream: S, secure: bool) -> io::Result + pub fn start_incoming(mut self, stream: S, secure: bool) -> io::Result where Self: ActorAddress, S: Stream + 'static { + if !self.sockets.is_empty() { + let addrs: Vec<(net::SocketAddr, Socket)> = self.sockets.drain().collect(); + let settings = ServerSettings::new(Some(addrs[0].0), false); + let workers = self.start_workers(&settings, &StreamHandlerType::Normal); + + // start acceptors threads + for (addr, sock) in addrs { + info!("Starting http server on {}", addr); + start_accept_thread(sock, addr, workers.clone()); + } + } + // set server settings let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); let settings = ServerSettings::new(Some(addr), secure); @@ -181,52 +210,53 @@ impl HttpServer })) } - fn bind(&self, addr: S) - -> io::Result> - { + /// The socket address to bind + /// + /// To mind multiple addresses this method can be call multiple times. + pub fn bind(mut self, addr: S) -> io::Result { let mut err = None; - let mut sockets = Vec::new(); + let mut succ = false; if let Ok(iter) = addr.to_socket_addrs() { for addr in iter { - match addr { + let socket = match addr { net::SocketAddr::V4(a) => { let socket = Socket::new(Domain::ipv4(), Type::stream(), None)?; match socket.bind(&a.into()) { - Ok(_) => { - socket.listen(1024) - .expect("failed to set socket backlog"); - socket.set_reuse_address(true) - .expect("failed to set socket reuse address"); - sockets.push((addr, socket)); - }, - Err(e) => err = Some(e), + Ok(_) => socket, + Err(e) => { + err = Some(e); + continue; + } } } net::SocketAddr::V6(a) => { let socket = Socket::new(Domain::ipv6(), Type::stream(), None)?; match socket.bind(&a.into()) { - Ok(_) => { - socket.listen(1024) - .expect("failed to set socket backlog"); - socket.set_reuse_address(true) - .expect("failed to set socket reuse address"); - sockets.push((addr, socket)) + Ok(_) => socket, + Err(e) => { + err = Some(e); + continue } - Err(e) => err = Some(e), } } - } + }; + succ = true; + socket.listen(self.backlog) + .expect("failed to set socket backlog"); + socket.set_reuse_address(true) + .expect("failed to set socket reuse address"); + self.sockets.insert(addr, socket); } } - if sockets.is_empty() { + if !succ { if let Some(e) = err.take() { Err(e) } else { Err(io::Error::new(io::ErrorKind::Other, "Can not bind to address.")) } } else { - Ok(sockets) + Ok(self) } } @@ -265,26 +295,26 @@ impl HttpServer { /// Start listening for incomming connections. /// - /// This methods converts address to list of `SocketAddr` - /// then binds to all available addresses. - /// It also starts number of http handler workers in seperate threads. + /// This method starts number of http handler workers in seperate threads. /// For each address this method starts separate thread which does `accept()` in a loop. - pub fn serve(mut self, addr: S) -> io::Result - where Self: ActorAddress, - S: net::ToSocketAddrs, + pub fn start(mut self) -> io::Result> { - let addrs = self.bind(addr)?; - let settings = ServerSettings::new(Some(addrs[0].0), false); - let workers = self.start_workers(&settings, &StreamHandlerType::Normal); + if self.sockets.is_empty() { + Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound")) + } else { + let addrs: Vec<(net::SocketAddr, Socket)> = self.sockets.drain().collect(); + let settings = ServerSettings::new(Some(addrs[0].0), false); + let workers = self.start_workers(&settings, &StreamHandlerType::Normal); - // start acceptors threads - for (addr, sock) in addrs { - info!("Starting http server on {}", addr); - start_accept_thread(sock, addr, workers.clone()); + // start acceptors threads + for (addr, sock) in addrs { + info!("Starting http server on {}", addr); + start_accept_thread(sock, addr, workers.clone()); + } + + // start http server actor + Ok(HttpServer::create(|_| {self})) } - - // start http server actor - Ok(HttpServer::create(|_| {self})) } } @@ -294,34 +324,34 @@ impl HttpServer, net::SocketAddr, H, V: IntoHttpHandler, { /// Start listening for incomming tls connections. - /// - /// This methods converts address to list of `SocketAddr` - /// then binds to all available addresses. - pub fn serve_tls(mut self, addr: S, pkcs12: ::Pkcs12) -> io::Result + pub fn start_tls(mut self, pkcs12: ::Pkcs12) -> io::Result where Self: ActorAddress, - S: net::ToSocketAddrs, { - let addrs = self.bind(addr)?; - let settings = ServerSettings::new(Some(addrs[0].0), false); - let acceptor = match TlsAcceptor::builder(pkcs12) { - Ok(builder) => { - match builder.build() { - Ok(acceptor) => acceptor, - Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err)) + if self.sockets.is_empty() { + Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound")) + } else { + let addrs: Vec<(net::SocketAddr, Socket)> = self.sockets.drain().collect(); + let settings = ServerSettings::new(Some(addrs[0].0), false); + let acceptor = match TlsAcceptor::builder(pkcs12) { + Ok(builder) => { + match builder.build() { + Ok(acceptor) => acceptor, + Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err)) + } } + Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err)) + }; + let workers = self.start_workers(&settings, &StreamHandlerType::Tls(acceptor)); + + // start acceptors threads + for (addr, sock) in addrs { + info!("Starting tls http server on {}", addr); + start_accept_thread(sock, addr, workers.clone()); } - Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err)) - }; - let workers = self.start_workers(&settings, &StreamHandlerType::Tls(acceptor)); - // start acceptors threads - for (addr, sock) in addrs { - info!("Starting tls http server on {}", addr); - start_accept_thread(sock, addr, workers.clone()); + // start http server actor + Ok(HttpServer::create(|_| {self})) } - - // start http server actor - Ok(HttpServer::create(|_| {self})) } } @@ -332,35 +362,37 @@ impl HttpServer, net::SocketAddr, H, { /// Start listening for incomming tls connections. /// - /// This methods converts address to list of `SocketAddr` - /// then binds to all available addresses. - pub fn serve_ssl(mut self, addr: S, identity: &ParsedPkcs12) -> io::Result + /// This method sets alpn protocols to "h2" and "http/1.1" + pub fn start_ssl(mut self, identity: &ParsedPkcs12) -> io::Result where Self: ActorAddress, - S: net::ToSocketAddrs, { - let addrs = self.bind(addr)?; - let settings = ServerSettings::new(Some(addrs[0].0), false); - let acceptor = match SslAcceptorBuilder::mozilla_intermediate( - SslMethod::tls(), &identity.pkey, &identity.cert, &identity.chain) - { - Ok(mut builder) => { - match builder.set_alpn_protocols(&[b"h2", b"http/1.1"]) { - Ok(_) => builder.build(), - Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err)), - } - }, - Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err)) - }; - let workers = self.start_workers(&settings, &StreamHandlerType::Alpn(acceptor)); + if self.sockets.is_empty() { + Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound")) + } else { + let addrs: Vec<(net::SocketAddr, Socket)> = self.sockets.drain().collect(); + let settings = ServerSettings::new(Some(addrs[0].0), false); + let acceptor = match SslAcceptorBuilder::mozilla_intermediate( + SslMethod::tls(), &identity.pkey, &identity.cert, &identity.chain) + { + Ok(mut builder) => { + match builder.set_alpn_protocols(&[b"h2", b"http/1.1"]) { + Ok(_) => builder.build(), + Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err)), + } + }, + Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err)) + }; + let workers = self.start_workers(&settings, &StreamHandlerType::Alpn(acceptor)); - // start acceptors threads - for (addr, sock) in addrs { - info!("Starting tls http server on {}", addr); - start_accept_thread(sock, addr, workers.clone()); + // start acceptors threads + for (addr, sock) in addrs { + info!("Starting tls http server on {}", addr); + start_accept_thread(sock, addr, workers.clone()); + } + + // start http server actor + Ok(HttpServer::create(|_| {self})) } - - // start http server actor - Ok(HttpServer::create(|_| {self})) } } diff --git a/tests/test_server.rs b/tests/test_server.rs index 53dacbbaa..3dbc924bb 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -18,7 +18,7 @@ fn test_serve() { let srv = HttpServer::new( || vec![Application::new() .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))]); - srv.serve::<_, ()>("127.0.0.1:58902").unwrap(); + srv.bind("127.0.0.1:58902").unwrap().start().unwrap(); sys.run(); }); assert!(reqwest::get("http://localhost:58902/").unwrap().status().is_success()); @@ -39,7 +39,7 @@ fn test_serve_incoming() { || Application::new() .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))); let tcp = TcpListener::from_listener(tcp, &addr2, Arbiter::handle()).unwrap(); - srv.serve_incoming::<_, ()>(tcp.incoming(), false).unwrap(); + srv.start_incoming::<_, ()>(tcp.incoming(), false).unwrap(); sys.run(); }); @@ -89,7 +89,8 @@ fn test_middlewares() { response: Arc::clone(&act_num2), finish: Arc::clone(&act_num3)}) .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))]) - .serve::<_, ()>("127.0.0.1:58904").unwrap(); + .bind("127.0.0.1:58904").unwrap() + .start().unwrap(); sys.run(); }); From 9ed4159c0ccefbcf0115ec2b7af05c2fdeb1e21d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Dec 2017 13:06:41 -0800 Subject: [PATCH 115/279] update examples --- examples/tls/src/main.rs | 3 ++- examples/websocket-chat/src/main.rs | 7 ++++--- guide/src/qs_3_5.md | 1 + src/lib.rs | 3 ++- src/server.rs | 13 ++++--------- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/examples/tls/src/main.rs b/examples/tls/src/main.rs index 2e0d55e3f..4ab0cbca2 100644 --- a/examples/tls/src/main.rs +++ b/examples/tls/src/main.rs @@ -42,7 +42,8 @@ fn main() { .header("LOCATION", "/index.html") .body(Body::Empty) }))) - .serve_ssl::<_, ()>("127.0.0.1:8443", &pkcs12).unwrap(); + .bind("127.0.0.1:8443").unwrap() + .start_ssl(&pkcs12).unwrap(); println!("Started http server: 127.0.0.1:8443"); let _ = sys.run(); diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs index b553b5f24..1f168eb84 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -207,13 +207,14 @@ fn main() { .header("LOCATION", "/static/websocket.html") .body(Body::Empty) })) - // websocket + // websocket .resource("/ws/", |r| r.route().f(chat_route)) - // static resources + // static resources .resource("/static/{tail:.*}", |r| r.h(fs::StaticFiles::new("tail", "static/", true))) }) - .serve::<_, ()>("127.0.0.1:8080").unwrap(); + .bind("127.0.0.1:8080").unwrap() + .start().unwrap(); let _ = sys.run(); } diff --git a/guide/src/qs_3_5.md b/guide/src/qs_3_5.md index 36f843fb5..4afbff4fb 100644 --- a/guide/src/qs_3_5.md +++ b/guide/src/qs_3_5.md @@ -1,6 +1,7 @@ # Server + ## Multi-threading Http server automatically starts number of http workers, by default diff --git a/src/lib.rs b/src/lib.rs index 92ed2ea52..74b33f1e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,8 @@ //! HttpServer::new( //! || Application::new() //! .resource("/{name}", |r| r.f(index))) -//! .serve("127.0.0.1:8080"); +//! .bind("127.0.0.1:8080")? +//! .start() //! } //! ``` //! diff --git a/src/server.rs b/src/server.rs index f787621f5..5961a40a1 100644 --- a/src/server.rs +++ b/src/server.rs @@ -177,9 +177,8 @@ impl HttpServer /// Start listening for incomming connections from a stream. /// /// This method uses only one thread for handling incoming connections. - pub fn start_incoming(mut self, stream: S, secure: bool) -> io::Result - where Self: ActorAddress, - S: Stream + 'static + pub fn start_incoming(mut self, stream: S, secure: bool) -> io::Result> + where S: Stream + 'static { if !self.sockets.is_empty() { let addrs: Vec<(net::SocketAddr, Socket)> = self.sockets.drain().collect(); @@ -324,9 +323,7 @@ impl HttpServer, net::SocketAddr, H, V: IntoHttpHandler, { /// Start listening for incomming tls connections. - pub fn start_tls(mut self, pkcs12: ::Pkcs12) -> io::Result - where Self: ActorAddress, - { + pub fn start_tls(mut self, pkcs12: ::Pkcs12) -> io::Result> { if self.sockets.is_empty() { Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound")) } else { @@ -363,9 +360,7 @@ impl HttpServer, net::SocketAddr, H, /// Start listening for incomming tls connections. /// /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn start_ssl(mut self, identity: &ParsedPkcs12) -> io::Result - where Self: ActorAddress, - { + pub fn start_ssl(mut self, identity: &ParsedPkcs12) -> io::Result> { if self.sockets.is_empty() { Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound")) } else { From 26af6040ff84e4030e1bec06d1ae6df9f343a810 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Dec 2017 13:26:43 -0800 Subject: [PATCH 116/279] update tests --- tests/test_server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_server.rs b/tests/test_server.rs index 3dbc924bb..71f0e0f9e 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -39,7 +39,7 @@ fn test_serve_incoming() { || Application::new() .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))); let tcp = TcpListener::from_listener(tcp, &addr2, Arbiter::handle()).unwrap(); - srv.start_incoming::<_, ()>(tcp.incoming(), false).unwrap(); + srv.start_incoming(tcp.incoming(), false).unwrap(); sys.run(); }); From 3e8a6c39881c5867fd72fb2eb9619e2292fe6968 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Dec 2017 13:41:52 -0800 Subject: [PATCH 117/279] add tera example --- examples/basic.rs | 2 +- examples/template_tera/Cargo.toml | 10 +++++ examples/template_tera/src/main.rs | 44 +++++++++++++++++++++ examples/template_tera/templates/index.html | 17 ++++++++ examples/template_tera/templates/user.html | 13 ++++++ 5 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 examples/template_tera/Cargo.toml create mode 100644 examples/template_tera/src/main.rs create mode 100644 examples/template_tera/templates/index.html create mode 100644 examples/template_tera/templates/user.html diff --git a/examples/basic.rs b/examples/basic.rs index 9c182817c..d6b8b3a9e 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -56,7 +56,7 @@ fn with_param(req: HttpRequest) -> Result fn main() { ::std::env::set_var("RUST_LOG", "actix_web=info"); let _ = env_logger::init(); - let sys = actix::System::new("ws-example"); + let sys = actix::System::new("basic-example"); HttpServer::new( || Application::new() diff --git a/examples/template_tera/Cargo.toml b/examples/template_tera/Cargo.toml new file mode 100644 index 000000000..b5ce86cad --- /dev/null +++ b/examples/template_tera/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "template-tera" +version = "0.1.0" +authors = ["Nikolay Kim "] + +[dependencies] +env_logger = "0.4" +actix = "^0.3.1" +actix-web = { git = "https://github.com/actix/actix-web.git" } +tera = "*" diff --git a/examples/template_tera/src/main.rs b/examples/template_tera/src/main.rs new file mode 100644 index 000000000..97b9d4812 --- /dev/null +++ b/examples/template_tera/src/main.rs @@ -0,0 +1,44 @@ +extern crate actix; +extern crate actix_web; +extern crate env_logger; +#[macro_use] +extern crate tera; +use actix_web::*; + +struct State { + template: tera::Tera, // <- store tera template in application state +} + +fn index(req: HttpRequest) -> HttpResponse { + let s = if let Some(name) = req.query().get("name") { // <- submitted form + let mut ctx = tera::Context::new(); + ctx.add("name", name); + ctx.add("text", &"Welcome!".to_owned()); + req.state().template.render("user.html", &ctx).unwrap() + } else { + req.state().template.render("index.html", &tera::Context::new()).unwrap() + }; + httpcodes::HTTPOk.build() + .content_type("text/html") + .body(s) + .unwrap() +} + +fn main() { + ::std::env::set_var("RUST_LOG", "actix_web=info"); + let _ = env_logger::init(); + let sys = actix::System::new("tera-example"); + + HttpServer::new(|| { + let tera = compile_templates!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")); + + Application::with_state(State{template: tera}) + // enable logger + .middleware(middlewares::Logger::default()) + .resource("/", |r| r.method(Method::GET).f(index))}) + .bind("127.0.0.1:8080").unwrap() + .start().unwrap(); + + println!("Started http server: 127.0.0.1:8080"); + let _ = sys.run(); +} diff --git a/examples/template_tera/templates/index.html b/examples/template_tera/templates/index.html new file mode 100644 index 000000000..d8a47bc09 --- /dev/null +++ b/examples/template_tera/templates/index.html @@ -0,0 +1,17 @@ + + + + + Actix web + + +

    Welcome!

    +

    +

    What is your name?

    +
    +
    +

    +
    +

    + + diff --git a/examples/template_tera/templates/user.html b/examples/template_tera/templates/user.html new file mode 100644 index 000000000..cb5328915 --- /dev/null +++ b/examples/template_tera/templates/user.html @@ -0,0 +1,13 @@ + + + + + Actix web + + +

    Hi, {{ name }}!

    +

    + {{ text }} +

    + + From fde94bfe95b13aca78750d2e84d5193290904a4a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Dec 2017 16:25:26 -0800 Subject: [PATCH 118/279] added diesel example --- examples/diesel/.env | 1 + examples/diesel/Cargo.toml | 18 +++ examples/diesel/README.md | 15 +++ .../20170124012402_create_users/down.sql | 1 + .../20170124012402_create_users/up.sql | 4 + examples/diesel/src/main.rs | 114 ++++++++++++++++++ examples/diesel/src/models.rs | 14 +++ examples/diesel/src/schema.rs | 6 + examples/diesel/test.db | Bin 0 -> 20480 bytes 9 files changed, 173 insertions(+) create mode 100644 examples/diesel/.env create mode 100644 examples/diesel/Cargo.toml create mode 100644 examples/diesel/README.md create mode 100644 examples/diesel/migrations/20170124012402_create_users/down.sql create mode 100644 examples/diesel/migrations/20170124012402_create_users/up.sql create mode 100644 examples/diesel/src/main.rs create mode 100644 examples/diesel/src/models.rs create mode 100644 examples/diesel/src/schema.rs create mode 100644 examples/diesel/test.db diff --git a/examples/diesel/.env b/examples/diesel/.env new file mode 100644 index 000000000..1fbc5af72 --- /dev/null +++ b/examples/diesel/.env @@ -0,0 +1 @@ +DATABASE_URL=file:test.db diff --git a/examples/diesel/Cargo.toml b/examples/diesel/Cargo.toml new file mode 100644 index 000000000..40e78e307 --- /dev/null +++ b/examples/diesel/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "diesel-example" +version = "0.1.0" +authors = ["Nikolay Kim "] + +[dependencies] +env_logger = "0.4" +actix = "^0.3.1" +actix-web = { git = "https://github.com/actix/actix-web.git" } + +futures = "0.1" +uuid = { version = "0.5", features = ["serde", "v4"] } +serde = "1.0" +serde_json = "1.0" +serde_derive = "1.0" + +diesel = { version = "1.0.0-beta1", features = ["sqlite"] } +dotenv = "0.10" diff --git a/examples/diesel/README.md b/examples/diesel/README.md new file mode 100644 index 000000000..129c4fbe3 --- /dev/null +++ b/examples/diesel/README.md @@ -0,0 +1,15 @@ +Diesel's `Getting Started` guide using SQLite for Actix web + +## Usage + +install `diesel_cli` + +``` +cargo install diesel_cli --no-default-features --features sqlite +``` + + +``` +$ echo "DATABASE_URL=file:test.db" > .env +$ diesel migration run +``` diff --git a/examples/diesel/migrations/20170124012402_create_users/down.sql b/examples/diesel/migrations/20170124012402_create_users/down.sql new file mode 100644 index 000000000..9951735c4 --- /dev/null +++ b/examples/diesel/migrations/20170124012402_create_users/down.sql @@ -0,0 +1 @@ +DROP TABLE users diff --git a/examples/diesel/migrations/20170124012402_create_users/up.sql b/examples/diesel/migrations/20170124012402_create_users/up.sql new file mode 100644 index 000000000..d88d44fb7 --- /dev/null +++ b/examples/diesel/migrations/20170124012402_create_users/up.sql @@ -0,0 +1,4 @@ +CREATE TABLE users ( + id VARCHAR NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL +) diff --git a/examples/diesel/src/main.rs b/examples/diesel/src/main.rs new file mode 100644 index 000000000..331c5f800 --- /dev/null +++ b/examples/diesel/src/main.rs @@ -0,0 +1,114 @@ +//! Actix web diesel example +//! +//! Diesel does not support tokio, so we have to run it in separate threads. +//! Actix supports sync actors by default, so we going to create sync actor that will +//! use diesel. Technically sync actors are worker style actors, multiple of them +//! can run in parallele and process messages from same queue. +extern crate serde; +extern crate serde_json; +#[macro_use] +extern crate serde_derive; +#[macro_use] +extern crate diesel; +extern crate uuid; +extern crate futures; +extern crate actix; +extern crate actix_web; +extern crate env_logger; + +use actix::prelude::*; +use actix_web::*; +use futures::future::{Future, ok}; +use diesel::Connection; +use diesel::sqlite::SqliteConnection; +use diesel::prelude::*; + +mod models; +mod schema; + + +struct State { + db: SyncAddress, +} + +fn index(req: HttpRequest) -> Box> { + let name = &req.match_info()["name"]; + + Box::new( + req.state().db.call_fut(CreatePerson{name: name.to_owned()}) + .and_then(|res| { + match res { + Ok(person) => ok(httpcodes::HTTPOk.build().json(person).unwrap()), + Err(_) => ok(httpcodes::HTTPInternalServerError.response()) + } + }) + .map_err(|e| error::ErrorInternalServerError(e).into())) +} + +/// This is db executor actor. We are going to run 3 of them in parallele. +struct DbExecutor(SqliteConnection); + +/// This is only message that this actor can handle, but it is easy to extend number of +/// messages. +struct CreatePerson { + name: String, +} + +impl ResponseType for CreatePerson { + type Item = models::User; + type Error = Error; +} + +impl Actor for DbExecutor { + type Context = SyncContext; +} + +impl Handler for DbExecutor { + fn handle(&mut self, msg: CreatePerson, _: &mut Self::Context) + -> Response + { + use self::schema::users::dsl::*; + + let uuid = format!("{}", uuid::Uuid::new_v4()); + let new_user = models::NewUser { + id: &uuid, + name: &msg.name, + }; + + diesel::insert_into(users) + .values(&new_user) + .execute(&self.0) + .expect("Error inserting person"); + + let mut items = users + .filter(id.eq(&uuid)) + .load::(&self.0) + .expect("Error loading person"); + + Self::reply(items.pop().unwrap()) + } +} + + +fn main() { + ::std::env::set_var("RUST_LOG", "actix_web=info"); + let _ = env_logger::init(); + let sys = actix::System::new("diesel-example"); + + // Start db executor actors + let addr = SyncArbiter::start(3, || { + DbExecutor(SqliteConnection::establish("test.db").unwrap()) + }); + + // Start http server + HttpServer::new(move || { + Application::with_state(State{db: addr.clone()}) + // enable logger + .middleware(middlewares::Logger::default()) + .resource("/{name}", |r| r.method(Method::GET).a(index))}) + .bind("127.0.0.1:8080").unwrap() + .start().unwrap(); + + println!("Started http server: 127.0.0.1:8080"); + let _ = sys.run(); +} diff --git a/examples/diesel/src/models.rs b/examples/diesel/src/models.rs new file mode 100644 index 000000000..315d59f13 --- /dev/null +++ b/examples/diesel/src/models.rs @@ -0,0 +1,14 @@ +use super::schema::users; + +#[derive(Serialize, Queryable)] +pub struct User { + pub id: String, + pub name: String, +} + +#[derive(Insertable)] +#[table_name = "users"] +pub struct NewUser<'a> { + pub id: &'a str, + pub name: &'a str, +} diff --git a/examples/diesel/src/schema.rs b/examples/diesel/src/schema.rs new file mode 100644 index 000000000..51aa40b89 --- /dev/null +++ b/examples/diesel/src/schema.rs @@ -0,0 +1,6 @@ +table! { + users (id) { + id -> Text, + name -> Text, + } +} diff --git a/examples/diesel/test.db b/examples/diesel/test.db new file mode 100644 index 0000000000000000000000000000000000000000..3afa1579ec71d061fd73a4bfbb498d853f58d024 GIT binary patch literal 20480 zcmeI%O>dMy7zgkHwykZ_r5+41Uglup5=&)(UAj$7#1^%2TPuYoJrN4CU6Sr@Tv+3^ zewE(shw-9*2+t;7oF&3`Rf&c^{009U<00Izz z00bZafxlRwrz~ljCY?Vilan((F8HXZy4rT&d!bK5?|H|k)g{_kp)}9vkr!;g@&da5 zCZzjOr$Y~d-90Zjrmy|ub&)bi`uvZi6EFGz~2w!S&DurFKVaWyrSn%D`xY@ z6!ALUnY>b~qDksEA~pmBAOHafKmY;|fB*y_009U<00RGl#HPdXfnK7fy6U%T?<{0h7NetUz zE#9hix^8Nx%x*hfUh<-x{QsBL yOQK$?Uv8^FJQo5GfB*y_009U<00Izz00bZafhz(HC6aEk6d*rRqUD>c0sI2R>)C_= literal 0 HcmV?d00001 From 625c4ad0db7bb1a7c2e22700a7ad0d443ff52f67 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Dec 2017 16:30:35 -0800 Subject: [PATCH 119/279] add more comments --- examples/diesel/src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/diesel/src/main.rs b/examples/diesel/src/main.rs index 331c5f800..1c9728d84 100644 --- a/examples/diesel/src/main.rs +++ b/examples/diesel/src/main.rs @@ -26,11 +26,12 @@ use diesel::prelude::*; mod models; mod schema; - +/// State with DbExecutor address struct State { db: SyncAddress, } +/// Async request handler fn index(req: HttpRequest) -> Box> { let name = &req.match_info()["name"]; From 1e1da5832fa404f150ac2edbd3fca23040909c39 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Dec 2017 16:40:33 -0800 Subject: [PATCH 120/279] better name --- examples/diesel/src/main.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/examples/diesel/src/main.rs b/examples/diesel/src/main.rs index 1c9728d84..07e99e0dd 100644 --- a/examples/diesel/src/main.rs +++ b/examples/diesel/src/main.rs @@ -16,12 +16,10 @@ extern crate actix; extern crate actix_web; extern crate env_logger; -use actix::prelude::*; use actix_web::*; -use futures::future::{Future, ok}; -use diesel::Connection; -use diesel::sqlite::SqliteConnection; +use actix::prelude::*; use diesel::prelude::*; +use futures::future::{Future, ok}; mod models; mod schema; @@ -36,7 +34,7 @@ fn index(req: HttpRequest) -> Box> let name = &req.match_info()["name"]; Box::new( - req.state().db.call_fut(CreatePerson{name: name.to_owned()}) + req.state().db.call_fut(CreateUser{name: name.to_owned()}) .and_then(|res| { match res { Ok(person) => ok(httpcodes::HTTPOk.build().json(person).unwrap()), @@ -51,11 +49,11 @@ struct DbExecutor(SqliteConnection); /// This is only message that this actor can handle, but it is easy to extend number of /// messages. -struct CreatePerson { +struct CreateUser { name: String, } -impl ResponseType for CreatePerson { +impl ResponseType for CreateUser { type Item = models::User; type Error = Error; } @@ -64,9 +62,9 @@ impl Actor for DbExecutor { type Context = SyncContext; } -impl Handler for DbExecutor { - fn handle(&mut self, msg: CreatePerson, _: &mut Self::Context) - -> Response +impl Handler for DbExecutor { + fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) + -> Response { use self::schema::users::dsl::*; @@ -90,7 +88,6 @@ impl Handler for DbExecutor { } } - fn main() { ::std::env::set_var("RUST_LOG", "actix_web=info"); let _ = env_logger::init(); From 3f0e7456c0ad1a025adf25af95500e3062394a44 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Dec 2017 16:42:58 -0800 Subject: [PATCH 121/279] update examples links --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 532c3692b..60c50bf45 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,12 @@ Some basic benchmarks could be found in this [respository](https://github.com/fa * [Basic](https://github.com/actix/actix-web/tree/master/examples/basic.rs) * [Stateful](https://github.com/actix/actix-web/tree/master/examples/state.rs) -* [Mulitpart streams](https://github.com/actix/actix-web/tree/master/examples/multipart) +* [Mulitpart streams](https://github.com/actix/actix-web/tree/master/examples/multipart/) * [Simple websocket session](https://github.com/actix/actix-web/tree/master/examples/websocket.rs) -* [Tcp/Websocket chat](https://github.com/actix/actix-web/tree/master/examples/websocket-chat) +* [Tera templates](https://github.com/actix/actix-web/tree/master/examples/template_tera/) +* [Diesel integration](https://github.com/actix/actix-web/tree/master/examples/diesel/) +* [SSL / HTTP/2.0](https://github.com/actix/actix-web/tree/master/examples/tls/) +* [Tcp/Websocket chat](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/) * [SockJS Server](https://github.com/actix/actix-sockjs) ## License From e9a3845e26c65e7b54e074ade771767b54b06483 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Dec 2017 16:48:30 -0800 Subject: [PATCH 122/279] update license in readme --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 60c50bf45..79c31d900 100644 --- a/README.md +++ b/README.md @@ -58,4 +58,11 @@ Some basic benchmarks could be found in this [respository](https://github.com/fa ## License -Actix web is licensed under the [Apache-2.0 license](http://opensource.org/licenses/APACHE-2.0). +This project is licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or + http://opensource.org/licenses/MIT) + +at your option. From 2124730e0a03498ec2af54ca5db49309ef8a80f2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Dec 2017 18:56:58 -0800 Subject: [PATCH 123/279] guide update --- Makefile | 2 +- guide/src/qs_3_5.md | 30 ++++++++++++++++- src/server.rs | 78 +++++++++++++++++++++++++-------------------- 3 files changed, 73 insertions(+), 37 deletions(-) diff --git a/Makefile b/Makefile index fc6071083..fdc3cbbc0 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .PHONY: default build test doc book clean -CARGO_FLAGS := --features "$(FEATURES)" +CARGO_FLAGS := --features "$(FEATURES) alpn" default: test diff --git a/guide/src/qs_3_5.md b/guide/src/qs_3_5.md index 4afbff4fb..70eb48095 100644 --- a/guide/src/qs_3_5.md +++ b/guide/src/qs_3_5.md @@ -1,5 +1,33 @@ # Server +[*HttpServer*](../actix_web/struct.HttpServer.html) type is responsible for +serving http requests. *HttpServer* accept applicaiton factory as a parameter, +Application factory must have `Send` + `Sync` bounderies. More about that in +*multi-threading* section. To bind to specific socket address `bind()` must be used. +This method could be called multiple times. To start http server one of the *start* +methods could be used. `start()` method start simple server, `start_tls()` or `start_ssl()` +starts ssl server. *HttpServer* is an actix actor, it has to be initialized +within properly configured actix system: + +```rust +# extern crate actix; +# extern crate actix_web; +use actix::*; +use actix_web::*; + +fn main() { + let sys = actix::System::new("guide"); + + HttpServer::new( + || Application::new() + .resource("/", |r| r.f(|_| httpcodes::HTTPOk))) + .bind("127.0.0.1:59080").unwrap() + .start().unwrap(); + +# actix::Arbiter::system().send(actix::msgs::SystemExit(0)); + let _ = sys.run(); +} +``` ## Multi-threading @@ -18,7 +46,7 @@ use actix_web::*; fn main() { HttpServer::::new( || Application::new() - .resource("/", |r| r.f(|r| httpcodes::HTTPOk))) + .resource("/", |r| r.f(|_| httpcodes::HTTPOk))) .threads(4); // <- Start 4 threads } ``` diff --git a/src/server.rs b/src/server.rs index 5961a40a1..9bec090a2 100644 --- a/src/server.rs +++ b/src/server.rs @@ -174,41 +174,6 @@ impl HttpServer self } - /// Start listening for incomming connections from a stream. - /// - /// This method uses only one thread for handling incoming connections. - pub fn start_incoming(mut self, stream: S, secure: bool) -> io::Result> - where S: Stream + 'static - { - if !self.sockets.is_empty() { - let addrs: Vec<(net::SocketAddr, Socket)> = self.sockets.drain().collect(); - let settings = ServerSettings::new(Some(addrs[0].0), false); - let workers = self.start_workers(&settings, &StreamHandlerType::Normal); - - // start acceptors threads - for (addr, sock) in addrs { - info!("Starting http server on {}", addr); - start_accept_thread(sock, addr, workers.clone()); - } - } - - // set server settings - let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); - let settings = ServerSettings::new(Some(addr), secure); - let mut apps: Vec<_> = (*self.factory)().into_iter().map(|h| h.into_handler()).collect(); - for app in &mut apps { - app.server_settings(settings.clone()); - } - self.h = Some(Rc::new(WorkerSettings::new(apps, self.keep_alive))); - - // start server - Ok(HttpServer::create(move |ctx| { - ctx.add_stream(stream.map( - move |(t, _)| IoStream{io: t, peer: None, http2: false})); - self - })) - } - /// The socket address to bind /// /// To mind multiple addresses this method can be call multiple times. @@ -391,6 +356,49 @@ impl HttpServer, net::SocketAddr, H, } } +impl HttpServer + where A: 'static, + T: AsyncRead + AsyncWrite + 'static, + H: HttpHandler, + U: IntoIterator + 'static, + V: IntoHttpHandler, +{ + /// Start listening for incomming connections from a stream. + /// + /// This method uses only one thread for handling incoming connections. + pub fn start_incoming(mut self, stream: S, secure: bool) -> io::Result> + where S: Stream + 'static + { + if !self.sockets.is_empty() { + let addrs: Vec<(net::SocketAddr, Socket)> = self.sockets.drain().collect(); + let settings = ServerSettings::new(Some(addrs[0].0), false); + let workers = self.start_workers(&settings, &StreamHandlerType::Normal); + + // start acceptors threads + for (addr, sock) in addrs { + info!("Starting http server on {}", addr); + start_accept_thread(sock, addr, workers.clone()); + } + } + + // set server settings + let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let settings = ServerSettings::new(Some(addr), secure); + let mut apps: Vec<_> = (*self.factory)().into_iter().map(|h| h.into_handler()).collect(); + for app in &mut apps { + app.server_settings(settings.clone()); + } + self.h = Some(Rc::new(WorkerSettings::new(apps, self.keep_alive))); + + // start server + Ok(HttpServer::create(move |ctx| { + ctx.add_stream(stream.map( + move |(t, _)| IoStream{io: t, peer: None, http2: false})); + self + })) + } +} + struct IoStream { io: T, peer: Option, From 56fd0881634aef9fbab09b1f09dc1f3336c178de Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Dec 2017 19:38:16 -0800 Subject: [PATCH 124/279] added database integration guide section --- examples/diesel/src/main.rs | 2 +- guide/src/SUMMARY.md | 1 + guide/src/qs_14.md | 127 ++++++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 guide/src/qs_14.md diff --git a/examples/diesel/src/main.rs b/examples/diesel/src/main.rs index 07e99e0dd..9e03873cd 100644 --- a/examples/diesel/src/main.rs +++ b/examples/diesel/src/main.rs @@ -37,7 +37,7 @@ fn index(req: HttpRequest) -> Box> req.state().db.call_fut(CreateUser{name: name.to_owned()}) .and_then(|res| { match res { - Ok(person) => ok(httpcodes::HTTPOk.build().json(person).unwrap()), + Ok(user) => ok(httpcodes::HTTPOk.build().json(user).unwrap()), Err(_) => ok(httpcodes::HTTPInternalServerError.response()) } }) diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index e260000a7..275392118 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -12,3 +12,4 @@ - [Middlewares](./qs_10.md) - [Static file handling](./qs_12.md) - [HTTP/2](./qs_13.md) +- [Database integration](./qs_14.md) diff --git a/guide/src/qs_14.md b/guide/src/qs_14.md new file mode 100644 index 000000000..38ea411cf --- /dev/null +++ b/guide/src/qs_14.md @@ -0,0 +1,127 @@ +# Database integration + +## Diesel + +At the moment of 1.0 release Diesel does not support asynchronous operations. +But it possible to use `actix` synchronous actor as an db interface api. +Multipl sync actors could be started, in this case all of this actor +process messages from same queu (sync actors actually work mpmc mode). + +Let's create simple db api that can insert new user row into sqlite table. +We need to define sync actor and connection that this actor will use. Same approach +could used for other databases: + +```rust,ignore +use actix::prelude::*;* + +struct DbExecutor(SqliteConnection); + +impl Actor for DbExecutor { + type Context = SyncContext; +} +``` + +This is definition of our actor. Now we need to define *create user* message. + +```rust,ignore +struct CreateUser { + name: String, +} + +impl ResponseType for CreateUser { + type Item = models::User; + type Error = Error; +} +``` + +We can send `CreateUser` message to `DbExecutor` actor, and as result we can get +`User` model. Now we need to define actual handler for this message. + +```rust,ignore +impl Handler for DbExecutor { + fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Response + { + use self::schema::users::dsl::*; + + // Create insertion model + let uuid = format!("{}", uuid::Uuid::new_v4()); + let new_user = models::NewUser { + id: &uuid, + name: &msg.name, + }; + + // normal diesl operations + diesel::insert_into(users) + .values(&new_user) + .execute(&self.0) + .expect("Error inserting person"); + + let mut items = users + .filter(id.eq(&uuid)) + .load::(&self.0) + .expect("Error loading person"); + + Self::reply(items.pop().unwrap()) + } +} +``` + +That is it. Now we can use *DbExecutor* actor from any http handler or middleware. +All we need is to start *DbExecutor* actors and store address in state where http endpoint +can access it. + + +```rust,ignore +/// This is state where we sill store *DbExecutor* address. +struct State { + db: SyncAddress, +} + +fn main() { + let sys = actix::System::new("diesel-example"); + + // Start 3 db executors + let addr = SyncArbiter::start(3, || { + DbExecutor(SqliteConnection::establish("test.db").unwrap()) + }); + + // Start http server + HttpServer::new(move || { + Application::with_state(State{db: addr.clone()}) + .resource("/{name}", |r| r.method(Method::GET).a(index))}) + .bind("127.0.0.1:8080").unwrap() + .start().unwrap(); + + println!("Started http server: 127.0.0.1:8080"); + let _ = sys.run(); +} +``` + +And finally we can use this handler function. We get message response +asynchronously, so handler needs to return future object, also `Route::a()` needs to be +used for async handler registration. + + +```rust,ignore +/// Async handler +fn index(req: HttpRequest) -> Box> { + let name = &req.match_info()["name"]; + + Box::new( + // Send message to `DbExecutor` actor + req.state().db.call_fut(CreateUser{name: name.to_owned()}) + .and_then(|res| { + match res { + Ok(user) => ok(HTTPOk.build().json(user)), + Err(_) => ok(HTTPInternalServerError.response()) + } + }) + .map_err(|e| error::ErrorInternalServerError(e).into())) +} +``` + +Full example is available in +[examples repository](https://github.com/actix/actix-web/tree/master/examples/diesel/). + +More information on sync actors could be found in +[actix documentation](https://docs.rs/actix/0.3.3/actix/sync/index.html). From 669975df75ac9a6da9dccf69ef0e650effa343ae Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Dec 2017 20:00:57 -0800 Subject: [PATCH 125/279] fix typos --- guide/src/qs_14.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/guide/src/qs_14.md b/guide/src/qs_14.md index 38ea411cf..e92375c43 100644 --- a/guide/src/qs_14.md +++ b/guide/src/qs_14.md @@ -3,13 +3,13 @@ ## Diesel At the moment of 1.0 release Diesel does not support asynchronous operations. -But it possible to use `actix` synchronous actor as an db interface api. -Multipl sync actors could be started, in this case all of this actor -process messages from same queu (sync actors actually work mpmc mode). +But it possible to use `actix` synchronous actor system as a db interface api. +Multiple sync actors could be run in parallel, in this case all of this actors +process messages from the same queue (sync actors actually work in mpmc mode). Let's create simple db api that can insert new user row into sqlite table. We need to define sync actor and connection that this actor will use. Same approach -could used for other databases: +could used for other databases. ```rust,ignore use actix::prelude::*;* @@ -21,7 +21,7 @@ impl Actor for DbExecutor { } ``` -This is definition of our actor. Now we need to define *create user* message. +This is definition of our actor. Now we need to define *create user* message and response. ```rust,ignore struct CreateUser { @@ -35,10 +35,11 @@ impl ResponseType for CreateUser { ``` We can send `CreateUser` message to `DbExecutor` actor, and as result we can get -`User` model. Now we need to define actual handler for this message. +`User` model. Now we need to define actual handler implementation for this message. ```rust,ignore impl Handler for DbExecutor { + fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Response { use self::schema::users::dsl::*; @@ -67,10 +68,9 @@ impl Handler for DbExecutor { ``` That is it. Now we can use *DbExecutor* actor from any http handler or middleware. -All we need is to start *DbExecutor* actors and store address in state where http endpoint +All we need is to start *DbExecutor* actors and store address in state where http handler can access it. - ```rust,ignore /// This is state where we sill store *DbExecutor* address. struct State { @@ -97,7 +97,7 @@ fn main() { } ``` -And finally we can use this handler function. We get message response +And finally we can use this state in handler function. We get message response asynchronously, so handler needs to return future object, also `Route::a()` needs to be used for async handler registration. From 64dc6c577159d1d00a0d5e19ad4d7ea8299cf855 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Dec 2017 20:03:42 -0800 Subject: [PATCH 126/279] fix typos --- examples/diesel/src/main.rs | 2 +- guide/src/qs_14.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/diesel/src/main.rs b/examples/diesel/src/main.rs index 9e03873cd..15f0cc1bf 100644 --- a/examples/diesel/src/main.rs +++ b/examples/diesel/src/main.rs @@ -3,7 +3,7 @@ //! Diesel does not support tokio, so we have to run it in separate threads. //! Actix supports sync actors by default, so we going to create sync actor that will //! use diesel. Technically sync actors are worker style actors, multiple of them -//! can run in parallele and process messages from same queue. +//! can run in parallel and process messages from same queue. extern crate serde; extern crate serde_json; #[macro_use] diff --git a/guide/src/qs_14.md b/guide/src/qs_14.md index e92375c43..2716bf854 100644 --- a/guide/src/qs_14.md +++ b/guide/src/qs_14.md @@ -4,8 +4,8 @@ At the moment of 1.0 release Diesel does not support asynchronous operations. But it possible to use `actix` synchronous actor system as a db interface api. -Multiple sync actors could be run in parallel, in this case all of this actors -process messages from the same queue (sync actors actually work in mpmc mode). +Technically sync actors are worker style actors, multiple of them +can be run in parallel and process messages from same queue (sync actors work in mpmc mode). Let's create simple db api that can insert new user row into sqlite table. We need to define sync actor and connection that this actor will use. Same approach From 0cab8730669ba0a854a46ec9274cab48cad804e8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Dec 2017 21:58:38 -0800 Subject: [PATCH 127/279] make payload sender public --- src/lib.rs | 1 + src/payload.rs | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 74b33f1e3..202da08a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -167,6 +167,7 @@ pub mod dev { pub use handler::Handler; pub use router::{Router, Pattern}; pub use pipeline::Pipeline; + pub use payload::{PayloadSender, PayloadWriter}; pub use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; pub use param::{FromParam, Params}; pub use server::ServerSettings; diff --git a/src/payload.rs b/src/payload.rs index 6d81e2f6e..3d0690da8 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -45,6 +45,7 @@ impl fmt::Debug for PayloadItem { /// Stream of byte chunks /// /// Payload stores chunks in vector. First chunk can be received with `.readany()` method. +/// Payload stream is not thread safe. #[derive(Debug)] pub struct Payload { inner: Rc>, @@ -52,7 +53,14 @@ pub struct Payload { impl Payload { - pub(crate) fn new(eof: bool) -> (PayloadSender, Payload) { + /// Create payload stream. + /// + /// This method construct two objects responsible for bytes stream generation. + /// + /// * `PayloadSender` - *Sender* side of the stream + /// + /// * `Payload` - *Receiver* side of the stream + pub fn new(eof: bool) -> (PayloadSender, Payload) { let shared = Rc::new(RefCell::new(Inner::new(eof))); (PayloadSender{inner: Rc::downgrade(&shared)}, Payload{inner: shared}) @@ -138,17 +146,23 @@ impl Stream for Payload { } } -pub(crate) trait PayloadWriter { +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); + /// Get estimated available capacity fn capacity(&self) -> usize; } -pub(crate) struct PayloadSender { +/// Sender part of the payload stream +pub struct PayloadSender { inner: Weak>, } From f3b853f2249a89f2e69385059fea4bc58ab8d342 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Dec 2017 00:18:57 -0800 Subject: [PATCH 128/279] refactor payload --- examples/basic.rs | 9 ++- guide/src/qs_7.md | 43 +++++++++++++- src/error.rs | 11 ---- src/h1.rs | 49 +++++++-------- src/httprequest.rs | 38 +++++------- src/lib.rs | 4 +- src/multipart.rs | 26 ++++---- src/payload.rs | 145 +++++++++++++++++++++++++++++++++------------ src/ws.rs | 24 ++++---- 9 files changed, 213 insertions(+), 136 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index d6b8b3a9e..4ceef9e13 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -5,6 +5,7 @@ extern crate actix; extern crate actix_web; extern crate env_logger; extern crate futures; +use futures::Stream; use actix_web::*; use actix_web::middlewares::RequestSession; @@ -13,11 +14,9 @@ use futures::future::{FutureResult, result}; /// simple handler fn index(mut req: HttpRequest) -> Result { println!("{:?}", req); - if let Some(payload) = req.payload_mut() { - if let Ok(ch) = payload.readany() { - if let futures::Async::Ready(Some(d)) = ch { - println!("{}", String::from_utf8_lossy(d.0.as_ref())); - } + if let Ok(ch) = req.payload_mut().readany().poll() { + if let futures::Async::Ready(Some(d)) = ch { + println!("{}", String::from_utf8_lossy(d.as_ref())); } } diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 7e8bd08a9..e3248068d 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -99,12 +99,11 @@ Enabling chunked encoding for *HTTP/2.0* responses is forbidden. ```rust # extern crate actix_web; use actix_web::*; -use actix_web::headers::ContentEncoding; fn index(req: HttpRequest) -> HttpResponse { HttpResponse::Ok() .chunked() - .body(Body::Streaming(Payload::empty().stream())).unwrap() + .body(Body::Streaming(payload::Payload::empty().stream())).unwrap() } # fn main() {} ``` @@ -123,4 +122,42 @@ fn index(req: HttpRequest) -> HttpResponse { ## Streaming request -[WIP] +Actix uses [*Payload*](../actix_web/struct.Payload.html) object as request payload stream. +*HttpRequest* provides several methods, which can be used for payload access. +At the same time *Payload* implements *Stream* trait, so it could be used with various +stream combinators. Also *Payload* provides serveral convinience methods that return +future object that resolve to Bytes object. + +* *readany* method returns *Stream* of *Bytes* objects. + +* *readexactly* method returns *Future* that resolves when specified number of bytes + get received. + +* *readline* method returns *Future* that resolves when `\n` get received. + +* *readuntil* method returns *Future* that resolves when specified bytes string + matches in input bytes stream + +Here is example that reads request payload and prints it. + +```rust +# extern crate actix_web; +# extern crate futures; +# use futures::future::result; +use actix_web::*; +use futures::{Future, Stream}; + + +fn index(mut req: HttpRequest) -> Box> { + Box::new( + req.payload_mut() + .readany() + .fold((), |_, chunk| { + println!("Chunk: {:?}", chunk); + result::<_, error::PayloadError>(Ok(())) + }) + .map_err(|e| Error::from(e)) + .map(|_| HttpResponse::Ok().finish().unwrap())) +} +# fn main() {} +``` diff --git a/src/error.rs b/src/error.rs index a44863ff5..98f26e4cb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -265,9 +265,6 @@ pub enum MultipartError { /// Multipart boundary is not found #[fail(display="Multipart boundary is not found")] Boundary, - /// Request does not contain payload - #[fail(display="Request does not contain payload")] - NoPayload, /// Error during field parsing #[fail(display="{}", _0)] Parse(#[cause] ParseError), @@ -335,9 +332,6 @@ pub enum WsHandshakeError { /// Websocket key is not set or wrong #[fail(display="Unknown websocket key")] BadWebsocketKey, - /// Request does not contain payload - #[fail(display="Request does not contain payload")] - NoPayload, } impl ResponseError for WsHandshakeError { @@ -361,8 +355,6 @@ impl ResponseError for WsHandshakeError { HTTPBadRequest.with_reason("Unsupported version"), WsHandshakeError::BadWebsocketKey => HTTPBadRequest.with_reason("Handshake error"), - WsHandshakeError::NoPayload => - HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR, Body::Empty), } } } @@ -382,9 +374,6 @@ pub enum UrlencodedError { /// Content type error #[fail(display="Content type error")] ContentType, - /// Request does not contain payload - #[fail(display="Request does not contain payload")] - NoPayload, } /// Return `BadRequest` for `UrlencodedError` diff --git a/src/h1.rs b/src/h1.rs index 78e060e1e..bfd187e19 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -1002,7 +1002,6 @@ mod tests { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); - assert!(req.payload().is_none()); } Ok(_) | Err(_) => panic!("Error during parsing http request"), } @@ -1026,7 +1025,6 @@ mod tests { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::PUT); assert_eq!(req.path(), "/test"); - assert!(req.payload().is_none()); } Ok(_) | Err(_) => panic!("Error during parsing http request"), } @@ -1044,7 +1042,6 @@ mod tests { assert_eq!(req.version(), Version::HTTP_10); assert_eq!(*req.method(), Method::POST); assert_eq!(req.path(), "/test2"); - assert!(req.payload().is_none()); } Ok(_) | Err(_) => panic!("Error during parsing http request"), } @@ -1062,7 +1059,7 @@ mod tests { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); - assert_eq!(req.payload_mut().unwrap().readall().unwrap().as_ref(), b"body"); + assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"body"); } Ok(_) | Err(_) => panic!("Error during parsing http request"), } @@ -1081,7 +1078,7 @@ mod tests { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); - assert_eq!(req.payload_mut().unwrap().readall().unwrap().as_ref(), b"body"); + assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"body"); } Ok(_) | Err(_) => panic!("Error during parsing http request"), } @@ -1102,7 +1099,6 @@ mod tests { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); - assert!(req.payload().is_none()); } Ok(_) | Err(_) => panic!("Error during parsing http request"), } @@ -1130,7 +1126,6 @@ mod tests { assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value"); - assert!(req.payload().is_none()); } Ok(_) | Err(_) => panic!("Error during parsing http request"), } @@ -1240,7 +1235,7 @@ mod tests { connection: upgrade\r\n\r\n"); let req = parse_ready!(&mut buf); - assert!(!req.payload().unwrap().eof()); + assert!(!req.payload().eof()); assert!(req.upgrade()); } @@ -1252,7 +1247,7 @@ mod tests { let req = parse_ready!(&mut buf); assert!(req.upgrade()); - assert!(!req.payload().unwrap().eof()); + assert!(!req.payload().eof()); } #[test] @@ -1262,7 +1257,6 @@ mod tests { transfer-encoding: chunked\r\n\r\n"); let req = parse_ready!(&mut buf); - assert!(req.payload().is_some()); if let Ok(val) = req.chunked() { assert!(val); } else { @@ -1274,7 +1268,6 @@ mod tests { transfer-encoding: chnked\r\n\r\n"); let req = parse_ready!(&mut buf); - assert!(req.payload().is_none()); if let Ok(val) = req.chunked() { assert!(!val); } else { @@ -1334,7 +1327,7 @@ mod tests { let mut req = parse_ready!(&mut buf); assert!(!req.keep_alive()); assert!(req.upgrade()); - assert_eq!(req.payload_mut().unwrap().readall().unwrap().as_ref(), b"some raw data"); + assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"some raw data"); } #[test] @@ -1383,13 +1376,13 @@ mod tests { let mut reader = Reader::new(); let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert!(req.chunked().unwrap()); - assert!(!req.payload().unwrap().eof()); + assert!(!req.payload().eof()); buf.feed_data("4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); - assert!(!req.payload().unwrap().eof()); - assert_eq!(req.payload_mut().unwrap().readall().unwrap().as_ref(), b"dataline"); - assert!(req.payload().unwrap().eof()); + assert!(!req.payload().eof()); + assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"dataline"); + assert!(req.payload().eof()); } #[test] @@ -1404,7 +1397,7 @@ mod tests { let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert!(req.chunked().unwrap()); - assert!(!req.payload().unwrap().eof()); + assert!(!req.payload().eof()); buf.feed_data( "4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\ @@ -1414,10 +1407,10 @@ mod tests { let req2 = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert_eq!(*req2.method(), Method::POST); assert!(req2.chunked().unwrap()); - assert!(!req2.payload().unwrap().eof()); + assert!(!req2.payload().eof()); - assert_eq!(req.payload_mut().unwrap().readall().unwrap().as_ref(), b"dataline"); - assert!(req.payload().unwrap().eof()); + assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"dataline"); + assert!(req.payload().eof()); } #[test] @@ -1431,7 +1424,7 @@ mod tests { let mut reader = Reader::new(); let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert!(req.chunked().unwrap()); - assert!(!req.payload().unwrap().eof()); + assert!(!req.payload().eof()); buf.feed_data("4\r\ndata\r"); not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); @@ -1453,12 +1446,12 @@ mod tests { //buf.feed_data("test: test\r\n"); //not_ready!(reader.parse(&mut buf, &mut readbuf)); - assert_eq!(req.payload_mut().unwrap().readall().unwrap().as_ref(), b"dataline"); - assert!(!req.payload().unwrap().eof()); + assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"dataline"); + assert!(!req.payload().eof()); buf.feed_data("\r\n"); not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); - assert!(req.payload().unwrap().eof()); + assert!(req.payload().eof()); } #[test] @@ -1472,13 +1465,13 @@ mod tests { let mut reader = Reader::new(); let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert!(req.chunked().unwrap()); - assert!(!req.payload().unwrap().eof()); + assert!(!req.payload().eof()); buf.feed_data("4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); - assert!(!req.payload().unwrap().eof()); - assert_eq!(req.payload_mut().unwrap().readall().unwrap().as_ref(), b"dataline"); - assert!(req.payload().unwrap().eof()); + assert!(!req.payload().eof()); + assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"dataline"); + assert!(req.payload().eof()); } /*#[test] diff --git a/src/httprequest.rs b/src/httprequest.rs index 0fab3c342..f22fea54d 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -79,8 +79,8 @@ impl HttpMessage { self.params.clear(); self.cookies = None; self.addr = None; - self.payload = None; self.info = None; + self.payload = None; } } @@ -385,32 +385,30 @@ impl HttpRequest { /// Returns reference to the associated http payload. #[inline] - pub fn payload(&self) -> Option<&Payload> { - self.as_ref().payload.as_ref() + pub fn payload(&self) -> &Payload { + let msg = self.as_mut(); + if msg.payload.is_none() { + msg.payload = Some(Payload::empty()); + } + msg.payload.as_ref().unwrap() } /// Returns mutable reference to the associated http payload. #[inline] - pub fn payload_mut(&mut self) -> Option<&mut Payload> { - self.as_mut().payload.as_mut() + pub fn payload_mut(&mut self) -> &mut Payload { + let msg = self.as_mut(); + if msg.payload.is_none() { + msg.payload = Some(Payload::empty()); + } + msg.payload.as_mut().unwrap() } - /// Return payload - #[inline] - pub fn take_payload(&mut self) -> Option { - self.as_mut().payload.take() - } - /// Return stream to process BODY as multipart. /// /// Content-type: multipart/form-data; pub fn multipart(&mut self) -> Result { let boundary = Multipart::boundary(self.headers())?; - if let Some(payload) = self.take_payload() { - Ok(Multipart::new(boundary, payload)) - } else { - Err(MultipartError::NoPayload) - } + Ok(Multipart::new(boundary, self.payload().clone())) } /// Parse `application/x-www-form-urlencoded` encoded body. @@ -453,11 +451,7 @@ impl HttpRequest { }; if t { - if let Some(payload) = self.take_payload() { - Ok(UrlEncoded{pl: payload, body: BytesMut::new()}) - } else { - Err(UrlencodedError::NoPayload) - } + Ok(UrlEncoded{pl: self.payload().clone(), body: BytesMut::new()}) } else { Err(UrlencodedError::ContentType) } @@ -523,7 +517,7 @@ impl Future for UrlEncoded { Ok(Async::Ready(m)) }, Ok(Async::Ready(Some(item))) => { - self.body.extend_from_slice(&item.0); + self.body.extend_from_slice(&item); continue }, Err(err) => Err(err), diff --git a/src/lib.rs b/src/lib.rs index 202da08a6..9a83907a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,7 +93,6 @@ mod helpers; mod encoding; mod httprequest; mod httpresponse; -mod payload; mod info; mod route; mod router; @@ -117,12 +116,12 @@ pub mod httpcodes; pub mod multipart; pub mod middlewares; pub mod pred; +pub mod payload; pub use error::{Error, Result, ResponseError}; pub use body::{Body, Binary}; pub use application::Application; pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; -pub use payload::{Payload, PayloadItem}; pub use handler::{Reply, Responder, Json, NormalizePath}; pub use route::Route; pub use resource::Resource; @@ -167,7 +166,6 @@ pub mod dev { pub use handler::Handler; pub use router::{Router, Pattern}; pub use pipeline::Pipeline; - pub use payload::{PayloadSender, PayloadWriter}; pub use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; pub use param::{FromParam, Params}; pub use server::ServerSettings; diff --git a/src/multipart.rs b/src/multipart.rs index f09c135fd..59ba232e7 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -9,7 +9,7 @@ use httparse; use bytes::Bytes; use http::HttpTryFrom; use http::header::{self, HeaderMap, HeaderName, HeaderValue}; -use futures::{Async, Stream, Poll}; +use futures::{Async, Future, Stream, Poll}; use futures::task::{Task, current as current_task}; use error::{ParseError, PayloadError, MultipartError}; @@ -119,7 +119,7 @@ impl InnerMultipart { fn read_headers(payload: &mut Payload) -> Poll { - match payload.readuntil(b"\r\n\r\n")? { + match payload.readuntil(b"\r\n\r\n").poll()? { Async::NotReady => Ok(Async::NotReady), Async::Ready(bytes) => { let mut hdrs = [httparse::EMPTY_HEADER; MAX_HEADERS]; @@ -150,7 +150,7 @@ impl InnerMultipart { fn read_boundary(payload: &mut Payload, boundary: &str) -> Poll { // TODO: need to read epilogue - match payload.readline()? { + match payload.readline().poll()? { Async::NotReady => Ok(Async::NotReady), Async::Ready(chunk) => { if chunk.len() == boundary.len() + 4 && @@ -175,7 +175,7 @@ impl InnerMultipart { { let mut eof = false; loop { - if let Async::Ready(chunk) = payload.readline()? { + if let Async::Ready(chunk) = payload.readline().poll()? { if chunk.is_empty() { //ValueError("Could not find starting boundary %r" //% (self._boundary)) @@ -452,15 +452,15 @@ impl InnerField { if *size == 0 { Ok(Async::Ready(None)) } else { - match payload.readany() { + match payload.readany().poll() { Ok(Async::NotReady) => Ok(Async::NotReady), Ok(Async::Ready(None)) => Ok(Async::Ready(None)), Ok(Async::Ready(Some(mut chunk))) => { - let len = cmp::min(chunk.0.len() as u64, *size); + let len = cmp::min(chunk.len() as u64, *size); *size -= len; - let ch = chunk.0.split_to(len as usize); - if !chunk.0.is_empty() { - payload.unread_data(chunk.0); + let ch = chunk.split_to(len as usize); + if !chunk.is_empty() { + payload.unread_data(chunk); } Ok(Async::Ready(Some(ch))) }, @@ -473,12 +473,12 @@ impl InnerField { /// The `Content-Length` header for body part is not necessary. fn read_stream(payload: &mut Payload, boundary: &str) -> Poll, MultipartError> { - match payload.readuntil(b"\r")? { + match payload.readuntil(b"\r").poll()? { Async::NotReady => Ok(Async::NotReady), Async::Ready(mut chunk) => { if chunk.len() == 1 { payload.unread_data(chunk); - match payload.readexactly(boundary.len() + 4)? { + match payload.readexactly(boundary.len() + 4).poll()? { Async::NotReady => Ok(Async::NotReady), Async::Ready(chunk) => { if &chunk[..2] == b"\r\n" && &chunk[2..4] == b"--" && @@ -507,7 +507,7 @@ impl InnerField { } if self.eof { if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) { - match payload.readline()? { + match payload.readline().poll()? { Async::NotReady => return Ok(Async::NotReady), Async::Ready(chunk) => { @@ -536,7 +536,7 @@ impl InnerField { Async::Ready(Some(bytes)) => Async::Ready(Some(FieldChunk(bytes))), Async::Ready(None) => { self.eof = true; - match payload.readline()? { + match payload.readline().poll()? { Async::NotReady => Async::NotReady, Async::Ready(chunk) => { assert_eq!( diff --git a/src/payload.rs b/src/payload.rs index 3d0690da8..57dd7cf03 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -1,10 +1,11 @@ +//! Payload stream use std::{fmt, cmp}; use std::rc::{Rc, Weak}; use std::cell::RefCell; use std::collections::VecDeque; use std::ops::{Deref, DerefMut}; use bytes::{Bytes, BytesMut}; -use futures::{Async, Poll, Stream}; +use futures::{Future, Async, Poll, Stream}; use futures::task::{Task, current as current_task}; use body::BodyStream; @@ -88,27 +89,23 @@ impl Payload { } /// Get first available chunk of data. - /// Returns Some(PayloadItem) as chunk, `None` indicates eof. - pub fn readany(&mut self) -> Poll, PayloadError> { - self.inner.borrow_mut().readany() + pub fn readany(&mut self) -> ReadAny { + ReadAny(Rc::clone(&self.inner)) } - /// Get exactly number of bytes - /// Returns Some(PayloadItem) as chunk, `None` indicates eof. - pub fn readexactly(&mut self, size: usize) -> Result, PayloadError> { - self.inner.borrow_mut().readexactly(size) + /// Get exact number of bytes + pub fn readexactly(&mut self, size: usize) -> ReadExactly { + ReadExactly(Rc::clone(&self.inner), size) } /// Read until `\n` - /// Returns Some(PayloadItem) as line, `None` indicates eof. - pub fn readline(&mut self) -> Result, PayloadError> { - self.inner.borrow_mut().readline() + pub fn readline(&mut self) -> ReadLine { + ReadLine(Rc::clone(&self.inner)) } /// Read until match line - /// Returns Some(PayloadItem) as line, `None` indicates eof. - pub fn readuntil(&mut self, line: &[u8]) -> Result, PayloadError> { - self.inner.borrow_mut().readuntil(line) + pub fn readuntil(&mut self, line: &[u8]) -> ReadUntil { + ReadUntil(Rc::clone(&self.inner), line.to_vec()) } #[doc(hidden)] @@ -133,19 +130,91 @@ impl Payload { /// Convert payload into BodyStream pub fn stream(self) -> BodyStream { - Box::new(self.map(|item| item.0).map_err(|e| e.into())) + Box::new(self.map_err(|e| e.into())) } } impl Stream for Payload { - type Item = PayloadItem; + type Item = Bytes; type Error = PayloadError; - fn poll(&mut self) -> Poll, PayloadError> { - self.readany() + fn poll(&mut self) -> Poll, PayloadError> { + match self.inner.borrow_mut().readany()? { + Async::Ready(Some(item)) => Ok(Async::Ready(Some(item.0))), + Async::Ready(None) => Ok(Async::Ready(None)), + Async::NotReady => Ok(Async::NotReady), + } } } +impl Clone for Payload { + fn clone(&self) -> Payload { + Payload{inner: Rc::clone(&self.inner)} + } +} + +/// Get first available chunk of data +pub struct ReadAny(Rc>); + +impl Stream for ReadAny { + type Item = Bytes; + type Error = PayloadError; + + fn poll(&mut self) -> Poll, Self::Error> { + match self.0.borrow_mut().readany()? { + Async::Ready(Some(item)) => Ok(Async::Ready(Some(item.0))), + Async::Ready(None) => Ok(Async::Ready(None)), + Async::NotReady => Ok(Async::NotReady), + } + } +} + +/// Get exact number of bytes +pub struct ReadExactly(Rc>, usize); + +impl Future for ReadExactly { + type Item = Bytes; + type Error = PayloadError; + + fn poll(&mut self) -> Poll { + match self.0.borrow_mut().readexactly(self.1)? { + Async::Ready(chunk) => Ok(Async::Ready(chunk)), + Async::NotReady => Ok(Async::NotReady), + } + } +} + +/// Read until `\n` +pub struct ReadLine(Rc>); + +impl Future for ReadLine { + type Item = Bytes; + type Error = PayloadError; + + fn poll(&mut self) -> Poll { + match self.0.borrow_mut().readline()? { + Async::Ready(chunk) => Ok(Async::Ready(chunk)), + Async::NotReady => Ok(Async::NotReady), + } + } +} + +/// Read until match line +pub struct ReadUntil(Rc>, Vec); + +impl Future for ReadUntil { + type Item = Bytes; + type Error = PayloadError; + + fn poll(&mut self) -> Poll { + match self.0.borrow_mut().readuntil(&self.1)? { + Async::Ready(chunk) => Ok(Async::Ready(chunk)), + Async::NotReady => Ok(Async::NotReady), + } + } +} + +/// Payload writer interface. pub trait PayloadWriter { /// Set stream error. @@ -408,7 +477,7 @@ mod tests { assert!(!payload.eof()); assert!(payload.is_empty()); assert_eq!(payload.len(), 0); - assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); + assert_eq!(Async::NotReady, payload.readany().poll().ok().unwrap()); let res: Result<(), ()> = Ok(()); result(res) @@ -420,7 +489,7 @@ mod tests { Core::new().unwrap().run(lazy(|| { let (mut sender, mut payload) = Payload::new(false); - assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); + assert_eq!(Async::NotReady, payload.readany().poll().ok().unwrap()); assert!(!payload.eof()); sender.feed_data(Bytes::from("data")); @@ -428,13 +497,13 @@ mod tests { assert!(!payload.eof()); - assert_eq!(Async::Ready(Some(PayloadItem(Bytes::from("data")))), - payload.readany().ok().unwrap()); + assert_eq!(Async::Ready(Some(Bytes::from("data"))), + payload.readany().poll().ok().unwrap()); assert!(payload.is_empty()); assert!(payload.eof()); assert_eq!(payload.len(), 0); - assert_eq!(Async::Ready(None), payload.readany().ok().unwrap()); + assert_eq!(Async::Ready(None), payload.readany().poll().ok().unwrap()); let res: Result<(), ()> = Ok(()); result(res) })).unwrap(); @@ -445,10 +514,10 @@ mod tests { Core::new().unwrap().run(lazy(|| { let (mut sender, mut payload) = Payload::new(false); - assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); + assert_eq!(Async::NotReady, payload.readany().poll().ok().unwrap()); sender.set_error(PayloadError::Incomplete); - payload.readany().err().unwrap(); + payload.readany().poll().err().unwrap(); let res: Result<(), ()> = Ok(()); result(res) })).unwrap(); @@ -468,8 +537,8 @@ mod tests { assert!(!payload.is_empty()); assert_eq!(payload.len(), 10); - assert_eq!(Async::Ready(Some(PayloadItem(Bytes::from("line1")))), - payload.readany().ok().unwrap()); + assert_eq!(Async::Ready(Some(Bytes::from("line1"))), + payload.readany().poll().ok().unwrap()); assert!(!payload.is_empty()); assert_eq!(payload.len(), 5); @@ -483,20 +552,22 @@ mod tests { Core::new().unwrap().run(lazy(|| { let (mut sender, mut payload) = Payload::new(false); - assert_eq!(Async::NotReady, payload.readexactly(2).ok().unwrap()); + assert_eq!(Async::NotReady, payload.readexactly(2).poll().ok().unwrap()); sender.feed_data(Bytes::from("line1")); sender.feed_data(Bytes::from("line2")); assert_eq!(payload.len(), 10); - assert_eq!(Async::Ready(Bytes::from("li")), payload.readexactly(2).ok().unwrap()); + assert_eq!(Async::Ready(Bytes::from("li")), + payload.readexactly(2).poll().ok().unwrap()); assert_eq!(payload.len(), 8); - assert_eq!(Async::Ready(Bytes::from("ne1l")), payload.readexactly(4).ok().unwrap()); + assert_eq!(Async::Ready(Bytes::from("ne1l")), + payload.readexactly(4).poll().ok().unwrap()); assert_eq!(payload.len(), 4); sender.set_error(PayloadError::Incomplete); - payload.readexactly(10).err().unwrap(); + payload.readexactly(10).poll().err().unwrap(); let res: Result<(), ()> = Ok(()); result(res) @@ -508,22 +579,22 @@ mod tests { Core::new().unwrap().run(lazy(|| { let (mut sender, mut payload) = Payload::new(false); - assert_eq!(Async::NotReady, payload.readuntil(b"ne").ok().unwrap()); + assert_eq!(Async::NotReady, payload.readuntil(b"ne").poll().ok().unwrap()); sender.feed_data(Bytes::from("line1")); sender.feed_data(Bytes::from("line2")); assert_eq!(payload.len(), 10); assert_eq!(Async::Ready(Bytes::from("line")), - payload.readuntil(b"ne").ok().unwrap()); + payload.readuntil(b"ne").poll().ok().unwrap()); assert_eq!(payload.len(), 6); assert_eq!(Async::Ready(Bytes::from("1line2")), - payload.readuntil(b"2").ok().unwrap()); + payload.readuntil(b"2").poll().ok().unwrap()); assert_eq!(payload.len(), 0); sender.set_error(PayloadError::Incomplete); - payload.readuntil(b"b").err().unwrap(); + payload.readuntil(b"b").poll().err().unwrap(); let res: Result<(), ()> = Ok(()); result(res) @@ -539,8 +610,8 @@ mod tests { assert!(!payload.is_empty()); assert_eq!(payload.len(), 4); - assert_eq!(Async::Ready(Some(PayloadItem(Bytes::from("data")))), - payload.readany().ok().unwrap()); + assert_eq!(Async::Ready(Some(Bytes::from("data"))), + payload.readany().poll().ok().unwrap()); let res: Result<(), ()> = Ok(()); result(res) diff --git a/src/ws.rs b/src/ws.rs index 324a304af..097ec7997 100644 --- a/src/ws.rs +++ b/src/ws.rs @@ -57,7 +57,7 @@ use actix::{Actor, AsyncContext, ResponseType, StreamHandler}; use body::Body; use context::HttpContext; use handler::Reply; -use payload::Payload; +use payload::ReadAny; use error::{Error, WsHandshakeError}; use httprequest::HttpRequest; use httpresponse::{ConnectionType, HttpResponse}; @@ -96,15 +96,11 @@ pub fn start(mut req: HttpRequest, actor: A) -> Result { let resp = handshake(&req)?; - if let Some(payload) = req.take_payload() { - let stream = WsStream::new(payload); - let mut ctx = HttpContext::new(req, actor); - ctx.start(resp); - ctx.add_stream(stream); - Ok(ctx.into()) - } else { - Err(WsHandshakeError::NoPayload.into()) - } + let stream = WsStream::new(req.payload_mut().readany()); + let mut ctx = HttpContext::new(req, actor); + ctx.start(resp); + ctx.add_stream(stream); + Ok(ctx.into()) } /// Prepare `WebSocket` handshake response. @@ -175,14 +171,14 @@ pub fn handshake(req: &HttpRequest) -> Result WsStream { + pub fn new(payload: ReadAny) -> WsStream { WsStream { rx: payload, buf: BytesMut::new(), closed: false, @@ -199,9 +195,9 @@ impl Stream for WsStream { if !self.closed { loop { - match self.rx.readany() { + match self.rx.poll() { Ok(Async::Ready(Some(chunk))) => { - self.buf.extend_from_slice(&chunk.0) + self.buf.extend_from_slice(&chunk) } Ok(Async::Ready(None)) => { done = true; From 4f6145e5c715b6dc79ba8eb0e9ac9679313d3e15 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Dec 2017 00:29:25 -0800 Subject: [PATCH 129/279] fix typos --- guide/src/qs_7.md | 2 +- src/payload.rs | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index e3248068d..b1fa3579e 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -122,7 +122,7 @@ fn index(req: HttpRequest) -> HttpResponse { ## Streaming request -Actix uses [*Payload*](../actix_web/struct.Payload.html) object as request payload stream. +Actix uses [*Payload*](../actix_web/payload/struct.Payload.html) object as request payload stream. *HttpRequest* provides several methods, which can be used for payload access. At the same time *Payload* implements *Stream* trait, so it could be used with various stream combinators. Also *Payload* provides serveral convinience methods that return diff --git a/src/payload.rs b/src/payload.rs index 57dd7cf03..eda81c755 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -43,10 +43,12 @@ impl fmt::Debug for PayloadItem { } } -/// Stream of byte chunks +/// Buffered stream of bytes chunks /// -/// Payload stores chunks in vector. First chunk can be received with `.readany()` method. +/// Payload stores chunks in a vector. First chunk can be received with `.readany()` method. /// Payload stream is not thread safe. +/// +/// Payload stream can be used as `HttpResponse` body stream. #[derive(Debug)] pub struct Payload { inner: Rc>, @@ -128,7 +130,7 @@ impl Payload { self.inner.borrow_mut().set_buffer_size(size) } - /// Convert payload into BodyStream + /// Convert payload into compatible `HttpResponse` body stream pub fn stream(self) -> BodyStream { Box::new(self.map_err(|e| e.into())) } From 13cbfc877d003d7203b09f63cf594e50b1b3ccb7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Dec 2017 09:08:36 -0800 Subject: [PATCH 130/279] simplify server start method --- examples/basic.rs | 2 +- examples/diesel/src/main.rs | 2 +- examples/state.rs | 2 +- examples/template_tera/src/main.rs | 2 +- examples/websocket-chat/src/main.rs | 2 +- examples/websocket.rs | 2 +- guide/src/qs_3_5.md | 5 +++-- src/server.rs | 14 ++++++++------ tests/test_server.rs | 6 +++--- 9 files changed, 20 insertions(+), 17 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index 4ceef9e13..8e8da3e87 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -93,7 +93,7 @@ fn main() { .body(Body::Empty) }))) .bind("127.0.0.1:8080").unwrap() - .start().unwrap(); + .start(); println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); diff --git a/examples/diesel/src/main.rs b/examples/diesel/src/main.rs index 15f0cc1bf..80eb30d29 100644 --- a/examples/diesel/src/main.rs +++ b/examples/diesel/src/main.rs @@ -105,7 +105,7 @@ fn main() { .middleware(middlewares::Logger::default()) .resource("/{name}", |r| r.method(Method::GET).a(index))}) .bind("127.0.0.1:8080").unwrap() - .start().unwrap(); + .start(); println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); diff --git a/examples/state.rs b/examples/state.rs index c36ac19d2..0a10b77bd 100644 --- a/examples/state.rs +++ b/examples/state.rs @@ -71,7 +71,7 @@ fn main() { // register simple handler, handle all methods .resource("/", |r| r.f(index))) .bind("127.0.0.1:8080").unwrap() - .start().unwrap(); + .start(); println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); diff --git a/examples/template_tera/src/main.rs b/examples/template_tera/src/main.rs index 97b9d4812..23d68cadf 100644 --- a/examples/template_tera/src/main.rs +++ b/examples/template_tera/src/main.rs @@ -37,7 +37,7 @@ fn main() { .middleware(middlewares::Logger::default()) .resource("/", |r| r.method(Method::GET).f(index))}) .bind("127.0.0.1:8080").unwrap() - .start().unwrap(); + .start(); println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs index 1f168eb84..8576503a4 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -214,7 +214,7 @@ fn main() { |r| r.h(fs::StaticFiles::new("tail", "static/", true))) }) .bind("127.0.0.1:8080").unwrap() - .start().unwrap(); + .start(); let _ = sys.run(); } diff --git a/examples/websocket.rs b/examples/websocket.rs index 8f62ef296..4b416b541 100644 --- a/examples/websocket.rs +++ b/examples/websocket.rs @@ -71,7 +71,7 @@ fn main() { |r| r.h(fs::StaticFiles::new("tail", "examples/static/", true)))) // start http server on 127.0.0.1:8080 .bind("127.0.0.1:8080").unwrap() - .start().unwrap(); + .start(); println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); diff --git a/guide/src/qs_3_5.md b/guide/src/qs_3_5.md index 70eb48095..01ccffd99 100644 --- a/guide/src/qs_3_5.md +++ b/guide/src/qs_3_5.md @@ -22,7 +22,7 @@ fn main() { || Application::new() .resource("/", |r| r.f(|_| httpcodes::HTTPOk))) .bind("127.0.0.1:59080").unwrap() - .start().unwrap(); + .start(); # actix::Arbiter::system().send(actix::msgs::SystemExit(0)); let _ = sys.run(); @@ -78,7 +78,8 @@ fn main() { HttpServer::new( || Application::new() .resource("/index.html", |r| r.f(index))) - .serve_ssl::<_, ()>("127.0.0.1:8080", pkcs12).unwrap(); + .bind("127.0.0.1:8080").unwrap() + .serve_ssl(pkcs12).unwrap(); } ``` diff --git a/src/server.rs b/src/server.rs index 9bec090a2..08d6645c6 100644 --- a/src/server.rs +++ b/src/server.rs @@ -261,10 +261,12 @@ impl HttpServer /// /// This method starts number of http handler workers in seperate threads. /// For each address this method starts separate thread which does `accept()` in a loop. - pub fn start(mut self) -> io::Result> + /// + /// This methods panics if no socket addresses get bound. + pub fn start(mut self) -> SyncAddress { if self.sockets.is_empty() { - Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound")) + panic!("HttpServer::bind() has to be called befor start()"); } else { let addrs: Vec<(net::SocketAddr, Socket)> = self.sockets.drain().collect(); let settings = ServerSettings::new(Some(addrs[0].0), false); @@ -277,7 +279,7 @@ impl HttpServer } // start http server actor - Ok(HttpServer::create(|_| {self})) + HttpServer::create(|_| {self}) } } } @@ -366,7 +368,7 @@ impl HttpServer /// Start listening for incomming connections from a stream. /// /// This method uses only one thread for handling incoming connections. - pub fn start_incoming(mut self, stream: S, secure: bool) -> io::Result> + pub fn start_incoming(mut self, stream: S, secure: bool) -> SyncAddress where S: Stream + 'static { if !self.sockets.is_empty() { @@ -391,11 +393,11 @@ impl HttpServer self.h = Some(Rc::new(WorkerSettings::new(apps, self.keep_alive))); // start server - Ok(HttpServer::create(move |ctx| { + HttpServer::create(move |ctx| { ctx.add_stream(stream.map( move |(t, _)| IoStream{io: t, peer: None, http2: false})); self - })) + }) } } diff --git a/tests/test_server.rs b/tests/test_server.rs index 71f0e0f9e..14369da37 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -18,7 +18,7 @@ fn test_serve() { let srv = HttpServer::new( || vec![Application::new() .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))]); - srv.bind("127.0.0.1:58902").unwrap().start().unwrap(); + srv.bind("127.0.0.1:58902").unwrap().start(); sys.run(); }); assert!(reqwest::get("http://localhost:58902/").unwrap().status().is_success()); @@ -39,7 +39,7 @@ fn test_serve_incoming() { || Application::new() .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))); let tcp = TcpListener::from_listener(tcp, &addr2, Arbiter::handle()).unwrap(); - srv.start_incoming(tcp.incoming(), false).unwrap(); + srv.start_incoming(tcp.incoming(), false); sys.run(); }); @@ -90,7 +90,7 @@ fn test_middlewares() { finish: Arc::clone(&act_num3)}) .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))]) .bind("127.0.0.1:58904").unwrap() - .start().unwrap(); + .start(); sys.run(); }); From 790793f8a1c9b9000f1bb328cb89b86e1a505fff Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Dec 2017 09:51:28 -0800 Subject: [PATCH 131/279] refactor multipart stream creation --- examples/basic.rs | 2 +- examples/multipart/Cargo.toml | 4 +- examples/multipart/client.py | 8 ++-- examples/multipart/src/main.rs | 74 +++++++++++++--------------------- guide/src/qs_7.md | 2 +- src/httprequest.rs | 7 ++-- src/multipart.rs | 33 ++++++++++++--- 7 files changed, 68 insertions(+), 62 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index 8e8da3e87..e52eac0dc 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -95,6 +95,6 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); - println!("Started http server: 127.0.0.1:8080"); + println!("Starting http server: 127.0.0.1:8080"); let _ = sys.run(); } diff --git a/examples/multipart/Cargo.toml b/examples/multipart/Cargo.toml index 5cb2031df..5a8d582e9 100644 --- a/examples/multipart/Cargo.toml +++ b/examples/multipart/Cargo.toml @@ -9,5 +9,7 @@ path = "src/main.rs" [dependencies] env_logger = "*" +futures = "0.1" actix = "^0.3.1" -actix-web = { git = "https://github.com/actix/actix-web.git" } +#actix-web = { git = "https://github.com/actix/actix-web.git" } +actix-web = { path = "../../" } diff --git a/examples/multipart/client.py b/examples/multipart/client.py index 35f97c1a6..2e3068d4c 100644 --- a/examples/multipart/client.py +++ b/examples/multipart/client.py @@ -2,25 +2,25 @@ import asyncio import aiohttp -def req1(): +async def req1(): with aiohttp.MultipartWriter() as writer: writer.append('test') writer.append_json({'passed': True}) - resp = yield from aiohttp.request( + resp = await aiohttp.ClientSession().request( "post", 'http://localhost:8080/multipart', data=writer, headers=writer.headers) print(resp) assert 200 == resp.status -def req2(): +async def req2(): with aiohttp.MultipartWriter() as writer: writer.append('test') writer.append_json({'passed': True}) writer.append(open('src/main.rs')) - resp = yield from aiohttp.request( + resp = await aiohttp.ClientSession().request( "post", 'http://localhost:8080/multipart', data=writer, headers=writer.headers) print(resp) diff --git a/examples/multipart/src/main.rs b/examples/multipart/src/main.rs index a1f31527d..ac3714ad0 100644 --- a/examples/multipart/src/main.rs +++ b/examples/multipart/src/main.rs @@ -2,76 +2,60 @@ extern crate actix; extern crate actix_web; extern crate env_logger; +extern crate futures; -use actix::*; use actix_web::*; +use futures::{Future, Stream}; +use futures::future::{result, Either}; -struct MyRoute; -impl Actor for MyRoute { - type Context = HttpContext; -} +fn index(mut req: HttpRequest) -> Box> +{ + println!("{:?}", req); -impl Route for MyRoute { - type State = (); - - fn request(mut req: HttpRequest, ctx: &mut HttpContext) -> RouteResult { - println!("{:?}", req); - - let multipart = req.multipart()?; - - // get Multipart stream - WrapStream::::actstream(multipart) - .and_then(|item, act, ctx| { + // get multipart stream and iterate over multipart items + Box::new( + req.multipart() + .map_err(Error::from) + .and_then(|item| { // Multipart stream is a stream of Fields and nested Multiparts match item { multipart::MultipartItem::Field(field) => { println!("==== FIELD ==== {:?}", field); // Read field's stream - fut::Either::A( - field.actstream() - .map(|chunk, act, ctx| { - println!( - "-- CHUNK: \n{}", - std::str::from_utf8(&chunk.0).unwrap()); - }) - .finish()) + Either::A( + field.map_err(Error::from) + .map(|chunk| { + println!("-- CHUNK: \n{}", + std::str::from_utf8(&chunk.0).unwrap());}) + .fold((), |_, _| result::<_, Error>(Ok(())))) }, multipart::MultipartItem::Nested(mp) => { // Do nothing for nested multipart stream - fut::Either::B(fut::ok(())) + Either::B(result(Ok(()))) } } }) // wait until stream finish - .finish() - .map_err(|e, act, ctx| { - ctx.start(httpcodes::HTTPBadRequest); - ctx.write_eof(); - }) - .map(|_, act, ctx| { - ctx.start(httpcodes::HTTPOk); - ctx.write_eof(); - }) - .spawn(ctx); - - Reply::async(MyRoute) - } + .fold((), |_, _| result::<_, Error>(Ok(()))) + .map(|_| httpcodes::HTTPOk.response()) + ) } fn main() { + ::std::env::set_var("RUST_LOG", "actix_web=info"); let _ = env_logger::init(); let sys = actix::System::new("multipart-example"); HttpServer::new( - vec![ - Application::default("/") - .resource("/multipart", |r| { - r.post::(); - }).finish() - ]) - .serve::<_, ()>("127.0.0.1:8080").unwrap(); + || Application::new() + // enable logger + .middleware(middlewares::Logger::default()) + .resource("/multipart", |r| r.method(Method::POST).a(index))) + .bind("127.0.0.1:8080").unwrap() + .start(); + println!("Starting http server: 127.0.0.1:8080"); let _ = sys.run(); } diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index b1fa3579e..2ab8e69ac 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -138,7 +138,7 @@ future object that resolve to Bytes object. * *readuntil* method returns *Future* that resolves when specified bytes string matches in input bytes stream -Here is example that reads request payload and prints it. +In this example handle reads request payload chunk by chunk and prints every chunk. ```rust # extern crate actix_web; diff --git a/src/httprequest.rs b/src/httprequest.rs index f22fea54d..067879edc 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -17,7 +17,7 @@ use payload::Payload; use multipart::Multipart; use helpers::SharedHttpMessage; use error::{ParseError, PayloadError, UrlGenerationError, - MultipartError, CookieParseError, HttpRangeError, UrlencodedError}; + CookieParseError, HttpRangeError, UrlencodedError}; pub struct HttpMessage { @@ -406,9 +406,8 @@ impl HttpRequest { /// Return stream to process BODY as multipart. /// /// Content-type: multipart/form-data; - pub fn multipart(&mut self) -> Result { - let boundary = Multipart::boundary(self.headers())?; - Ok(Multipart::new(boundary, self.payload().clone())) + pub fn multipart(&mut self) -> Multipart { + Multipart::from_request(self) } /// Parse `application/x-www-form-urlencoded` encoded body. diff --git a/src/multipart.rs b/src/multipart.rs index 59ba232e7..634b6652f 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -14,6 +14,7 @@ use futures::task::{Task, current as current_task}; use error::{ParseError, PayloadError, MultipartError}; use payload::Payload; +use httprequest::HttpRequest; const MAX_HEADERS: usize = 32; @@ -26,7 +27,8 @@ const MAX_HEADERS: usize = 32; #[derive(Debug)] pub struct Multipart { safety: Safety, - inner: Rc>, + error: Option, + inner: Option>>, } /// @@ -66,17 +68,32 @@ struct InnerMultipart { } impl Multipart { + /// Create multipart instance for boundary. pub fn new(boundary: String, payload: Payload) -> Multipart { Multipart { + error: None, safety: Safety::new(), - inner: Rc::new(RefCell::new( + inner: Some(Rc::new(RefCell::new( InnerMultipart { payload: PayloadRef::new(payload), boundary: boundary, state: InnerState::FirstBoundary, item: InnerMultipartItem::None, - })) + }))) + } + } + + /// Create multipart instance for request. + pub fn from_request(req: &mut HttpRequest) -> Multipart { + match Multipart::boundary(req.headers()) { + Ok(boundary) => Multipart::new(boundary, req.payload().clone()), + Err(err) => + Multipart { + error: Some(err), + safety: Safety::new(), + inner: None, + } } } @@ -107,8 +124,10 @@ impl Stream for Multipart { type Error = MultipartError; fn poll(&mut self) -> Poll, Self::Error> { - if self.safety.current() { - self.inner.borrow_mut().poll(&self.safety) + if let Some(err) = self.error.take() { + Err(err) + } else if self.safety.current() { + self.inner.as_mut().unwrap().borrow_mut().poll(&self.safety) } else { Ok(Async::NotReady) } @@ -327,7 +346,9 @@ impl InnerMultipart { Ok(Async::Ready(Some( MultipartItem::Nested( - Multipart{safety: safety.clone(), inner: inner})))) + Multipart{safety: safety.clone(), + error: None, + inner: Some(inner)})))) } else { let field = Rc::new(RefCell::new(InnerField::new( self.payload.clone(), self.boundary.clone(), &headers)?)); From e3f9345420cbc18a9e289eb15651f7c0be1cbaee Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Dec 2017 09:55:49 -0800 Subject: [PATCH 132/279] multipart field is stream of bytes --- examples/multipart/Cargo.toml | 3 +-- examples/multipart/src/main.rs | 2 +- guide/src/qs_7.md | 2 ++ src/multipart.rs | 14 +++++--------- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/examples/multipart/Cargo.toml b/examples/multipart/Cargo.toml index 5a8d582e9..bfe89f82b 100644 --- a/examples/multipart/Cargo.toml +++ b/examples/multipart/Cargo.toml @@ -11,5 +11,4 @@ path = "src/main.rs" env_logger = "*" futures = "0.1" actix = "^0.3.1" -#actix-web = { git = "https://github.com/actix/actix-web.git" } -actix-web = { path = "../../" } +actix-web = { git = "https://github.com/actix/actix-web.git" } diff --git a/examples/multipart/src/main.rs b/examples/multipart/src/main.rs index ac3714ad0..0b3dfb1da 100644 --- a/examples/multipart/src/main.rs +++ b/examples/multipart/src/main.rs @@ -28,7 +28,7 @@ fn index(mut req: HttpRequest) -> Box> field.map_err(Error::from) .map(|chunk| { println!("-- CHUNK: \n{}", - std::str::from_utf8(&chunk.0).unwrap());}) + std::str::from_utf8(&chunk).unwrap());}) .fold((), |_, _| result::<_, Error>(Ok(())))) }, multipart::MultipartItem::Nested(mp) => { diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 2ab8e69ac..3d4ef7be6 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -114,6 +114,8 @@ fn index(req: HttpRequest) -> HttpResponse { ## Multipart body + + [WIP] ## Urlencoded body diff --git a/src/multipart.rs b/src/multipart.rs index 634b6652f..aa3f72018 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -377,10 +377,6 @@ pub struct Field { safety: Safety, } -/// A field's chunk -#[derive(PartialEq, Debug)] -pub struct FieldChunk(pub Bytes); - impl Field { fn new(safety: Safety, headers: HeaderMap, @@ -403,7 +399,7 @@ impl Field { } impl Stream for Field { - type Item = FieldChunk; + type Item = Bytes; type Error = MultipartError; fn poll(&mut self) -> Poll, Self::Error> { @@ -522,7 +518,7 @@ impl InnerField { } } - fn poll(&mut self, s: &Safety) -> Poll, MultipartError> { + fn poll(&mut self, s: &Safety) -> Poll, MultipartError> { if self.payload.is_none() { return Ok(Async::Ready(None)) } @@ -554,7 +550,7 @@ impl InnerField { match res { Async::NotReady => Async::NotReady, - Async::Ready(Some(bytes)) => Async::Ready(Some(FieldChunk(bytes))), + Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)), Async::Ready(None) => { self.eof = true; match payload.readline().poll()? { @@ -734,7 +730,7 @@ mod tests { match field.poll() { Ok(Async::Ready(Some(chunk))) => - assert_eq!(chunk.0, "test"), + assert_eq!(chunk, "test"), _ => unreachable!() } match field.poll() { @@ -757,7 +753,7 @@ mod tests { match field.poll() { Ok(Async::Ready(Some(chunk))) => - assert_eq!(chunk.0, "data"), + assert_eq!(chunk, "data"), _ => unreachable!() } match field.poll() { From 2e790dfcc6a6b9c67a44553c2f5b5700babcbe1b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Dec 2017 10:10:03 -0800 Subject: [PATCH 133/279] add multipart guide section --- examples/multipart/src/main.rs | 12 +++++------ guide/src/qs_14.md | 2 +- guide/src/qs_7.md | 37 +++++++++++++++++++++++++++++++++- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/examples/multipart/src/main.rs b/examples/multipart/src/main.rs index 0b3dfb1da..ed804fa88 100644 --- a/examples/multipart/src/main.rs +++ b/examples/multipart/src/main.rs @@ -15,15 +15,15 @@ fn index(mut req: HttpRequest) -> Box> // get multipart stream and iterate over multipart items Box::new( - req.multipart() + req.multipart() // <- get multipart stream for current request .map_err(Error::from) - .and_then(|item| { - // Multipart stream is a stream of Fields and nested Multiparts + .and_then(|item| { // <- iterate over multipart items match item { + // Handle multipart Field multipart::MultipartItem::Field(field) => { println!("==== FIELD ==== {:?}", field); - // Read field's stream + // Field in turn is stream of *Bytes* object Either::A( field.map_err(Error::from) .map(|chunk| { @@ -32,12 +32,12 @@ fn index(mut req: HttpRequest) -> Box> .fold((), |_, _| result::<_, Error>(Ok(())))) }, multipart::MultipartItem::Nested(mp) => { - // Do nothing for nested multipart stream + // Or item could be nested Multipart stream Either::B(result(Ok(()))) } } }) - // wait until stream finish + // wait until stream finishes .fold((), |_, _| result::<_, Error>(Ok(()))) .map(|_| httpcodes::HTTPOk.response()) ) diff --git a/guide/src/qs_14.md b/guide/src/qs_14.md index 2716bf854..133d6d10a 100644 --- a/guide/src/qs_14.md +++ b/guide/src/qs_14.md @@ -121,7 +121,7 @@ fn index(req: HttpRequest) -> Box> ``` Full example is available in -[examples repository](https://github.com/actix/actix-web/tree/master/examples/diesel/). +[examples directory](https://github.com/actix/actix-web/tree/master/examples/diesel/). More information on sync actors could be found in [actix documentation](https://docs.rs/actix/0.3.3/actix/sync/index.html). diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 3d4ef7be6..848cdcd09 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -114,9 +114,44 @@ fn index(req: HttpRequest) -> HttpResponse { ## Multipart body +Actix provides multipart stream support. +[*Multipart*](../actix_web/multipart/struct.Multipart.html) is implemented as +a stream of multipart items, each item could be +[*Field*](../actix_web/multipart/struct.Field.html) or nested *Multipart* stream. +`HttpResponse::multipart()` method returns *Multipart* stream for current request. +In simple form multipart stream handling could be implemented similar to this example -[WIP] +```rust,ignore +# extern crate actix_web; +use actix_web::*; + +fn index(req: HttpRequest) -> Box> { + req.multipart() // <- get multipart stream for current request + .and_then(|item| { // <- iterate over multipart items + match item { + // Handle multipart Field + multipart::MultipartItem::Field(field) => { + println!("==== FIELD ==== {:?} {:?}", field.heders(), field.content_type()); + + Either::A( + // Field in turn is a stream of *Bytes* objects + field.map(|chunk| { + println!("-- CHUNK: \n{}", + std::str::from_utf8(&chunk).unwrap());}) + .fold((), |_, _| result(Ok(())))) + }, + multipart::MultipartItem::Nested(mp) => { + // Or item could be nested Multipart stream + Either::B(result(Ok(()))) + } + } + }) +} +``` + +Full example is available in +[examples directory](https://github.com/actix/actix-web/tree/master/examples/multipart/). ## Urlencoded body From 009874125ec5760db2f4c611ae1475e63ae0134f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Dec 2017 10:25:23 -0800 Subject: [PATCH 134/279] add client.py comments --- examples/multipart/client.py | 4 +++- examples/multipart/src/main.rs | 10 ++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/multipart/client.py b/examples/multipart/client.py index 2e3068d4c..afc07f17d 100644 --- a/examples/multipart/client.py +++ b/examples/multipart/client.py @@ -1,7 +1,9 @@ +# This script could be used for actix-web multipart example test +# just start server and run client.py + import asyncio import aiohttp - async def req1(): with aiohttp.MultipartWriter() as writer: writer.append('test') diff --git a/examples/multipart/src/main.rs b/examples/multipart/src/main.rs index ed804fa88..b4ed98787 100644 --- a/examples/multipart/src/main.rs +++ b/examples/multipart/src/main.rs @@ -13,11 +13,10 @@ fn index(mut req: HttpRequest) -> Box> { println!("{:?}", req); - // get multipart stream and iterate over multipart items Box::new( - req.multipart() // <- get multipart stream for current request - .map_err(Error::from) - .and_then(|item| { // <- iterate over multipart items + req.multipart() // <- get multipart stream for current request + .map_err(Error::from) // <- convert multipart errors + .and_then(|item| { // <- iterate over multipart items match item { // Handle multipart Field multipart::MultipartItem::Field(field) => { @@ -50,8 +49,7 @@ fn main() { HttpServer::new( || Application::new() - // enable logger - .middleware(middlewares::Logger::default()) + .middleware(middlewares::Logger::default()) // <- logger .resource("/multipart", |r| r.method(Method::POST).a(index))) .bind("127.0.0.1:8080").unwrap() .start(); From 566066e8557a7bfe19c520f20f674d71d8b9a638 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Dec 2017 10:17:34 -0800 Subject: [PATCH 135/279] check examples --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index cc0574091..60dd57ede 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,6 +29,11 @@ before_script: script: - USE_SKEPTIC=1 cargo test --features=alpn + - cd examples/diesel && cargo check && cd ../.. + - cd examples/multipart && cargo check && cd ../.. + - cd examples/template_tera && cargo check && cd ../.. + - cd examples/tls && cargo check && cd ../.. + - cd examples/websocket-chat && cargo check && cd ../.. - | if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then cargo clippy From 2bad99b6456132f09b7f0156ec3d00628b222cff Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Dec 2017 11:34:51 -0800 Subject: [PATCH 136/279] better query() method impl; update doc strings --- guide/src/qs_5.md | 14 +++++++++++--- src/httprequest.rs | 39 +++++++++++++++++++++++++++++++++------ 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index 46b41edaa..8451c3692 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -317,10 +317,18 @@ resource with the name "foo" and the pattern "{a}/{b}/{c}", you might do this. # use actix_web::httpcodes::*; # fn index(req: HttpRequest) -> HttpResponse { - let url = req.url_for("foo", &["1", "2", "3"]); - HTTPOk.into() + let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource + HTTPOk.into() +} + +fn main() { + let app = Application::new() + .resource("/test/{one}/{two}/{three}", |r| { + r.name("foo"); // <- set resource name, then it could be used in `url_for` + r.method(Method::GET).f(|_| httpcodes::HTTPOk); + }) + .finish(); } -# fn main() {} ``` This would return something like the string *http://example.com/1/2/3* (at least if diff --git a/src/httprequest.rs b/src/httprequest.rs index 067879edc..1944fc4d8 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -1,5 +1,6 @@ //! HTTP Request message related code. use std::{str, fmt, mem}; +use std::borrow::Cow; use std::rc::Rc; use std::net::SocketAddr; use std::collections::HashMap; @@ -235,6 +236,27 @@ impl HttpRequest { self.as_ref().info.as_ref().unwrap() } + /// Generate url for named resource + /// + /// ```rust + /// # extern crate actix_web; + /// # use actix_web::*; + /// # use actix_web::httpcodes::*; + /// # + /// fn index(req: HttpRequest) -> HttpResponse { + /// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource + /// HTTPOk.into() + /// } + /// + /// fn main() { + /// let app = Application::new() + /// .resource("/test/{one}/{two}/{three}", |r| { + /// r.name("foo"); // <- set resource name, then it could be used in `url_for` + /// r.method(Method::GET).f(|_| httpcodes::HTTPOk); + /// }) + /// .finish(); + /// } + /// ``` pub fn url_for(&self, name: &str, elements: U) -> Result where U: IntoIterator, I: AsRef, @@ -252,11 +274,18 @@ impl HttpRequest { } } + /// This method returns reference to current `Router` object. #[inline] pub fn router(&self) -> Option<&Router> { self.2.as_ref() } + /// Peer socket address + /// + /// Peer address is actuall 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()` method should be used. #[inline] pub fn peer_addr(&self) -> Option<&SocketAddr> { self.as_ref().addr.as_ref() @@ -268,12 +297,10 @@ impl HttpRequest { } /// Return a new iterator that yields pairs of `Cow` for query parameters - pub fn query(&self) -> HashMap { - let mut q: HashMap = HashMap::new(); - if let Some(query) = self.as_ref().uri.query().as_ref() { - for (key, val) in form_urlencoded::parse(query.as_ref()) { - q.insert(key.to_string(), val.to_string()); - } + pub fn query(&self) -> HashMap, Cow> { + let mut q = HashMap::new(); + for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) { + q.insert(key, val); } q } From db7bd962cb927ece14dc18f7c270095a51ab9a93 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Dec 2017 11:46:11 -0800 Subject: [PATCH 137/279] fix some doc strings --- src/httprequest.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index 1944fc4d8..4da687b0b 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -223,7 +223,7 @@ impl HttpRequest { /// The target path of this Request. #[inline] pub fn path(&self) -> &str { - self.as_ref().uri.path() + self.uri().path() } /// Get *ConnectionInfo* for currect request. @@ -310,7 +310,7 @@ impl HttpRequest { /// E.g., id=10 #[inline] pub fn query_string(&self) -> &str { - if let Some(query) = self.as_ref().uri.query().as_ref() { + if let Some(query) = self.uri().query().as_ref() { query } else { "" @@ -355,7 +355,7 @@ impl HttpRequest { unsafe{ mem::transmute(&self.as_ref().params) } } - /// Set request Params. + /// Get mutable reference to request's Params. #[inline] pub(crate) fn match_info_mut(&mut self) -> &mut Params { unsafe{ mem::transmute(&mut self.as_mut().params) } @@ -366,7 +366,8 @@ impl HttpRequest { self.as_ref().keep_alive() } - /// Read the request content type + /// Read the request content type. If request does not contain + /// *Content-Type* header, empty str get returned. pub fn content_type(&self) -> &str { if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { if let Ok(content_type) = content_type.to_str() { From 52c98657166bc0fba4c0015ba547796a7377e1d8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Dec 2017 12:22:11 -0800 Subject: [PATCH 138/279] split examples check --- .travis.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 60dd57ede..2588f46c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,11 +29,17 @@ before_script: script: - USE_SKEPTIC=1 cargo test --features=alpn - - cd examples/diesel && cargo check && cd ../.. - - cd examples/multipart && cargo check && cd ../.. - - cd examples/template_tera && cargo check && cd ../.. - - cd examples/tls && cargo check && cd ../.. - - cd examples/websocket-chat && cargo check && cd ../.. + - | + if [[ "$TRAVIS_RUST_VERSION" == "1.20.0" ]]; then + cd examples/diesel && cargo check && cd ../.. + cd examples/multipart && cargo check && cd ../.. + fi + - | + if [[ "$TRAVIS_RUST_VERSION" == "beta" ]]; then + cd examples/template_tera && cargo check && cd ../.. + cd examples/tls && cargo check && cd ../.. + cd examples/websocket-chat && cargo check && cd ../.. + fi - | if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then cargo clippy From fa2a3bc55edde9ac315dd8a6ac0d77c12a986bb1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Dec 2017 13:11:19 -0800 Subject: [PATCH 139/279] make method private --- src/httprequest.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index 4da687b0b..660d172a1 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -154,7 +154,7 @@ impl HttpRequest { #[inline] /// Construct new http request without state. - pub fn clone_without_state(&self) -> HttpRequest { + pub(crate) fn clone_without_state(&self) -> HttpRequest { HttpRequest(self.0.clone(), None, None) } From 1596f4db73e91b6bf9459939e9543ba93542916d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Dec 2017 14:03:01 -0800 Subject: [PATCH 140/279] refactor url encoded body parsing --- guide/src/qs_7.md | 32 +++++++++- src/error.rs | 11 +++- src/h1.rs | 2 + src/httprequest.rs | 141 +++++++++++++++++++++++++++++++-------------- 4 files changed, 141 insertions(+), 45 deletions(-) diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 848cdcd09..b69e67899 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -155,7 +155,37 @@ Full example is available in ## Urlencoded body -[WIP] +Actix provides support for *application/x-www-form-urlencoded* encoded body. +`HttpResponse::urlencoded()` method returns +[*UrlEncoded*](../actix_web/dev/struct.UrlEncoded.html) future, it resolves +into `HashMap` which contains decoded parameters. +*UrlEncoded* future can resolve into a error in several cases: + +* content type is not `application/x-www-form-urlencoded` +* transfer encoding is `chunked`. +* content-length is greater than 256k +* payload terminates with error. + + +```rust +# extern crate actix_web; +# extern crate futures; +use actix_web::*; +use futures::future::{Future, ok}; + +fn index(mut req: HttpRequest) -> Box> { + Box::new( + req.urlencoded() // <- get UrlEncoded future + .and_then(|params| { // <- url encoded parameters + println!("==== BODY ==== {:?}", params); + ok(httpcodes::HTTPOk.response()) + }) + .map_err(Error::from) + ) +} +# fn main() {} +``` + ## Streaming request diff --git a/src/error.rs b/src/error.rs index 98f26e4cb..275485603 100644 --- a/src/error.rs +++ b/src/error.rs @@ -360,7 +360,7 @@ impl ResponseError for WsHandshakeError { } /// A set of errors that can occur during parsing urlencoded payloads -#[derive(Fail, Debug, PartialEq)] +#[derive(Fail, Debug)] pub enum UrlencodedError { /// Can not decode chunked transfer encoding #[fail(display="Can not decode chunked transfer encoding")] @@ -374,6 +374,9 @@ pub enum UrlencodedError { /// Content type error #[fail(display="Content type error")] ContentType, + /// Payload error + #[fail(display="Error that occur during reading payload")] + Payload(PayloadError), } /// Return `BadRequest` for `UrlencodedError` @@ -384,6 +387,12 @@ impl ResponseError for UrlencodedError { } } +impl From for UrlencodedError { + fn from(err: PayloadError) -> UrlencodedError { + UrlencodedError::Payload(err) + } +} + /// Errors which can occur when attempting to interpret a segment string as a /// valid path segment. #[derive(Fail, Debug, PartialEq)] diff --git a/src/h1.rs b/src/h1.rs index bfd187e19..9af1d8cdd 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -107,6 +107,8 @@ impl Http1 } } + // TODO: refacrtor + #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] pub fn poll(&mut self) -> Poll { // keep-alive timer if self.keepalive_timer.is_some() { diff --git a/src/httprequest.rs b/src/httprequest.rs index 660d172a1..e48cfcd85 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -17,8 +17,7 @@ use router::Router; use payload::Payload; use multipart::Multipart; use helpers::SharedHttpMessage; -use error::{ParseError, PayloadError, UrlGenerationError, - CookieParseError, HttpRangeError, UrlencodedError}; +use error::{ParseError, UrlGenerationError, CookieParseError, HttpRangeError, UrlencodedError}; pub struct HttpMessage { @@ -447,41 +446,27 @@ impl HttpRequest { /// * content type is not `application/x-www-form-urlencoded` /// * transfer encoding is `chunked`. /// * content-length is greater than 256k - pub fn urlencoded(&mut self) -> Result { - if let Ok(true) = self.chunked() { - return Err(UrlencodedError::Chunked) - } - - if let Some(len) = self.headers().get(header::CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - if len > 262_144 { - return Err(UrlencodedError::Overflow) - } - } else { - return Err(UrlencodedError::UnknownLength) - } - } else { - return Err(UrlencodedError::UnknownLength) - } - } - - // check content type - let t = if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - content_type.to_lowercase() == "application/x-www-form-urlencoded" - } else { - false - } - } else { - false - }; - - if t { - Ok(UrlEncoded{pl: self.payload().clone(), body: BytesMut::new()}) - } else { - Err(UrlencodedError::ContentType) - } + /// + /// ```rust + /// # extern crate actix_web; + /// # extern crate futures; + /// use actix_web::*; + /// use futures::future::{Future, ok}; + /// + /// fn index(mut req: HttpRequest) -> Box> { + /// Box::new( + /// req.urlencoded() // <- get UrlEncoded future + /// .and_then(|params| { // <- url encoded parameters + /// println!("==== BODY ==== {:?}", params); + /// ok(httpcodes::HTTPOk.response()) + /// }) + /// .map_err(Error::from) + /// ) + /// } + /// # fn main() {} + /// ``` + pub fn urlencoded(&mut self) -> UrlEncoded { + UrlEncoded::from_request(self) } } @@ -526,13 +511,59 @@ impl fmt::Debug for HttpRequest { pub struct UrlEncoded { pl: Payload, body: BytesMut, + error: Option, +} + +impl UrlEncoded { + pub fn from_request(req: &mut HttpRequest) -> UrlEncoded { + let mut encoded = UrlEncoded { + pl: req.payload_mut().clone(), + body: BytesMut::new(), + error: None + }; + + if let Ok(true) = req.chunked() { + encoded.error = Some(UrlencodedError::Chunked); + } else if let Some(len) = req.headers().get(header::CONTENT_LENGTH) { + if let Ok(s) = len.to_str() { + if let Ok(len) = s.parse::() { + if len > 262_144 { + encoded.error = Some(UrlencodedError::Overflow); + } + } else { + encoded.error = Some(UrlencodedError::UnknownLength); + } + } else { + encoded.error = Some(UrlencodedError::UnknownLength); + } + } + + // check content type + if encoded.error.is_none() { + if let Some(content_type) = req.headers().get(header::CONTENT_TYPE) { + if let Ok(content_type) = content_type.to_str() { + if content_type.to_lowercase() == "application/x-www-form-urlencoded" { + return encoded + } + } + } + encoded.error = Some(UrlencodedError::ContentType); + return encoded + } + + encoded + } } impl Future for UrlEncoded { type Item = HashMap; - type Error = PayloadError; + type Error = UrlencodedError; fn poll(&mut self) -> Poll { + if let Some(err) = self.error.take() { + return Err(err) + } + loop { return match self.pl.poll() { Ok(Async::NotReady) => Ok(Async::NotReady), @@ -547,7 +578,7 @@ impl Future for UrlEncoded { self.body.extend_from_slice(&item); continue }, - Err(err) => Err(err), + Err(err) => Err(err.into()), } } } @@ -673,6 +704,30 @@ mod tests { assert!(req.chunked().is_err()); } + impl PartialEq for UrlencodedError { + fn eq(&self, other: &UrlencodedError) -> bool { + match *self { + UrlencodedError::Chunked => match *other { + UrlencodedError::Chunked => true, + _ => false, + }, + UrlencodedError::Overflow => match *other { + UrlencodedError::Overflow => true, + _ => false, + }, + UrlencodedError::UnknownLength => match *other { + UrlencodedError::UnknownLength => true, + _ => false, + }, + UrlencodedError::ContentType => match *other { + UrlencodedError::ContentType => true, + _ => false, + }, + _ => false, + } + } + } + #[test] fn test_urlencoded_error() { let mut headers = HeaderMap::new(); @@ -681,7 +736,7 @@ mod tests { let mut req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); - assert_eq!(req.urlencoded().err().unwrap(), UrlencodedError::Chunked); + assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Chunked); let mut headers = HeaderMap::new(); headers.insert(header::CONTENT_TYPE, @@ -691,7 +746,7 @@ mod tests { let mut req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); - assert_eq!(req.urlencoded().err().unwrap(), UrlencodedError::UnknownLength); + assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::UnknownLength); let mut headers = HeaderMap::new(); headers.insert(header::CONTENT_TYPE, @@ -701,7 +756,7 @@ mod tests { let mut req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); - assert_eq!(req.urlencoded().err().unwrap(), UrlencodedError::Overflow); + assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Overflow); let mut headers = HeaderMap::new(); headers.insert(header::CONTENT_TYPE, @@ -711,7 +766,7 @@ mod tests { let mut req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); - assert_eq!(req.urlencoded().err().unwrap(), UrlencodedError::ContentType); + assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::ContentType); } #[test] From 64d867d9a1a56a54c4cdf6c73635a5e4b2f7003c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Dec 2017 15:44:25 -0800 Subject: [PATCH 141/279] update session guide section --- guide/src/qs_10.md | 61 ++++++++++++++++++++++++++++++++++++-- src/middlewares/session.rs | 32 ++++++++++++++++++++ 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index 10e5c7bc6..aefd08717 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -64,7 +64,8 @@ INFO:actix_web::middlewares::logger: 127.0.0.1:59947 [02/Dec/2017:00:22:40 -0800 ## Default headers To set default response headers `DefaultHeaders` middleware could be used. -*DefaultHeaders* middleware does not set header if response headers contains header. +*DefaultHeaders* middleware does not set header if response headers already contains +specified header. ```rust # extern crate actix_web; @@ -86,4 +87,60 @@ fn main() { ## User sessions -[WIP] +Actix provides general solution for session management. +[*Session storage*](../actix_web/middlewares/struct.SessionStorage.html) middleare can be +use with different backend types to store session data in different backends. +By default only cookie session backend is implemented. Other backend implementations +could be added later. + +[*Cookie session backend*](../actix_web/middlewares/struct.CookieSessionBackend.html) +uses signed cookies as session storage. *Cookie session backend* creates sessions which +are limited to storing fewer than 4000 bytes of data (as the payload must fit into a +single cookie). Internal server error get generated if session contains more than 4000 bytes. + +You need to pass a random value to the constructor of *CookieSessionBackend*. +This is private key for cookie session. When this value is changed, all session data is lost. +Note that whatever you write into your session is visible by the user (but not modifiable). + +In general case, you cretate +[*Session storage*](../actix_web/middlewares/struct.SessionStorage.html) middleware +and initializes it with specific backend implementation, like *CookieSessionBackend*. +To access session data +[*HttpRequest::session()*](../actix_web/middlewares/trait.RequestSession.html#tymethod.session) +method has to be used. This method returns +[*Session*](../actix_web/middlewares/struct.Session.html) object, which allows to get or set +session data. + +```rust +# extern crate actix; +# extern crate actix_web; +use actix_web::*; +use actix_web::middlewares::RequestSession; + +fn index(mut req: HttpRequest) -> Result<&'static str> { + // access session data + if let Some(count) = req.session().get::("counter")? { + println!("SESSION value: {}", count); + req.session().set("counter", count+1)?; + } else { + req.session().set("counter", 1)?; + } + + Ok("Welcome!") +} + +fn main() { +# let sys = actix::System::new("basic-example"); + HttpServer::new( + || Application::new() + .middleware(middlewares::SessionStorage::new( // <- create session middlewares + middlewares::CookieSessionBackend::build(&[0; 32]) // <- create cookie session backend + .secure(false) + .finish() + ))) + .bind("127.0.0.1:59880").unwrap() + .start(); +# actix::Arbiter::system().send(actix::msgs::SystemExit(0)); +# let _ = sys.run(); +} +``` diff --git a/src/middlewares/session.rs b/src/middlewares/session.rs index a807b0c03..25caffa6e 100644 --- a/src/middlewares/session.rs +++ b/src/middlewares/session.rs @@ -42,6 +42,23 @@ impl RequestSession for HttpRequest { /// Session object could be obtained with /// [`RequestSession::session`](trait.RequestSession.html#tymethod.session) /// method. `RequestSession` trait is implemented for `HttpRequest`. +/// +/// ```rust +/// use actix_web::*; +/// use actix_web::middlewares::RequestSession; +/// +/// fn index(mut req: HttpRequest) -> Result<&'static str> { +/// // access session data +/// if let Some(count) = req.session().get::("counter")? { +/// req.session().set("counter", count+1)?; +/// } else { +/// req.session().set("counter", 1)?; +/// } +/// +/// Ok("Welcome!") +/// } +/// # fn main() {} +/// ``` pub struct Session<'a>(&'a mut SessionImpl); impl<'a> Session<'a> { @@ -80,6 +97,21 @@ unsafe impl Send for SessionImplBox {} unsafe impl Sync for SessionImplBox {} /// Session storage middleware +/// +/// ```rust +/// # extern crate actix; +/// # extern crate actix_web; +/// use actix_web::*; +/// +/// fn main() { +/// let app = Application::new() +/// .middleware(middlewares::SessionStorage::new( // <- create session middlewares +/// middlewares::CookieSessionBackend::build(&[0; 32]) // <- create cookie session backend +/// .secure(false) +/// .finish()) +/// ); +/// } +/// ``` pub struct SessionStorage(T, PhantomData); impl> SessionStorage { From 626999bcc927f6a092980076159094204e4a61bf Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Dec 2017 16:09:19 -0800 Subject: [PATCH 142/279] update doc strings --- src/middlewares/session.rs | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/middlewares/session.rs b/src/middlewares/session.rs index 25caffa6e..6edba1983 100644 --- a/src/middlewares/session.rs +++ b/src/middlewares/session.rs @@ -20,6 +20,23 @@ use httpresponse::HttpResponse; use middlewares::{Middleware, Started, Response}; /// The helper trait to obtain your session data from a request. +/// +/// ```rust +/// use actix_web::*; +/// use actix_web::middlewares::RequestSession; +/// +/// fn index(mut req: HttpRequest) -> Result<&'static str> { +/// // access session data +/// if let Some(count) = req.session().get::("counter")? { +/// req.session().set("counter", count+1)?; +/// } else { +/// req.session().set("counter", 1)?; +/// } +/// +/// Ok("Welcome!") +/// } +/// # fn main() {} +/// ``` pub trait RequestSession { fn session(&mut self) -> Session; } @@ -101,14 +118,15 @@ unsafe impl Sync for SessionImplBox {} /// ```rust /// # extern crate actix; /// # extern crate actix_web; +/// # use actix_web::middlewares::{SessionStorage, CookieSessionBackend}; /// use actix_web::*; /// /// fn main() { -/// let app = Application::new() -/// .middleware(middlewares::SessionStorage::new( // <- create session middlewares -/// middlewares::CookieSessionBackend::build(&[0; 32]) // <- create cookie session backend -/// .secure(false) -/// .finish()) +/// let app = Application::new().middleware( +/// SessionStorage::new( // <- create session middlewares +/// CookieSessionBackend::build(&[0; 32]) // <- create cookie session backend +/// .secure(false) +/// .finish()) /// ); /// } /// ``` From d41aade0b74a513898a757eaa09892a206ba6eb8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Dec 2017 16:14:47 -0800 Subject: [PATCH 143/279] update readme --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 79c31d900..de2fcccd4 100644 --- a/README.md +++ b/README.md @@ -66,3 +66,6 @@ This project is licensed under either of http://opensource.org/licenses/MIT) at your option. + + +[![Analytics](https://ga-beacon.appspot.com/UA-111455201-1/actix-web/readme)](https://github.com/igrigorik/ga-beacon) From 0a96b8c579f4476341042e30134bcadaacdb27d9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Dec 2017 16:17:27 -0800 Subject: [PATCH 144/279] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index de2fcccd4..87edf9c33 100644 --- a/README.md +++ b/README.md @@ -68,4 +68,4 @@ This project is licensed under either of at your option. -[![Analytics](https://ga-beacon.appspot.com/UA-111455201-1/actix-web/readme)](https://github.com/igrigorik/ga-beacon) +[![Analytics](https://ga-beacon.appspot.com/UA-110322332-2/actix-web/readme)](https://github.com/igrigorik/ga-beacon) From 50b2f62c8013454d3fc148995b70319bad44d42c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Dec 2017 18:36:29 -0800 Subject: [PATCH 145/279] update guide section about ssl --- guide/src/qs_13.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/guide/src/qs_13.md b/guide/src/qs_13.md index ee0c21f17..193b2e109 100644 --- a/guide/src/qs_13.md +++ b/guide/src/qs_13.md @@ -28,7 +28,8 @@ fn main() { HttpServer::new( || Application::new() .resource("/index.html", |r| r.f(index))) - .serve_ssl::<_, ()>("127.0.0.1:8080", pkcs12).unwrap(); + .bind("127.0.0.1:8080").unwrap(); + .serve_ssl(pkcs12).unwrap(); } ``` From d0c01c2cdd668a577e922886d4058889f3989ce1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Dec 2017 18:38:02 -0800 Subject: [PATCH 146/279] update guide example --- guide/src/qs_10.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index aefd08717..f7c499458 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -115,7 +115,7 @@ session data. # extern crate actix; # extern crate actix_web; use actix_web::*; -use actix_web::middlewares::RequestSession; +use actix_web::middlewares::{RequestSession, SessionStorage, CookieSessionBackend}; fn index(mut req: HttpRequest) -> Result<&'static str> { // access session data @@ -133,8 +133,8 @@ fn main() { # let sys = actix::System::new("basic-example"); HttpServer::new( || Application::new() - .middleware(middlewares::SessionStorage::new( // <- create session middlewares - middlewares::CookieSessionBackend::build(&[0; 32]) // <- create cookie session backend + .middleware(SessionStorage::new( // <- create session middlewares + CookieSessionBackend::build(&[0; 32]) // <- create cookie session backend .secure(false) .finish() ))) From c47e2ccfeed40d9ffc0b2a9160a144ee91cd45b5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Dec 2017 18:44:17 -0800 Subject: [PATCH 147/279] update guide examples --- guide/src/qs_2.md | 6 ++++-- guide/src/qs_3_5.md | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md index b76855c85..a3b9e8155 100644 --- a/guide/src/qs_2.md +++ b/guide/src/qs_2.md @@ -58,7 +58,9 @@ After that, application instance can be used with `HttpServer` to listen for inc connections. Server accepts function that should return `HttpHandler` instance: ```rust,ignore - HttpServer::new(|| app).serve::<_, ()>("127.0.0.1:8088"); + HttpServer::new(|| app) + .bind("127.0.0.1:8088")? + .start(); ``` That's it. Now, compile and run the program with cargo run. @@ -92,5 +94,5 @@ fn main() { Note on `actix` crate. Actix web framework is built on top of actix actor library. `actix::System` initializes actor system, `HttpServer` is an actor and must run within -proper configured actix system. For more information please check +properly configured actix system. For more information please check [actix documentation](https://actix.github.io/actix/actix/) diff --git a/guide/src/qs_3_5.md b/guide/src/qs_3_5.md index 01ccffd99..dfde017dc 100644 --- a/guide/src/qs_3_5.md +++ b/guide/src/qs_3_5.md @@ -47,7 +47,7 @@ fn main() { HttpServer::::new( || Application::new() .resource("/", |r| r.f(|_| httpcodes::HTTPOk))) - .threads(4); // <- Start 4 threads + .threads(4); // <- Start 4 workers } ``` @@ -88,7 +88,7 @@ Note on *HTTP/2.0* protocol over tls without prior knowledge, it requires `openssl` has `alpn ` support. Please check [example](https://github.com/actix/actix-web/tree/master/examples/tls) -for concrete example. +for full example. ## Keep-Alive From 7fc7d6e17a6e1aca2e5bc33e468ebf832905090c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Dec 2017 22:36:06 -0800 Subject: [PATCH 148/279] update guide --- guide/src/qs_5.md | 36 ++++++++++++++++++++++++++++-------- src/resource.rs | 2 +- src/route.rs | 16 ++++++++++++++++ 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index 8451c3692..7eb1ac809 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -83,6 +83,27 @@ if request contains `Content-Type` header and value of this header is *text/plai and path equals to `/test`. Resource calls handle of the first matches route. If resource can not match any route "NOT FOUND" response get returned. +[*Resource::route()*](../actix_web/struct.Resource.html#method.route) method returns +[*Route*](../actix_web/struct.Route.html) object. Route can be configured with +builder-like pattern. Following configuration methods are available: + +* [*Route::p()*](../actix_web/struct.Route.html#method.p) method registers new predicate, + any number of predicates could be registered for each route. + +* [*Route::f()*](../actix_web/struct.Route.html#method.f) method registers handler function + for this route. Only one handler could be registered. Usually handler registeration + is the last config operation. Handler fanction could be function or closure and has type + `Fn(HttpRequest) -> R + 'static` + +* [*Route::h()*](../actix_web/struct.Route.html#method.h) method registers handler object + that implements `Handler` trait. This is similar to `f()` method, only one handler could + be registered. Handler registeration is the last config operation. + +* [*Route::a()*](../actix_web/struct.Route.html#method.a) method registers asynchandler + function for this route. Only one handler could be registered. Handler registeration + is the last config operation. Handler fanction could be function or closure and has type + `Fn(HttpRequest) -> Future + 'static` + ## Route matching The main purpose of route configuration is to match (or not match) the request's `path` @@ -323,7 +344,7 @@ fn index(req: HttpRequest) -> HttpResponse { fn main() { let app = Application::new() - .resource("/test/{one}/{two}/{three}", |r| { + .resource("/test/{a}/{b}/{c}", |r| { r.name("foo"); // <- set resource name, then it could be used in `url_for` r.method(Method::GET).f(|_| httpcodes::HTTPOk); }) @@ -331,7 +352,7 @@ fn main() { } ``` -This would return something like the string *http://example.com/1/2/3* (at least if +This would return something like the string *http://example.com/test/1/2/3* (at least if the current protocol and hostname implied http://example.com). `url_for()` method return [*Url object*](https://docs.rs/url/1.6.0/url/struct.Url.html) so you can modify this url (add query parameters, anchor, etc). @@ -462,8 +483,7 @@ and returns *true* or *false*. Formally predicate is any object that implements several predicates, you can check [functions section](../actix_web/pred/index.html#functions) of api docs. -Here is simple predicates that check that request contains specific *header* and predicate -usage: +Here is simple predicates that check that request contains specific *header*: ```rust # extern crate actix_web; @@ -538,9 +558,9 @@ predicates match. i.e: ## Changing the default Not Found response If path pattern can not be found in routing table or resource can not find matching -route default resource is used. Default response is *NOT FOUND* response. -To override *NOT FOUND* resource use `Application::default_resource()` method. -This method accepts *configuration function* same as normal resource registration +route, default resource is used. Default response is *NOT FOUND* response. +It is possible to override *NOT FOUND* response with `Application::default_resource()` method. +This method accepts *configuration function* same as normal resource configuration with `Application::resource()` method. ```rust @@ -555,6 +575,6 @@ fn main() { r.method(Method::GET).f(|req| HTTPNotFound); r.route().p(pred::Not(pred::Get())).f(|req| HTTPMethodNotAllowed); }) - .finish(); +# .finish(); } ``` diff --git a/src/resource.rs b/src/resource.rs index af6151474..b914cfa3b 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -92,7 +92,7 @@ impl Resource { /// This is shortcut for: /// /// ```rust,ignore - /// Resource::resource("/", |r| r.route().method(Method::GET).f(index) + /// Resource::resource("/", |r| r.route().p(pred::Get()).f(index) /// ``` pub fn method(&mut self, method: Method) -> &mut Route { self.routes.push(Route::default()); diff --git a/src/route.rs b/src/route.rs index fa2c78130..284daaf64 100644 --- a/src/route.rs +++ b/src/route.rs @@ -43,6 +43,22 @@ impl Route { } /// Add match predicate to route. + /// + /// ```rust + /// # extern crate actix_web; + /// # use actix_web::*; + /// # use actix_web::httpcodes::*; + /// # fn main() { + /// Application::new() + /// .resource("/path", |r| + /// r.route() + /// .p(pred::Get()) + /// .p(pred::Header("content-type", "text/plain")) + /// .f(|req| HTTPOk) + /// ) + /// # .finish(); + /// # } + /// ``` pub fn p(&mut self, p: Box>) -> &mut Self { self.preds.push(p); self From 65767558fbdcb27151d632db6f06f8a1f1d83ce6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Dec 2017 08:00:28 -0800 Subject: [PATCH 149/279] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 87edf9c33..7b5478b90 100644 --- a/README.md +++ b/README.md @@ -68,4 +68,4 @@ This project is licensed under either of at your option. -[![Analytics](https://ga-beacon.appspot.com/UA-110322332-2/actix-web/readme)](https://github.com/igrigorik/ga-beacon) +[![Analytics](https://ga-beacon.appspot.com/UA-110322332-2/actix-web/readme?pixel)](https://github.com/igrigorik/ga-beacon) From e05596b65d9b4de8a2c2fad6e1bd5bdea8a9c5d2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Dec 2017 11:37:27 -0800 Subject: [PATCH 150/279] upgrade actix min version --- Cargo.toml | 2 +- examples/multipart/Cargo.toml | 2 +- examples/multipart/src/main.rs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5d80682d3..e6190d3f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,7 +73,7 @@ tokio-tls = { version="0.1", optional = true } tokio-openssl = { version="0.1", optional = true } [dependencies.actix] -version = "^0.3.1" +version = "^0.3.4" default-features = false features = [] diff --git a/examples/multipart/Cargo.toml b/examples/multipart/Cargo.toml index bfe89f82b..03ab65294 100644 --- a/examples/multipart/Cargo.toml +++ b/examples/multipart/Cargo.toml @@ -10,5 +10,5 @@ path = "src/main.rs" [dependencies] env_logger = "*" futures = "0.1" -actix = "^0.3.1" +actix = "^0.3.4" actix-web = { git = "https://github.com/actix/actix-web.git" } diff --git a/examples/multipart/src/main.rs b/examples/multipart/src/main.rs index b4ed98787..afe3a3f8d 100644 --- a/examples/multipart/src/main.rs +++ b/examples/multipart/src/main.rs @@ -4,6 +4,7 @@ extern crate actix_web; extern crate env_logger; extern crate futures; +use actix::*; use actix_web::*; use futures::{Future, Stream}; use futures::future::{result, Either}; @@ -28,7 +29,7 @@ fn index(mut req: HttpRequest) -> Box> .map(|chunk| { println!("-- CHUNK: \n{}", std::str::from_utf8(&chunk).unwrap());}) - .fold((), |_, _| result::<_, Error>(Ok(())))) + .finish()) }, multipart::MultipartItem::Nested(mp) => { // Or item could be nested Multipart stream @@ -36,8 +37,7 @@ fn index(mut req: HttpRequest) -> Box> } } }) - // wait until stream finishes - .fold((), |_, _| result::<_, Error>(Ok(()))) + .finish() // <- Stream::finish() combinator from actix .map(|_| httpcodes::HTTPOk.response()) ) } From 813b56ebe59224012210f560dd1a8b4cf592d04a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Dec 2017 12:51:39 -0800 Subject: [PATCH 151/279] make async handler future more generic --- guide/src/qs_4.md | 10 ++++++++-- src/handler.rs | 47 ++++++++++++++++++++++++++++++++--------------- src/route.rs | 10 +++++----- 3 files changed, 45 insertions(+), 22 deletions(-) diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index 25528c45d..dda86e278 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -95,8 +95,9 @@ fn main() { There are two different types of async handlers. -Response object could be generated asynchronously. In this case handle must -return `Future` object that resolves to `HttpResponse`, i.e: +Response object could be generated asynchronously or more precisely, any type +that implements [*Responder*](../actix_web/trait.Responder.html) trait. In this case handle must +return `Future` object that resolves to *Responder* type, i.e: ```rust # extern crate actix_web; @@ -114,9 +115,14 @@ fn index(req: HttpRequest) -> FutureResult { .map_err(|e| e.into())) } +fn index2(req: HttpRequest) -> FutureResult<&'static str, Error> { + result(Ok("Welcome!")) +} + fn main() { Application::new() .resource("/async", |r| r.route().a(index)) + .resource("/", |r| r.route().a(index2)) .finish(); } ``` diff --git a/src/handler.rs b/src/handler.rs index f0fbb1ea3..2293d9090 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use actix::Actor; -use futures::Future; +use futures::future::{Future, ok, err}; use serde_json; use serde::Serialize; use regex::Regex; @@ -221,36 +221,53 @@ impl RouteHandler for WrapHandler /// Async route handler pub(crate) -struct AsyncHandler - where F: Fn(HttpRequest) -> R + 'static, - R: Future + 'static, +struct AsyncHandler + where H: Fn(HttpRequest) -> F + 'static, + F: Future + 'static, + R: Responder + 'static, + E: Into + 'static, S: 'static, { - f: Box, + h: Box, s: PhantomData, } -impl AsyncHandler - where F: Fn(HttpRequest) -> R + 'static, - R: Future + 'static, +impl AsyncHandler + where H: Fn(HttpRequest) -> F + 'static, + F: Future + 'static, + R: Responder + 'static, + E: Into + 'static, S: 'static, { - pub fn new(f: F) -> Self { - AsyncHandler{f: Box::new(f), s: PhantomData} + pub fn new(h: H) -> Self { + AsyncHandler{h: Box::new(h), s: PhantomData} } } -impl RouteHandler for AsyncHandler - where F: Fn(HttpRequest) -> R + 'static, - R: Future + 'static, +impl RouteHandler for AsyncHandler + where H: Fn(HttpRequest) -> F + 'static, + F: Future + 'static, + R: Responder + 'static, + E: Into + 'static, S: 'static, { fn handle(&self, req: HttpRequest) -> Reply { - Reply::async((self.f)(req)) + let req2 = req.clone_without_state(); + let fut = (self.h)(req) + .map_err(|e| e.into()) + .then(move |r| { + match r.respond_to(req2) { + Ok(reply) => match reply.into().0 { + ReplyItem::Message(resp) => ok(resp), + _ => panic!("Nested async replies are not supported"), + } + Err(e) => err(e), + } + }); + Reply::async(fut) } } - /// Json response helper /// /// The `Json` type allows you to respond with well-formed JSON data: simply return a value of diff --git a/src/route.rs b/src/route.rs index 284daaf64..194a1c06c 100644 --- a/src/route.rs +++ b/src/route.rs @@ -5,8 +5,6 @@ use pred::Predicate; use handler::{Reply, Handler, Responder, RouteHandler, AsyncHandler, WrapHandler}; use httpcodes::HTTPNotFound; use httprequest::HttpRequest; -use httpresponse::HttpResponse; - /// Resource route definition /// @@ -80,9 +78,11 @@ impl Route { } /// Set async handler function. - pub fn a(&mut self, handler: F) - where F: Fn(HttpRequest) -> R + 'static, - R: Future + 'static, + pub fn a(&mut self, handler: H) + where H: Fn(HttpRequest) -> F + 'static, + F: Future + 'static, + R: Responder + 'static, + E: Into + 'static { self.handler = Box::new(AsyncHandler::new(handler)); } From 79f047f5be1ca6a74f40e9170fe8e081320e1780 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Dec 2017 13:23:50 -0800 Subject: [PATCH 152/279] remove box from predicates --- guide/src/qs_5.md | 9 ++-- src/handler.rs | 2 +- src/pred.rs | 133 +++++++++++++++++++++++++++++++--------------- src/resource.rs | 2 +- src/route.rs | 4 +- 5 files changed, 99 insertions(+), 51 deletions(-) diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index 7eb1ac809..65ee24c3f 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -506,9 +506,8 @@ fn main() { Application::new() .resource("/index.html", |r| r.route() - .p(Box::new(ContentTypeHeader)) - .f(|req| HTTPOk)) - .finish(); + .p(ContentTypeHeader) + .h(HTTPOk)); } ``` @@ -545,14 +544,14 @@ fn main() { predicates match. i.e: ```rust,ignore - pred::Any(vec![pred::Get(), pred::Post()]) + pred::Any(pred::Get()).or(pred::Post()) ``` `All` predicate accept list of predicates and matches if all of the supplied predicates match. i.e: ```rust,ignore - pred::All(vec![pred::Get(), pred::Header("content-type", "plain/text")]) + pred::All(pred::Get()).and(pred::Header("content-type", "plain/text")) ``` ## Changing the default Not Found response diff --git a/src/handler.rs b/src/handler.rs index 2293d9090..cbca0aed6 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -260,7 +260,7 @@ impl RouteHandler for AsyncHandler Ok(reply) => match reply.into().0 { ReplyItem::Message(resp) => ok(resp), _ => panic!("Nested async replies are not supported"), - } + }, Err(e) => err(e), } }); diff --git a/src/pred.rs b/src/pred.rs index 82283899f..47d906fb0 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -16,13 +16,36 @@ pub trait Predicate { } /// Return predicate that matches if any of supplied predicate matches. -pub fn Any(preds: T) -> Box> - where T: IntoIterator>> +/// +/// ```rust +/// # extern crate actix_web; +/// # extern crate http; +/// # use actix_web::*; +/// # use actix_web::httpcodes::*; +/// use actix_web::pred; +/// +/// fn main() { +/// Application::new() +/// .resource("/index.html", |r| r.route() +/// .p(pred::Any(pred::Get()).or(pred::Post())) +/// .h(HTTPMethodNotAllowed)); +/// } +/// ``` +pub fn Any + 'static>(pred: P) -> AnyPredicate { - Box::new(AnyPredicate(preds.into_iter().collect())) + AnyPredicate(vec![Box::new(pred)]) } -struct AnyPredicate(Vec>>); +/// Matches if any of supplied predicate matches. +pub struct AnyPredicate(Vec>>); + +impl AnyPredicate { + /// Add new predicate to list of predicates to check + pub fn or + 'static>(mut self, pred: P) -> Self { + self.0.push(Box::new(pred)); + self + } +} impl Predicate for AnyPredicate { fn check(&self, req: &mut HttpRequest) -> bool { @@ -36,13 +59,36 @@ impl Predicate for AnyPredicate { } /// Return predicate that matches if all of supplied predicate matches. -pub fn All(preds: T) -> Box> - where T: IntoIterator>> -{ - Box::new(AllPredicate(preds.into_iter().collect())) +/// +/// ```rust +/// # extern crate actix_web; +/// # extern crate http; +/// # use actix_web::*; +/// # use actix_web::httpcodes::*; +/// use actix_web::pred; +/// +/// fn main() { +/// Application::new() +/// .resource("/index.html", |r| r.route() +/// .p(pred::All(pred::Get()) +/// .and(pred::Header("content-type", "plain/text"))) +/// .h(HTTPMethodNotAllowed)); +/// } +/// ``` +pub fn All + 'static>(pred: P) -> AllPredicate { + AllPredicate(vec![Box::new(pred)]) } -struct AllPredicate(Vec>>); +/// Matches if all of supplied predicate matches. +pub struct AllPredicate(Vec>>); + +impl AllPredicate { + /// Add new predicate to list of predicates to check + pub fn and + 'static>(mut self, pred: P) -> Self { + self.0.push(Box::new(pred)); + self + } +} impl Predicate for AllPredicate { fn check(&self, req: &mut HttpRequest) -> bool { @@ -56,12 +102,13 @@ impl Predicate for AllPredicate { } /// Return predicate that matches if supplied predicate does not match. -pub fn Not(pred: Box>) -> Box> +pub fn Not + 'static>(pred: P) -> NotPredicate { - Box::new(NotPredicate(pred)) + NotPredicate(Box::new(pred)) } -struct NotPredicate(Box>); +#[doc(hidden)] +pub struct NotPredicate(Box>); impl Predicate for NotPredicate { fn check(&self, req: &mut HttpRequest) -> bool { @@ -70,7 +117,8 @@ impl Predicate for NotPredicate { } /// Http method predicate -struct MethodPredicate(http::Method, PhantomData); +#[doc(hidden)] +pub struct MethodPredicate(http::Method, PhantomData); impl Predicate for MethodPredicate { fn check(&self, req: &mut HttpRequest) -> bool { @@ -79,64 +127,65 @@ impl Predicate for MethodPredicate { } /// Predicate to match *GET* http method -pub fn Get() -> Box> { - Box::new(MethodPredicate(http::Method::GET, PhantomData)) +pub fn Get() -> MethodPredicate { + MethodPredicate(http::Method::GET, PhantomData) } /// Predicate to match *POST* http method -pub fn Post() -> Box> { - Box::new(MethodPredicate(http::Method::POST, PhantomData)) +pub fn Post() -> MethodPredicate { + MethodPredicate(http::Method::POST, PhantomData) } /// Predicate to match *PUT* http method -pub fn Put() -> Box> { - Box::new(MethodPredicate(http::Method::PUT, PhantomData)) +pub fn Put() -> MethodPredicate { + MethodPredicate(http::Method::PUT, PhantomData) } /// Predicate to match *DELETE* http method -pub fn Delete() -> Box> { - Box::new(MethodPredicate(http::Method::DELETE, PhantomData)) +pub fn Delete() -> MethodPredicate { + MethodPredicate(http::Method::DELETE, PhantomData) } /// Predicate to match *HEAD* http method -pub fn Head() -> Box> { - Box::new(MethodPredicate(http::Method::HEAD, PhantomData)) +pub fn Head() -> MethodPredicate { + MethodPredicate(http::Method::HEAD, PhantomData) } /// Predicate to match *OPTIONS* http method -pub fn Options() -> Box> { - Box::new(MethodPredicate(http::Method::OPTIONS, PhantomData)) +pub fn Options() -> MethodPredicate { + MethodPredicate(http::Method::OPTIONS, PhantomData) } /// Predicate to match *CONNECT* http method -pub fn Connect() -> Box> { - Box::new(MethodPredicate(http::Method::CONNECT, PhantomData)) +pub fn Connect() -> MethodPredicate { + MethodPredicate(http::Method::CONNECT, PhantomData) } /// Predicate to match *PATCH* http method -pub fn Patch() -> Box> { - Box::new(MethodPredicate(http::Method::PATCH, PhantomData)) +pub fn Patch() -> MethodPredicate { + MethodPredicate(http::Method::PATCH, PhantomData) } /// Predicate to match *TRACE* http method -pub fn Trace() -> Box> { - Box::new(MethodPredicate(http::Method::TRACE, PhantomData)) +pub fn Trace() -> MethodPredicate { + MethodPredicate(http::Method::TRACE, PhantomData) } /// Predicate to match specified http method -pub fn Method(method: http::Method) -> Box> { - Box::new(MethodPredicate(method, PhantomData)) +pub fn Method(method: http::Method) -> MethodPredicate { + MethodPredicate(method, PhantomData) } /// Return predicate that matches if request contains specified header and value. -pub fn Header(name: &'static str, value: &'static str) -> Box> +pub fn Header(name: &'static str, value: &'static str) -> HeaderPredicate { - Box::new(HeaderPredicate(header::HeaderName::try_from(name).unwrap(), - header::HeaderValue::from_static(value), - PhantomData)) + HeaderPredicate(header::HeaderName::try_from(name).unwrap(), + header::HeaderValue::from_static(value), + PhantomData) } -struct HeaderPredicate(header::HeaderName, header::HeaderValue, PhantomData); +#[doc(hidden)] +pub struct HeaderPredicate(header::HeaderName, header::HeaderValue, PhantomData); impl Predicate for HeaderPredicate { fn check(&self, req: &mut HttpRequest) -> bool { @@ -238,10 +287,10 @@ mod tests { assert!(Not(Get()).check(&mut r)); assert!(!Not(Trace()).check(&mut r)); - assert!(All(vec![Trace(), Trace()]).check(&mut r)); - assert!(!All(vec![Get(), Trace()]).check(&mut r)); + assert!(All(Trace()).and(Trace()).check(&mut r)); + assert!(!All(Get()).and(Trace()).check(&mut r)); - assert!(Any(vec![Get(), Trace()]).check(&mut r)); - assert!(!Any(vec![Get(), Get()]).check(&mut r)); + assert!(Any(Get()).or(Trace()).check(&mut r)); + assert!(!Any(Get()).or(Get()).check(&mut r)); } } diff --git a/src/resource.rs b/src/resource.rs index b914cfa3b..937c28251 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -76,7 +76,7 @@ impl Resource { /// let app = Application::new() /// .resource( /// "/", |r| r.route() - /// .p(pred::Any(vec![pred::Get(), pred::Put()])) + /// .p(pred::Any(pred::Get()).or(pred::Put())) /// .p(pred::Header("Content-Type", "text/plain")) /// .f(|r| HttpResponse::Ok())) /// .finish(); diff --git a/src/route.rs b/src/route.rs index 194a1c06c..404fa16d3 100644 --- a/src/route.rs +++ b/src/route.rs @@ -57,8 +57,8 @@ impl Route { /// # .finish(); /// # } /// ``` - pub fn p(&mut self, p: Box>) -> &mut Self { - self.preds.push(p); + pub fn p + 'static>(&mut self, p: T) -> &mut Self { + self.preds.push(Box::new(p)); self } From cbb81bc747da20ab98f0338d9ac878391c9089d3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Dec 2017 15:12:43 -0800 Subject: [PATCH 153/279] json request example --- examples/json/Cargo.toml | 16 ++++++++++ examples/json/client.py | 17 +++++++++++ examples/json/src/main.rs | 64 +++++++++++++++++++++++++++++++++++++++ guide/src/qs_7.md | 41 ++++++++++++++++++++++++- 4 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 examples/json/Cargo.toml create mode 100644 examples/json/client.py create mode 100644 examples/json/src/main.rs diff --git a/examples/json/Cargo.toml b/examples/json/Cargo.toml new file mode 100644 index 000000000..468b04900 --- /dev/null +++ b/examples/json/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "json-example" +version = "0.1.0" +authors = ["Nikolay Kim "] + +[dependencies] +bytes = "0.4" +futures = "0.1" +env_logger = "*" + +serde = "1.0" +serde_json = "1.0" +serde_derive = "1.0" + +actix = "^0.3.1" +actix-web = { git = "https://github.com/actix/actix-web.git" } diff --git a/examples/json/client.py b/examples/json/client.py new file mode 100644 index 000000000..31429443d --- /dev/null +++ b/examples/json/client.py @@ -0,0 +1,17 @@ +# This script could be used for actix-web multipart example test +# just start server and run client.py + +import json +import asyncio +import aiohttp + +async def req(): + resp = await aiohttp.ClientSession().request( + "post", 'http://localhost:8080/', + data=json.dumps({"name": "Test user", "number": 100}), + headers={"content-type": "application/json"}) + print(str(resp)) + assert 200 == resp.status + + +asyncio.get_event_loop().run_until_complete(req()) diff --git a/examples/json/src/main.rs b/examples/json/src/main.rs new file mode 100644 index 000000000..c271c12b0 --- /dev/null +++ b/examples/json/src/main.rs @@ -0,0 +1,64 @@ +extern crate actix; +extern crate actix_web; +extern crate bytes; +extern crate futures; +extern crate env_logger; +extern crate serde_json; +#[macro_use] extern crate serde_derive; + +use actix_web::*; +use bytes::BytesMut; +use futures::Stream; +use futures::future::{Future, ok, err, result}; + +#[derive(Debug, Deserialize)] +struct MyObj { + name: String, + number: i32, +} + +fn index(mut req: HttpRequest) -> Result>> { + // check content-type, early error exit + if req.content_type() != "application/json" { + return Err(error::ErrorBadRequest("wrong content-type").into()) + } + + Ok(Box::new( + // load request body + req.payload_mut() + .readany() + .fold(BytesMut::new(), |mut body, chunk| { + body.extend(chunk); + result::<_, error::PayloadError>(Ok(body)) + }) + .map_err(|e| Error::from(e)) + .and_then(|body| { + // body is loaded, now we can deserialize json + match serde_json::from_slice::(&body) { + Ok(obj) => { + println!("MODEL: {:?}", obj); // <- do something with payload + ok(httpcodes::HTTPOk.response()) // <- send response + }, + Err(e) => { + err(error::ErrorBadRequest(e).into()) + } + } + }))) +} + +fn main() { + ::std::env::set_var("RUST_LOG", "actix_web=info"); + let _ = env_logger::init(); + let sys = actix::System::new("json-example"); + + HttpServer::new(|| { + Application::new() + // enable logger + .middleware(middlewares::Logger::default()) + .resource("/", |r| r.method(Method::POST).f(index))}) + .bind("127.0.0.1:8080").unwrap() + .start(); + + println!("Started http server: 127.0.0.1:8080"); + let _ = sys.run(); +} diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index b69e67899..bb1a14367 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -55,7 +55,46 @@ fn index(req: HttpRequest) -> HttpResponse { } # fn main() {} ``` - + + +## JSON Request + +Unfortunately, because of async nature of actix web framework, deserializing json +requests is not very ergonomic process. First you need to load whole body into +temporal storage and only then you can deserialize it. + +Here is simple example. We will deserialize *MyObj* struct. + +```rust,ignore +#[derive(Debug, Deserialize)] +struct MyObj { + name: String, + number: i32, +} +``` + +We need to load request body first. + +```rust,ignore +fn index(mut req: HttpRequest) -> Future { + + req.payload_mut().readany() + .fold(BytesMut::new(), |mut body, chunk| { // <- load request body + body.extend(chunk); + result::<_, error::PayloadError>(Ok(body)) + }) + .and_then(|body| { // <- body is loaded, now we can deserialize json + let obj = serde_json::from_slice::(&body).unwrap(); + println!("MODEL: {:?}", obj); // <- do something with payload + ok(httpcodes::HTTPOk.response()) // <- send response + }) +} +``` + +Full example is available in +[examples directory](https://github.com/actix/actix-web/tree/master/examples/json/). + + ## JSON Response The `Json` type allows you to respond with well-formed JSON data: simply return a value of From 821c96c37c717b33ef8addd3bc43dcab40249092 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Dec 2017 15:20:28 -0800 Subject: [PATCH 154/279] check json example during travis build --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 2588f46c1..f3e0ebbf0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,6 +33,7 @@ script: if [[ "$TRAVIS_RUST_VERSION" == "1.20.0" ]]; then cd examples/diesel && cargo check && cd ../.. cd examples/multipart && cargo check && cd ../.. + cd examples/json && cargo check && cd ../.. fi - | if [[ "$TRAVIS_RUST_VERSION" == "beta" ]]; then From c36ad063323f198fcd2a2407c59bf3337cac90ee Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Dec 2017 15:26:28 -0800 Subject: [PATCH 155/279] more general Responder implementaiton for response future --- src/handler.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index cbca0aed6..186237b47 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -168,14 +168,26 @@ impl>, S: 'static> From> fo } } -impl Responder for Box> +impl Responder for Box> + where I: Responder + 'static, + E: Into + 'static { type Item = Reply; type Error = Error; #[inline] - fn respond_to(self, _: HttpRequest) -> Result { - Ok(Reply(ReplyItem::Future(self))) + fn respond_to(self, req: HttpRequest) -> Result { + let fut = self.map_err(|e| e.into()) + .then(move |r| { + match r.respond_to(req) { + Ok(reply) => match reply.into().0 { + ReplyItem::Message(resp) => ok(resp), + _ => panic!("Nested async replies are not supported"), + }, + Err(e) => err(e), + } + }); + Ok(Reply::async(fut)) } } From df2aa42dad4465afd5f3c0bc5ad31c8a3238fb0e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Dec 2017 15:45:26 -0800 Subject: [PATCH 156/279] cleanup example --- examples/json/src/main.rs | 14 ++++++-------- guide/src/qs_7.md | 11 +++++------ 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/examples/json/src/main.rs b/examples/json/src/main.rs index c271c12b0..239874525 100644 --- a/examples/json/src/main.rs +++ b/examples/json/src/main.rs @@ -9,7 +9,7 @@ extern crate serde_json; use actix_web::*; use bytes::BytesMut; use futures::Stream; -use futures::future::{Future, ok, err, result}; +use futures::future::{Future, ok, err}; #[derive(Debug, Deserialize)] struct MyObj { @@ -18,25 +18,23 @@ struct MyObj { } fn index(mut req: HttpRequest) -> Result>> { - // check content-type, early error exit + // check content-type if req.content_type() != "application/json" { return Err(error::ErrorBadRequest("wrong content-type").into()) } Ok(Box::new( - // load request body - req.payload_mut() + req.payload_mut() // <- load request body .readany() .fold(BytesMut::new(), |mut body, chunk| { body.extend(chunk); - result::<_, error::PayloadError>(Ok(body)) + ok::<_, error::PayloadError>(body) }) .map_err(|e| Error::from(e)) - .and_then(|body| { - // body is loaded, now we can deserialize json + .and_then(|body| { // <- body is loaded, now we can deserialize json match serde_json::from_slice::(&body) { Ok(obj) => { - println!("MODEL: {:?}", obj); // <- do something with payload + println!("model: {:?}", obj); // <- do something with payload ok(httpcodes::HTTPOk.response()) // <- send response }, Err(e) => { diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index bb1a14367..2cbc527b6 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -59,8 +59,8 @@ fn index(req: HttpRequest) -> HttpResponse { ## JSON Request -Unfortunately, because of async nature of actix web framework, deserializing json -requests is not very ergonomic process. First you need to load whole body into +Unfortunately, because of async nature of actix web framework, json requests deserialization +is not very ergonomic process. First you need to load whole body into a temporal storage and only then you can deserialize it. Here is simple example. We will deserialize *MyObj* struct. @@ -73,17 +73,16 @@ struct MyObj { } ``` -We need to load request body first. +We need to load request body first and then deserialize json into object. ```rust,ignore fn index(mut req: HttpRequest) -> Future { - req.payload_mut().readany() .fold(BytesMut::new(), |mut body, chunk| { // <- load request body body.extend(chunk); - result::<_, error::PayloadError>(Ok(body)) + ok(body) }) - .and_then(|body| { // <- body is loaded, now we can deserialize json + .and_then(|body| { // <- body is loaded, now we can deserialize json let obj = serde_json::from_slice::(&body).unwrap(); println!("MODEL: {:?}", obj); // <- do something with payload ok(httpcodes::HTTPOk.response()) // <- send response From 50891986bc60e02519f3229615954c80d7ed78ba Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Dec 2017 16:05:07 -0800 Subject: [PATCH 157/279] simplify json example --- examples/json/src/main.rs | 27 +++++++++++++-------------- guide/src/qs_7.md | 24 ++++++++++++++---------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/examples/json/src/main.rs b/examples/json/src/main.rs index 239874525..1695c4d7a 100644 --- a/examples/json/src/main.rs +++ b/examples/json/src/main.rs @@ -1,17 +1,15 @@ extern crate actix; extern crate actix_web; -extern crate bytes; extern crate futures; extern crate env_logger; extern crate serde_json; #[macro_use] extern crate serde_derive; use actix_web::*; -use bytes::BytesMut; use futures::Stream; use futures::future::{Future, ok, err}; -#[derive(Debug, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] struct MyObj { name: String, number: i32, @@ -24,22 +22,23 @@ fn index(mut req: HttpRequest) -> Result(body) - }) - .map_err(|e| Error::from(e)) + // `concat2` will asynchronously read each chunk of the request body and + // return a single, concatenated, chunk + req.payload_mut().readany().concat2() + // `Future::from_err` acts like `?` in that it coerces the error type from + // the future into the final error type + .from_err() + // `Future::and_then` can be used to merge an asynchronous workflow with a + // synchronous workflow .and_then(|body| { // <- body is loaded, now we can deserialize json match serde_json::from_slice::(&body) { Ok(obj) => { println!("model: {:?}", obj); // <- do something with payload - ok(httpcodes::HTTPOk.response()) // <- send response + ok(httpcodes::HTTPOk.build() // <- send response + .content_type("application/json") + .json(obj).unwrap()) }, - Err(e) => { - err(error::ErrorBadRequest(e).into()) - } + Err(e) => err(error::ErrorBadRequest(e).into()) } }))) } diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 2cbc527b6..f821e5e54 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -77,16 +77,20 @@ We need to load request body first and then deserialize json into object. ```rust,ignore fn index(mut req: HttpRequest) -> Future { - req.payload_mut().readany() - .fold(BytesMut::new(), |mut body, chunk| { // <- load request body - body.extend(chunk); - ok(body) - }) - .and_then(|body| { // <- body is loaded, now we can deserialize json - let obj = serde_json::from_slice::(&body).unwrap(); - println!("MODEL: {:?}", obj); // <- do something with payload - ok(httpcodes::HTTPOk.response()) // <- send response - }) + // `concat2` will asynchronously read each chunk of the request body and + // return a single, concatenated, chunk + req.payload_mut().readany().concat2() + // `Future::from_err` acts like `?` in that it coerces the error type from + // the future into the final error type + .from_err() + // `Future::and_then` can be used to merge an asynchronous workflow with a + // synchronous workflow + .and_then(|body| { // <- body is loaded, now we can deserialize json + let obj = serde_json::from_slice::(&body).unwrap(); + ok(httpcodes::HTTPOk.build() // <- send response + .content_type("application/json") + .json(obj).unwrap()) + }) } ``` From 4dd3382ac704251948920aab9f9b66ed6b815dc6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Dec 2017 16:13:21 -0800 Subject: [PATCH 158/279] update example --- examples/json/src/main.rs | 19 +++++++------------ guide/src/qs_7.md | 8 +++----- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/examples/json/src/main.rs b/examples/json/src/main.rs index 1695c4d7a..f47c195ca 100644 --- a/examples/json/src/main.rs +++ b/examples/json/src/main.rs @@ -6,8 +6,7 @@ extern crate serde_json; #[macro_use] extern crate serde_derive; use actix_web::*; -use futures::Stream; -use futures::future::{Future, ok, err}; +use futures::{Future, Stream}; #[derive(Debug, Serialize, Deserialize)] struct MyObj { @@ -31,16 +30,12 @@ fn index(mut req: HttpRequest) -> Result(&body) { - Ok(obj) => { - println!("model: {:?}", obj); // <- do something with payload - ok(httpcodes::HTTPOk.build() // <- send response - .content_type("application/json") - .json(obj).unwrap()) - }, - Err(e) => err(error::ErrorBadRequest(e).into()) - } - }))) + let obj = serde_json::from_slice::(&body).map_err(error::ErrorBadRequest)?; + + println!("model: {:?}", obj); + Ok(httpcodes::HTTPOk.build().json(obj)?) // <- send response + }) + )) } fn main() { diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index f821e5e54..2d4368c84 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -85,11 +85,9 @@ fn index(mut req: HttpRequest) -> Future { .from_err() // `Future::and_then` can be used to merge an asynchronous workflow with a // synchronous workflow - .and_then(|body| { // <- body is loaded, now we can deserialize json - let obj = serde_json::from_slice::(&body).unwrap(); - ok(httpcodes::HTTPOk.build() // <- send response - .content_type("application/json") - .json(obj).unwrap()) + .and_then(|body| { // <- body is loaded, now we can deserialize json + let obj = serde_json::from_slice::(&body)?; + Ok(httpcodes::HTTPOk.build().json(obj)?) // <- send response }) } ``` From 3c5fd18e0264718009d3a94f0bdf07908f0d6fcc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Dec 2017 16:32:31 -0800 Subject: [PATCH 159/279] cleanup examples --- examples/basic.rs | 2 +- examples/diesel/src/main.rs | 10 +++++----- examples/multipart/src/main.rs | 2 +- examples/template_tera/src/main.rs | 15 ++++++++------- examples/websocket-chat/src/main.rs | 6 +++--- 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index e52eac0dc..009a01a1a 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -90,7 +90,7 @@ fn main() { httpcodes::HTTPFound .build() .header("LOCATION", "/index.html") - .body(Body::Empty) + .finish() }))) .bind("127.0.0.1:8080").unwrap() .start(); diff --git a/examples/diesel/src/main.rs b/examples/diesel/src/main.rs index 80eb30d29..18d926343 100644 --- a/examples/diesel/src/main.rs +++ b/examples/diesel/src/main.rs @@ -19,7 +19,7 @@ extern crate env_logger; use actix_web::*; use actix::prelude::*; use diesel::prelude::*; -use futures::future::{Future, ok}; +use futures::future::Future; mod models; mod schema; @@ -35,13 +35,13 @@ fn index(req: HttpRequest) -> Box> Box::new( req.state().db.call_fut(CreateUser{name: name.to_owned()}) + .from_err() .and_then(|res| { match res { - Ok(user) => ok(httpcodes::HTTPOk.build().json(user).unwrap()), - Err(_) => ok(httpcodes::HTTPInternalServerError.response()) + Ok(user) => Ok(httpcodes::HTTPOk.build().json(user)?), + Err(_) => Ok(httpcodes::HTTPInternalServerError.response()) } - }) - .map_err(|e| error::ErrorInternalServerError(e).into())) + })) } /// This is db executor actor. We are going to run 3 of them in parallele. diff --git a/examples/multipart/src/main.rs b/examples/multipart/src/main.rs index afe3a3f8d..0778c051c 100644 --- a/examples/multipart/src/main.rs +++ b/examples/multipart/src/main.rs @@ -16,7 +16,7 @@ fn index(mut req: HttpRequest) -> Box> Box::new( req.multipart() // <- get multipart stream for current request - .map_err(Error::from) // <- convert multipart errors + .from_err() // <- convert multipart errors .and_then(|item| { // <- iterate over multipart items match item { // Handle multipart Field diff --git a/examples/template_tera/src/main.rs b/examples/template_tera/src/main.rs index 23d68cadf..86b37e0d6 100644 --- a/examples/template_tera/src/main.rs +++ b/examples/template_tera/src/main.rs @@ -9,19 +9,20 @@ struct State { template: tera::Tera, // <- store tera template in application state } -fn index(req: HttpRequest) -> HttpResponse { +fn index(req: HttpRequest) -> Result { let s = if let Some(name) = req.query().get("name") { // <- submitted form let mut ctx = tera::Context::new(); ctx.add("name", name); ctx.add("text", &"Welcome!".to_owned()); - req.state().template.render("user.html", &ctx).unwrap() + req.state().template.render("user.html", &ctx) + .map_err(|_| error::ErrorInternalServerError("Template error"))? } else { - req.state().template.render("index.html", &tera::Context::new()).unwrap() + req.state().template.render("index.html", &tera::Context::new()) + .map_err(|_| error::ErrorInternalServerError("Template error"))? }; - httpcodes::HTTPOk.build() - .content_type("text/html") - .body(s) - .unwrap() + Ok(httpcodes::HTTPOk.build() + .content_type("text/html") + .body(s)?) } fn main() { diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs index 8576503a4..8d0d55d98 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -200,12 +200,12 @@ fn main() { let state = WsChatSessionState { addr: server.clone() }; Application::with_state(state) - // redirect to websocket.html - .resource("/", |r| r.method(Method::GET).f(|req| { + // redirect to websocket.html + .resource("/", |r| r.method(Method::GET).f(|_| { httpcodes::HTTPFound .build() .header("LOCATION", "/static/websocket.html") - .body(Body::Empty) + .finish() })) // websocket .resource("/ws/", |r| r.route().f(chat_route)) From bf23aa5d4b2467a3e920d7e835abd4b7cc2664f6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Dec 2017 17:43:43 -0800 Subject: [PATCH 160/279] move db code to separate module --- .travis.yml | 5 +--- examples/diesel/src/db.rs | 53 +++++++++++++++++++++++++++++++++++++ examples/diesel/src/main.rs | 48 +++------------------------------ 3 files changed, 58 insertions(+), 48 deletions(-) create mode 100644 examples/diesel/src/db.rs diff --git a/.travis.yml b/.travis.yml index f3e0ebbf0..8cfdaefd6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,13 +30,10 @@ before_script: script: - USE_SKEPTIC=1 cargo test --features=alpn - | - if [[ "$TRAVIS_RUST_VERSION" == "1.20.0" ]]; then + if [[ "$TRAVIS_RUST_VERSION" == "beta" ]]; then cd examples/diesel && cargo check && cd ../.. cd examples/multipart && cargo check && cd ../.. cd examples/json && cargo check && cd ../.. - fi - - | - if [[ "$TRAVIS_RUST_VERSION" == "beta" ]]; then cd examples/template_tera && cargo check && cd ../.. cd examples/tls && cargo check && cd ../.. cd examples/websocket-chat && cargo check && cd ../.. diff --git a/examples/diesel/src/db.rs b/examples/diesel/src/db.rs new file mode 100644 index 000000000..4e7bced91 --- /dev/null +++ b/examples/diesel/src/db.rs @@ -0,0 +1,53 @@ +//! Db executor actor +use uuid; +use diesel; +use actix_web::*; +use actix::prelude::*; +use diesel::prelude::*; + +use models; +use schema; + +/// This is db executor actor. We are going to run 3 of them in parallele. +pub struct DbExecutor(pub SqliteConnection); + +/// This is only message that this actor can handle, but it is easy to extend number of +/// messages. +pub struct CreateUser { + pub name: String, +} + +impl ResponseType for CreateUser { + type Item = models::User; + type Error = Error; +} + +impl Actor for DbExecutor { + type Context = SyncContext; +} + +impl Handler for DbExecutor { + fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) + -> Response + { + use self::schema::users::dsl::*; + + let uuid = format!("{}", uuid::Uuid::new_v4()); + let new_user = models::NewUser { + id: &uuid, + name: &msg.name, + }; + + diesel::insert_into(users) + .values(&new_user) + .execute(&self.0) + .expect("Error inserting person"); + + let mut items = users + .filter(id.eq(&uuid)) + .load::(&self.0) + .expect("Error loading person"); + + Self::reply(items.pop().unwrap()) + } +} diff --git a/examples/diesel/src/main.rs b/examples/diesel/src/main.rs index 18d926343..e29f5dbc2 100644 --- a/examples/diesel/src/main.rs +++ b/examples/diesel/src/main.rs @@ -21,9 +21,13 @@ use actix::prelude::*; use diesel::prelude::*; use futures::future::Future; +mod db; mod models; mod schema; +use db::{CreateUser, DbExecutor}; + + /// State with DbExecutor address struct State { db: SyncAddress, @@ -44,50 +48,6 @@ fn index(req: HttpRequest) -> Box> })) } -/// This is db executor actor. We are going to run 3 of them in parallele. -struct DbExecutor(SqliteConnection); - -/// This is only message that this actor can handle, but it is easy to extend number of -/// messages. -struct CreateUser { - name: String, -} - -impl ResponseType for CreateUser { - type Item = models::User; - type Error = Error; -} - -impl Actor for DbExecutor { - type Context = SyncContext; -} - -impl Handler for DbExecutor { - fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) - -> Response - { - use self::schema::users::dsl::*; - - let uuid = format!("{}", uuid::Uuid::new_v4()); - let new_user = models::NewUser { - id: &uuid, - name: &msg.name, - }; - - diesel::insert_into(users) - .values(&new_user) - .execute(&self.0) - .expect("Error inserting person"); - - let mut items = users - .filter(id.eq(&uuid)) - .load::(&self.0) - .expect("Error loading person"); - - Self::reply(items.pop().unwrap()) - } -} - fn main() { ::std::env::set_var("RUST_LOG", "actix_web=info"); let _ = env_logger::init(); From 33b2be32813c913bd359f001211b87a8e5b38c7f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Dec 2017 17:51:28 -0800 Subject: [PATCH 161/279] move json responder to separate module --- src/handler.rs | 50 --------------------------------------- src/json.rs | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 4 +++- 3 files changed, 66 insertions(+), 51 deletions(-) create mode 100644 src/json.rs diff --git a/src/handler.rs b/src/handler.rs index 186237b47..934345da6 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -2,8 +2,6 @@ use std::marker::PhantomData; use actix::Actor; use futures::future::{Future, ok, err}; -use serde_json; -use serde::Serialize; use regex::Regex; use http::{header, StatusCode, Error as HttpError}; @@ -280,42 +278,6 @@ impl RouteHandler for AsyncHandler } } -/// Json response helper -/// -/// The `Json` type allows you to respond with well-formed JSON data: simply return a value of -/// type Json where T is the type of a structure to serialize into *JSON*. The -/// type `T` must implement the `Serialize` trait from *serde*. -/// -/// ```rust -/// # extern crate actix_web; -/// # #[macro_use] extern crate serde_derive; -/// # use actix_web::*; -/// # -/// #[derive(Serialize)] -/// struct MyObj { -/// name: String, -/// } -/// -/// fn index(req: HttpRequest) -> Result> { -/// Ok(Json(MyObj{name: req.match_info().query("name")?})) -/// } -/// # fn main() {} -/// ``` -pub struct Json (pub T); - -impl Responder for Json { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, _: HttpRequest) -> Result { - let body = serde_json::to_string(&self.0)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)?) - } -} - /// Path normalization helper /// /// By normalizing it means: @@ -436,18 +398,6 @@ mod tests { use http::{header, Method}; use application::Application; - #[derive(Serialize)] - struct MyObj { - name: &'static str, - } - - #[test] - fn test_json() { - let json = Json(MyObj{name: "test"}); - let resp = json.respond_to(HttpRequest::default()).unwrap(); - assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "application/json"); - } - fn index(_req: HttpRequest) -> HttpResponse { HttpResponse::new(StatusCode::OK, Body::Empty) } diff --git a/src/json.rs b/src/json.rs new file mode 100644 index 000000000..fd9d2d9fc --- /dev/null +++ b/src/json.rs @@ -0,0 +1,63 @@ +use serde_json; +use serde::Serialize; + +use error::Error; +use handler::Responder; +use httprequest::HttpRequest; +use httpresponse::HttpResponse; + +/// Json response helper +/// +/// The `Json` type allows you to respond with well-formed JSON data: simply return a value of +/// type Json where T is the type of a structure to serialize into *JSON*. The +/// type `T` must implement the `Serialize` trait from *serde*. +/// +/// ```rust +/// # extern crate actix_web; +/// # #[macro_use] extern crate serde_derive; +/// # use actix_web::*; +/// # +/// #[derive(Serialize)] +/// struct MyObj { +/// name: String, +/// } +/// +/// fn index(req: HttpRequest) -> Result> { +/// Ok(Json(MyObj{name: req.match_info().query("name")?})) +/// } +/// # fn main() {} +/// ``` +pub struct Json (pub T); + +impl Responder for Json { + type Item = HttpResponse; + type Error = Error; + + fn respond_to(self, _: HttpRequest) -> Result { + let body = serde_json::to_string(&self.0)?; + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(body)?) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use http::{header, Method}; + use application::Application; + + #[derive(Serialize)] + struct MyObj { + name: &'static str, + } + + #[test] + fn test_json() { + let json = Json(MyObj{name: "test"}); + let resp = json.respond_to(HttpRequest::default()).unwrap(); + assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "application/json"); + } + +} diff --git a/src/lib.rs b/src/lib.rs index 9a83907a6..81b9fc2f6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,6 +94,7 @@ mod encoding; mod httprequest; mod httpresponse; mod info; +mod json; mod route; mod router; mod param; @@ -119,10 +120,11 @@ pub mod pred; pub mod payload; pub use error::{Error, Result, ResponseError}; pub use body::{Body, Binary}; +pub use json::{Json}; pub use application::Application; pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; -pub use handler::{Reply, Responder, Json, NormalizePath}; +pub use handler::{Reply, Responder, NormalizePath}; pub use route::Route; pub use resource::Resource; pub use server::HttpServer; From 63ddc07ccb7bd495b769b567b8aa2ad7a8a544b0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Dec 2017 20:30:54 -0800 Subject: [PATCH 162/279] added JsonBody future --- examples/json/src/main.rs | 31 ++----- guide/src/qs_7.md | 36 +++++++-- src/error.rs | 41 ++++++++++ src/handler.rs | 15 ++++ src/httprequest.rs | 38 ++++++++- src/json.rs | 165 ++++++++++++++++++++++++++++++++++++-- src/lib.rs | 3 +- src/payload.rs | 2 +- 8 files changed, 290 insertions(+), 41 deletions(-) diff --git a/examples/json/src/main.rs b/examples/json/src/main.rs index f47c195ca..fae29053d 100644 --- a/examples/json/src/main.rs +++ b/examples/json/src/main.rs @@ -6,7 +6,7 @@ extern crate serde_json; #[macro_use] extern crate serde_derive; use actix_web::*; -use futures::{Future, Stream}; +use futures::Future; #[derive(Debug, Serialize, Deserialize)] struct MyObj { @@ -14,28 +14,13 @@ struct MyObj { number: i32, } -fn index(mut req: HttpRequest) -> Result>> { - // check content-type - if req.content_type() != "application/json" { - return Err(error::ErrorBadRequest("wrong content-type").into()) - } - - Ok(Box::new( - // `concat2` will asynchronously read each chunk of the request body and - // return a single, concatenated, chunk - req.payload_mut().readany().concat2() - // `Future::from_err` acts like `?` in that it coerces the error type from - // the future into the final error type - .from_err() - // `Future::and_then` can be used to merge an asynchronous workflow with a - // synchronous workflow - .and_then(|body| { // <- body is loaded, now we can deserialize json - let obj = serde_json::from_slice::(&body).map_err(error::ErrorBadRequest)?; - - println!("model: {:?}", obj); - Ok(httpcodes::HTTPOk.build().json(obj)?) // <- send response - }) - )) +fn index(mut req: HttpRequest) -> Box> { + req.json().from_err() + .and_then(|val: MyObj| { + println!("model: {:?}", val); + Ok(httpcodes::HTTPOk.build().json(val)?) // <- send response + }) + .responder() } fn main() { diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 2d4368c84..3e321ff4d 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -59,21 +59,41 @@ fn index(req: HttpRequest) -> HttpResponse { ## JSON Request -Unfortunately, because of async nature of actix web framework, json requests deserialization -is not very ergonomic process. First you need to load whole body into a -temporal storage and only then you can deserialize it. +There are two options of json body deserialization. -Here is simple example. We will deserialize *MyObj* struct. +First option is to use *HttpResponse::json()* method. This method returns +[*JsonBody*](../actix_web/dev/struct.JsonBody.html) object which resolves into +deserialized value. -```rust,ignore -#[derive(Debug, Deserialize)] +```rust +# extern crate actix; +# extern crate actix_web; +# extern crate futures; +# extern crate env_logger; +# extern crate serde_json; +# #[macro_use] extern crate serde_derive; +# use actix_web::*; +# use futures::Future; +#[derive(Debug, Serialize, Deserialize)] struct MyObj { name: String, number: i32, } + +fn index(mut req: HttpRequest) -> Box> { + req.json().from_err() + .and_then(|val: MyObj| { + println!("model: {:?}", val); + Ok(httpcodes::HTTPOk.build().json(val)?) // <- send response + }) + .responder() +} +# fn main() {} ``` -We need to load request body first and then deserialize json into object. +Or you can manually load payload into memory and ther deserialize it. +Here is simple example. We will deserialize *MyObj* struct. We need to load request +body first and then deserialize json into object. ```rust,ignore fn index(mut req: HttpRequest) -> Future { @@ -92,7 +112,7 @@ fn index(mut req: HttpRequest) -> Future { } ``` -Full example is available in +Example is available in [examples directory](https://github.com/actix/actix-web/tree/master/examples/json/). diff --git a/src/error.rs b/src/error.rs index 275485603..ea9f06526 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,6 +10,7 @@ use std::error::Error as StdError; use cookie; use httparse; use failure::Fail; +use futures::Canceled; use http2::Error as Http2Error; use http::{header, StatusCode, Error as HttpError}; use http::uri::InvalidUriBytes; @@ -110,6 +111,9 @@ impl ResponseError for io::Error { /// `InternalServerError` for `InvalidHeaderValue` impl ResponseError for header::InvalidHeaderValue {} +/// `InternalServerError` for `futures::Canceled` +impl ResponseError for Canceled {} + /// Internal error #[derive(Fail, Debug)] #[fail(display="Unexpected task frame")] @@ -393,6 +397,43 @@ impl From for UrlencodedError { } } +/// A set of errors that can occur during parsing json payloads +#[derive(Fail, Debug)] +pub enum JsonPayloadError { + /// Payload size is bigger than 256k + #[fail(display="Payload size is bigger than 256k")] + Overflow, + /// Content type error + #[fail(display="Content type error")] + ContentType, + /// Deserialize error + #[fail(display="Json deserialize error")] + Deserialize(JsonError), + /// Payload error + #[fail(display="Error that occur during reading payload")] + Payload(PayloadError), +} + +/// Return `BadRequest` for `UrlencodedError` +impl ResponseError for JsonPayloadError { + + fn error_response(&self) -> HttpResponse { + HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) + } +} + +impl From for JsonPayloadError { + fn from(err: PayloadError) -> JsonPayloadError { + JsonPayloadError::Payload(err) + } +} + +impl From for JsonPayloadError { + fn from(err: JsonError) -> JsonPayloadError { + JsonPayloadError::Deserialize(err) + } +} + /// Errors which can occur when attempting to interpret a segment string as a /// valid path segment. #[derive(Fail, Debug, PartialEq)] diff --git a/src/handler.rs b/src/handler.rs index 934345da6..6ad426d53 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -36,6 +36,21 @@ pub trait Responder { fn respond_to(self, req: HttpRequest) -> Result; } +/// Convinience trait that convert `Future` object into `Boxed` future +pub trait AsyncResponder: Sized { + fn responder(self) -> Box>; +} + +impl AsyncResponder for F + where F: Future + 'static, + I: Responder + 'static, + E: Into + 'static, +{ + fn responder(self) -> Box> { + Box::new(self) + } +} + /// Handler for Fn() impl Handler for F where F: Fn(HttpRequest) -> R + 'static, diff --git a/src/httprequest.rs b/src/httprequest.rs index e48cfcd85..3308d8513 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -5,9 +5,10 @@ use std::rc::Rc; use std::net::SocketAddr; use std::collections::HashMap; use bytes::BytesMut; -use futures::{Async, Future, Stream, Poll}; use cookie::Cookie; +use futures::{Async, Future, Stream, Poll}; use http_range::HttpRange; +use serde::de::DeserializeOwned; use url::{Url, form_urlencoded}; use http::{header, Uri, Method, Version, HeaderMap, Extensions}; @@ -15,6 +16,7 @@ use info::ConnectionInfo; use param::Params; use router::Router; use payload::Payload; +use json::JsonBody; use multipart::Multipart; use helpers::SharedHttpMessage; use error::{ParseError, UrlGenerationError, CookieParseError, HttpRangeError, UrlencodedError}; @@ -468,6 +470,40 @@ impl HttpRequest { pub fn urlencoded(&mut self) -> UrlEncoded { UrlEncoded::from_request(self) } + + /// Parse `application/json` encoded body. + /// Return `JsonBody` future. It resolves to a `T` value. + /// + /// Returns error: + /// + /// * content type is not `application/json` + /// * content length is greater than 256k + /// + /// ```rust + /// # extern crate actix_web; + /// # extern crate futures; + /// # #[macro_use] extern crate serde_derive; + /// use actix_web::*; + /// use futures::future::{Future, ok}; + /// + /// #[derive(Deserialize, Debug)] + /// struct MyObj { + /// name: String, + /// } + /// + /// fn index(mut req: HttpRequest) -> Box> { + /// req.json() // <- get JsonBody future + /// .from_err() + /// .and_then(|val: MyObj| { // <- deserialized value + /// println!("==== BODY ==== {:?}", val); + /// Ok(httpcodes::HTTPOk.response()) + /// }).responder() + /// } + /// # fn main() {} + /// ``` + pub fn json(&mut self) -> JsonBody { + JsonBody::from_request(self) + } } impl Default for HttpRequest<()> { diff --git a/src/json.rs b/src/json.rs index fd9d2d9fc..93cff4135 100644 --- a/src/json.rs +++ b/src/json.rs @@ -1,7 +1,12 @@ +use bytes::BytesMut; +use futures::{Poll, Future, Stream}; +use http::header::CONTENT_LENGTH; + use serde_json; use serde::Serialize; +use serde::de::DeserializeOwned; -use error::Error; +use error::{Error, JsonPayloadError}; use handler::Responder; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -42,22 +47,168 @@ impl Responder for Json { } } +/// Request payload json parser that resolves to a deserialized `T` value. +/// +/// Returns error: +/// +/// * content type is not `application/json` +/// * content length is greater than 256k +/// +/// ```rust +/// # extern crate actix_web; +/// # extern crate futures; +/// # #[macro_use] extern crate serde_derive; +/// use actix_web::*; +/// use futures::future::Future; +/// +/// #[derive(Deserialize, Debug)] +/// struct MyObj { +/// name: String, +/// } +/// +/// fn index(mut req: HttpRequest) -> Box> { +/// req.json() // <- get JsonBody future +/// .from_err() +/// .and_then(|val: MyObj| { // <- deserialized value +/// println!("==== BODY ==== {:?}", val); +/// Ok(httpcodes::HTTPOk.response()) +/// }).responder() +/// } +/// # fn main() {} +/// ``` +pub struct JsonBody{ + limit: usize, + ct: &'static str, + req: Option>, + fut: Option>>, +} + +impl JsonBody { + + pub fn from_request(req: &mut HttpRequest) -> Self { + JsonBody{ + limit: 262_144, + req: Some(req.clone()), + fut: None, + ct: "application/json", + } + } + + /// Change max size of payload. By default max size is 256Kb + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } + + /// Set allowed content type. + /// + /// By default *application/json* content type is used. Set content type + /// to empty string if you want to disable content type check. + pub fn content_type(mut self, ct: &'static str) -> Self { + self.ct = ct; + self + } +} + +impl Future for JsonBody { + type Item = T; + type Error = JsonPayloadError; + + fn poll(&mut self) -> Poll { + if let Some(mut req) = self.req.take() { + if let Some(len) = req.headers().get(CONTENT_LENGTH) { + if let Ok(s) = len.to_str() { + if let Ok(len) = s.parse::() { + if len > self.limit { + return Err(JsonPayloadError::Overflow); + } + } else { + return Err(JsonPayloadError::Overflow); + } + } + } + // check content-type + if !self.ct.is_empty() && req.content_type() != self.ct { + return Err(JsonPayloadError::ContentType) + } + + let limit = self.limit; + let fut = req.payload_mut().readany() + .from_err() + .fold(BytesMut::new(), move |mut body, chunk| { + if (body.len() + chunk.len()) > limit { + Err(JsonPayloadError::Overflow) + } else { + body.extend_from_slice(&chunk); + Ok(body) + } + }) + .and_then(|body| Ok(serde_json::from_slice::(&body)?)); + self.fut = Some(Box::new(fut)); + } + + self.fut.as_mut().expect("JsonBody could not be used second time").poll() + } +} + #[cfg(test)] mod tests { use super::*; - use http::{header, Method}; - use application::Application; + use bytes::Bytes; + use http::header; + use futures::Async; - #[derive(Serialize)] - struct MyObj { - name: &'static str, + impl PartialEq for JsonPayloadError { + fn eq(&self, other: &JsonPayloadError) -> bool { + match *self { + JsonPayloadError::Overflow => match *other { + JsonPayloadError::Overflow => true, + _ => false, + }, + JsonPayloadError::ContentType => match *other { + JsonPayloadError::ContentType => true, + _ => false, + }, + _ => false, + } + } + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct MyObject { + name: String, } #[test] fn test_json() { - let json = Json(MyObj{name: "test"}); + let json = Json(MyObject{name: "test".to_owned()}); let resp = json.respond_to(HttpRequest::default()).unwrap(); assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "application/json"); } + #[test] + fn test_json_body() { + let mut req = HttpRequest::default(); + let mut json = req.json::(); + assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); + + let mut json = req.json::().content_type("text/json"); + req.headers_mut().insert(header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json")); + assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); + + let mut json = req.json::().limit(100); + req.headers_mut().insert(header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json")); + req.headers_mut().insert(header::CONTENT_LENGTH, + header::HeaderValue::from_static("10000")); + assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow); + + req.headers_mut().insert(header::CONTENT_LENGTH, + header::HeaderValue::from_static("16")); + req.payload_mut().unread_data(Bytes::from_static(b"{\"name\": \"test\"}")); + let mut json = req.json::(); + assert_eq!(json.poll().ok().unwrap(), Async::Ready(MyObject{name: "test".to_owned()})); + } + } diff --git a/src/lib.rs b/src/lib.rs index 81b9fc2f6..56240a8cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -124,7 +124,7 @@ pub use json::{Json}; pub use application::Application; pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; -pub use handler::{Reply, Responder, NormalizePath}; +pub use handler::{Reply, Responder, NormalizePath, AsyncResponder}; pub use route::Route; pub use resource::Resource; pub use server::HttpServer; @@ -166,6 +166,7 @@ pub mod dev { pub use body::BodyStream; pub use info::ConnectionInfo; pub use handler::Handler; + pub use json::JsonBody; pub use router::{Router, Pattern}; pub use pipeline::Pipeline; pub use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; diff --git a/src/payload.rs b/src/payload.rs index eda81c755..7c921070c 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -433,7 +433,7 @@ impl Inner { fn unread_data(&mut self, data: Bytes) { self.len += data.len(); - self.items.push_front(data) + self.items.push_front(data); } fn capacity(&self) -> usize { From 406d2c41e95b5f6fc20129921f05ea293398b7c1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Dec 2017 20:56:17 -0800 Subject: [PATCH 163/279] add doc string --- src/json.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/json.rs b/src/json.rs index 93cff4135..a33fa46d6 100644 --- a/src/json.rs +++ b/src/json.rs @@ -85,6 +85,7 @@ pub struct JsonBody{ impl JsonBody { + /// Create `JsonBody` for request. pub fn from_request(req: &mut HttpRequest) -> Self { JsonBody{ limit: 262_144, From 0a68811dce28d6567827ca10876eb5acf88abe50 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Dec 2017 21:06:04 -0800 Subject: [PATCH 164/279] cleanup more examples --- examples/diesel/src/main.rs | 18 +++++++++--------- guide/src/qs_14.md | 20 ++++++++++---------- guide/src/qs_7.md | 33 ++++++++++++++++----------------- src/httprequest.rs | 15 +++++++-------- 4 files changed, 42 insertions(+), 44 deletions(-) diff --git a/examples/diesel/src/main.rs b/examples/diesel/src/main.rs index e29f5dbc2..c05857c1a 100644 --- a/examples/diesel/src/main.rs +++ b/examples/diesel/src/main.rs @@ -37,15 +37,15 @@ struct State { fn index(req: HttpRequest) -> Box> { let name = &req.match_info()["name"]; - Box::new( - req.state().db.call_fut(CreateUser{name: name.to_owned()}) - .from_err() - .and_then(|res| { - match res { - Ok(user) => Ok(httpcodes::HTTPOk.build().json(user)?), - Err(_) => Ok(httpcodes::HTTPInternalServerError.response()) - } - })) + req.state().db.call_fut(CreateUser{name: name.to_owned()}) + .from_err() + .and_then(|res| { + match res { + Ok(user) => Ok(httpcodes::HTTPOk.build().json(user)?), + Err(_) => Ok(httpcodes::HTTPInternalServerError.response()) + } + }) + .responder() } fn main() { diff --git a/guide/src/qs_14.md b/guide/src/qs_14.md index 133d6d10a..0378c6b42 100644 --- a/guide/src/qs_14.md +++ b/guide/src/qs_14.md @@ -107,16 +107,16 @@ used for async handler registration. fn index(req: HttpRequest) -> Box> { let name = &req.match_info()["name"]; - Box::new( - // Send message to `DbExecutor` actor - req.state().db.call_fut(CreateUser{name: name.to_owned()}) - .and_then(|res| { - match res { - Ok(user) => ok(HTTPOk.build().json(user)), - Err(_) => ok(HTTPInternalServerError.response()) - } - }) - .map_err(|e| error::ErrorInternalServerError(e).into())) + // Send message to `DbExecutor` actor + req.state().db.call_fut(CreateUser{name: name.to_owned()}) + .from_err() + .and_then(|res| { + match res { + Ok(user) => Ok(httpcodes::HTTPOk.build().json(user)?), + Err(_) => Ok(httpcodes::HTTPInternalServerError.response()) + } + }) + .responder() } ``` diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 3e321ff4d..7e341c889 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -234,14 +234,13 @@ use actix_web::*; use futures::future::{Future, ok}; fn index(mut req: HttpRequest) -> Box> { - Box::new( - req.urlencoded() // <- get UrlEncoded future - .and_then(|params| { // <- url encoded parameters - println!("==== BODY ==== {:?}", params); - ok(httpcodes::HTTPOk.response()) - }) - .map_err(Error::from) - ) + req.urlencoded() // <- get UrlEncoded future + .from_err() + .and_then(|params| { // <- url encoded parameters + println!("==== BODY ==== {:?}", params); + ok(httpcodes::HTTPOk.response()) + }) + .responder() } # fn main() {} ``` @@ -276,15 +275,15 @@ use futures::{Future, Stream}; fn index(mut req: HttpRequest) -> Box> { - Box::new( - req.payload_mut() - .readany() - .fold((), |_, chunk| { - println!("Chunk: {:?}", chunk); - result::<_, error::PayloadError>(Ok(())) - }) - .map_err(|e| Error::from(e)) - .map(|_| HttpResponse::Ok().finish().unwrap())) + req.payload_mut() + .readany() + .from_err() + .fold((), |_, chunk| { + println!("Chunk: {:?}", chunk); + result::<_, error::PayloadError>(Ok(())) + }) + .map(|_| HttpResponse::Ok().finish().unwrap()) + .responder() } # fn main() {} ``` diff --git a/src/httprequest.rs b/src/httprequest.rs index 3308d8513..dd28282be 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -456,14 +456,13 @@ impl HttpRequest { /// use futures::future::{Future, ok}; /// /// fn index(mut req: HttpRequest) -> Box> { - /// Box::new( - /// req.urlencoded() // <- get UrlEncoded future - /// .and_then(|params| { // <- url encoded parameters - /// println!("==== BODY ==== {:?}", params); - /// ok(httpcodes::HTTPOk.response()) - /// }) - /// .map_err(Error::from) - /// ) + /// req.urlencoded() // <- get UrlEncoded future + /// .from_err() + /// .and_then(|params| { // <- url encoded parameters + /// println!("==== BODY ==== {:?}", params); + /// ok(httpcodes::HTTPOk.response()) + /// }) + /// .responder() /// } /// # fn main() {} /// ``` From bca1dd4f9e65b3ea14a2fdf3d3cc0021c17124a3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Dec 2017 23:19:21 -0800 Subject: [PATCH 165/279] update doc strings --- examples/multipart/src/main.rs | 47 +++++++++++++------------- guide/src/qs_7.md | 4 --- src/error.rs | 1 + src/handler.rs | 1 + src/httprequest.rs | 33 +++++++++++++++++- src/httpresponse.rs | 62 +++++++++++++++++++++++++++------- 6 files changed, 107 insertions(+), 41 deletions(-) diff --git a/examples/multipart/src/main.rs b/examples/multipart/src/main.rs index 0778c051c..41204bef0 100644 --- a/examples/multipart/src/main.rs +++ b/examples/multipart/src/main.rs @@ -14,32 +14,31 @@ fn index(mut req: HttpRequest) -> Box> { println!("{:?}", req); - Box::new( - req.multipart() // <- get multipart stream for current request - .from_err() // <- convert multipart errors - .and_then(|item| { // <- iterate over multipart items - match item { - // Handle multipart Field - multipart::MultipartItem::Field(field) => { - println!("==== FIELD ==== {:?}", field); + req.multipart() // <- get multipart stream for current request + .from_err() // <- convert multipart errors + .and_then(|item| { // <- iterate over multipart items + match item { + // Handle multipart Field + multipart::MultipartItem::Field(field) => { + println!("==== FIELD ==== {:?}", field); - // Field in turn is stream of *Bytes* object - Either::A( - field.map_err(Error::from) - .map(|chunk| { - println!("-- CHUNK: \n{}", - std::str::from_utf8(&chunk).unwrap());}) - .finish()) - }, - multipart::MultipartItem::Nested(mp) => { - // Or item could be nested Multipart stream - Either::B(result(Ok(()))) - } + // Field in turn is stream of *Bytes* object + Either::A( + field.map_err(Error::from) + .map(|chunk| { + println!("-- CHUNK: \n{}", + std::str::from_utf8(&chunk).unwrap());}) + .finish()) + }, + multipart::MultipartItem::Nested(mp) => { + // Or item could be nested Multipart stream + Either::B(result(Ok(()))) } - }) - .finish() // <- Stream::finish() combinator from actix - .map(|_| httpcodes::HTTPOk.response()) - ) + } + }) + .finish() // <- Stream::finish() combinator from actix + .map(|_| httpcodes::HTTPOk.response()) + .responder() } fn main() { diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 7e341c889..8385bdbd4 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -168,10 +168,6 @@ fn index(req: HttpRequest) -> HttpResponse { # fn main() {} ``` -## Cookies - -[WIP] - ## Multipart body Actix provides multipart stream support. diff --git a/src/error.rs b/src/error.rs index ea9f06526..ef4b65c56 100644 --- a/src/error.rs +++ b/src/error.rs @@ -115,6 +115,7 @@ impl ResponseError for header::InvalidHeaderValue {} impl ResponseError for Canceled {} /// Internal error +#[doc(hidden)] #[derive(Fail, Debug)] #[fail(display="Unexpected task frame")] pub struct UnexpectedTaskFrame; diff --git a/src/handler.rs b/src/handler.rs index 6ad426d53..f58513a95 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -36,6 +36,7 @@ pub trait Responder { fn respond_to(self, req: HttpRequest) -> Result; } +#[doc(hidden)] /// Convinience trait that convert `Future` object into `Boxed` future pub trait AsyncResponder: Sized { fn responder(self) -> Box>; diff --git a/src/httprequest.rs b/src/httprequest.rs index dd28282be..1f2e9eb05 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -432,9 +432,40 @@ impl HttpRequest { msg.payload.as_mut().unwrap() } - /// Return stream to process BODY as multipart. + /// Return stream to http payload processes as multipart. /// /// Content-type: multipart/form-data; + /// + /// ```rust + /// # extern crate actix; + /// # extern crate actix_web; + /// # extern crate env_logger; + /// # extern crate futures; + /// # use std::str; + /// # use actix::*; + /// # use actix_web::*; + /// # use futures::{Future, Stream}; + /// # use futures::future::{ok, result, Either}; + /// fn index(mut req: HttpRequest) -> Box> { + /// req.multipart().from_err() // <- get multipart stream for current request + /// .and_then(|item| match item { // <- iterate over multipart items + /// multipart::MultipartItem::Field(field) => { + /// // Field in turn is stream of *Bytes* object + /// Either::A(field.from_err() + /// .map(|c| println!("-- CHUNK: \n{:?}", str::from_utf8(&c))) + /// .finish()) + /// }, + /// multipart::MultipartItem::Nested(mp) => { + /// // Or item could be nested Multipart stream + /// Either::B(ok(())) + /// } + /// }) + /// .finish() // <- Stream::finish() combinator from actix + /// .map(|_| httpcodes::HTTPOk.response()) + /// .responder() + /// } + /// # fn main() {} + /// ``` pub fn multipart(&mut self) -> Multipart { Multipart::from_request(self) } diff --git a/src/httpresponse.rs b/src/httpresponse.rs index f8b410877..4b7b25a29 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -227,7 +227,9 @@ pub struct HttpResponseBuilder { } impl HttpResponseBuilder { - /// Set the HTTP version of this response. + /// Set HTTP version of this response. + /// + /// By default response's http version depends on request's version. #[inline] pub fn version(&mut self, version: Version) -> &mut Self { if let Some(parts) = parts(&mut self.response, &self.err) { @@ -236,16 +238,24 @@ impl HttpResponseBuilder { self } - /// Set the `StatusCode` for this response. - #[inline] - pub fn status(&mut self, status: StatusCode) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.status = status; - } - self - } - /// Set a header. + /// + /// ```rust + /// # extern crate http; + /// # extern crate actix_web; + /// # use actix_web::*; + /// # use actix_web::httpcodes::*; + /// # + /// use http::header; + /// + /// fn index(req: HttpRequest) -> Result { + /// Ok(HTTPOk.build() + /// .header("X-TEST", "value") + /// .header(header::CONTENT_TYPE, "application/json") + /// .finish()?) + /// } + /// fn main() {} + /// ``` #[inline] pub fn header(&mut self, key: K, value: V) -> &mut Self where HeaderName: HttpTryFrom, @@ -289,6 +299,7 @@ impl HttpResponseBuilder { /// Set connection type #[inline] + #[doc(hidden)] pub fn connection_type(&mut self, conn: ConnectionType) -> &mut Self { if let Some(parts) = parts(&mut self.response, &self.err) { parts.connection_type = Some(conn); @@ -298,6 +309,7 @@ impl HttpResponseBuilder { /// Set connection type to Upgrade #[inline] + #[doc(hidden)] pub fn upgrade(&mut self) -> &mut Self { self.connection_type(ConnectionType::Upgrade) } @@ -332,6 +344,27 @@ impl HttpResponseBuilder { } /// Set a cookie + /// + /// ```rust + /// # extern crate actix_web; + /// # use actix_web::*; + /// # use actix_web::httpcodes::*; + /// # + /// use actix_web::headers::Cookie; + /// + /// fn index(req: HttpRequest) -> Result { + /// Ok(HTTPOk.build() + /// .cookie( + /// Cookie::build("name", "value") + /// .domain("www.rust-lang.org") + /// .path("/") + /// .secure(true) + /// .http_only(true) + /// .finish()) + /// .finish()?) + /// } + /// fn main() {} + /// ``` pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { if self.cookies.is_none() { let mut jar = CookieJar::new(); @@ -343,7 +376,7 @@ impl HttpResponseBuilder { self } - /// Remote cookie, cookie has to be cookie from `HttpRequest::cookies()` method. + /// Remove cookie, cookie has to be cookie from `HttpRequest::cookies()` method. pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self { { if self.cookies.is_none() { @@ -357,7 +390,7 @@ impl HttpResponseBuilder { self } - /// Calls provided closure with builder reference if value is true. + /// This method calls provided closure with builder reference if value is true. pub fn if_true(&mut self, value: bool, f: F) -> &mut Self where F: Fn(&mut HttpResponseBuilder) + 'static { @@ -368,6 +401,7 @@ impl HttpResponseBuilder { } /// Set a body and generate `HttpResponse`. + /// /// `HttpResponseBuilder` can not be used after this call. pub fn body>(&mut self, body: B) -> Result { if let Some(e) = self.err.take() { @@ -386,6 +420,8 @@ impl HttpResponseBuilder { } /// Set a json body and generate `HttpResponse` + /// + /// `HttpResponseBuilder` can not be used after this call. pub fn json(&mut self, value: T) -> Result { let body = serde_json::to_string(&value)?; @@ -402,6 +438,8 @@ impl HttpResponseBuilder { } /// Set an empty body and generate `HttpResponse` + /// + /// `HttpResponseBuilder` can not be used after this call. pub fn finish(&mut self) -> Result { self.body(Body::Empty) } From 55534bff8c4591f40e11ccecbafa430d4fd1cc6d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Dec 2017 23:21:26 -0800 Subject: [PATCH 166/279] simplify guide examples --- guide/src/qs_3_5.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/guide/src/qs_3_5.md b/guide/src/qs_3_5.md index dfde017dc..647aa9654 100644 --- a/guide/src/qs_3_5.md +++ b/guide/src/qs_3_5.md @@ -20,7 +20,7 @@ fn main() { HttpServer::new( || Application::new() - .resource("/", |r| r.f(|_| httpcodes::HTTPOk))) + .resource("/", |r| r.h(httpcodes::HTTPOk))) .bind("127.0.0.1:59080").unwrap() .start(); @@ -46,7 +46,7 @@ use actix_web::*; fn main() { HttpServer::::new( || Application::new() - .resource("/", |r| r.f(|_| httpcodes::HTTPOk))) + .resource("/", |r| r.h(httpcodes::HTTPOk))) .threads(4); // <- Start 4 workers } ``` @@ -109,7 +109,7 @@ use actix_web::*; fn main() { HttpServer::::new(|| Application::new() - .resource("/", |r| r.f(|r| httpcodes::HTTPOk))) + .resource("/", |r| r.h(httpcodes::HTTPOk))) .keep_alive(None); // <- Use `SO_KEEPALIVE` socket option. } ``` From 0567e6fb0a2239d839eaff1c2bcad7a62c374ea7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Dec 2017 23:27:30 -0800 Subject: [PATCH 167/279] fix typos in guide --- guide/src/qs_4.md | 3 ++- guide/src/qs_4_5.md | 11 ++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index dda86e278..910e83f88 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -57,7 +57,7 @@ struct MyObj { name: &'static str, } -/// we have to convert Error into HttpResponse as well +/// Responder impl Responder for MyObj { type Item = HttpResponse; type Error = Error; @@ -72,6 +72,7 @@ impl Responder for MyObj { } } +/// Because `MyObj` implements `Responder`, it is possible to return it directly fn index(req: HttpRequest) -> MyObj { MyObj{name: "user"} } diff --git a/guide/src/qs_4_5.md b/guide/src/qs_4_5.md index cbc69654c..ef3a982ae 100644 --- a/guide/src/qs_4_5.md +++ b/guide/src/qs_4_5.md @@ -35,9 +35,9 @@ fn index(req: HttpRequest) -> io::Result { ## Custom error response -To add support for custom errors all we need to do just implement `ResponseError` trait. -`ResponseError` trait has default implementation for `error_response()` method, it -generates *500* response. +To add support for custom errors, all we need to do is just implement `ResponseError` trait +for custom error. `ResponseError` trait has default implementation +for `error_response()` method, it generates *500* response. ```rust # extern crate actix_web; @@ -50,6 +50,7 @@ struct MyError { name: &'static str } +/// Use default implementation for `error_response()` method impl error::ResponseError for MyError {} fn index(req: HttpRequest) -> Result<&'static str, MyError> { @@ -64,7 +65,7 @@ fn index(req: HttpRequest) -> Result<&'static str, MyError> { ``` In this example *index* handler will always return *500* response. But it is easy -to return different responses. +to return different responses for different type of errors. ```rust # extern crate actix_web; @@ -132,4 +133,4 @@ fn index(req: HttpRequest) -> Result<&'static str> { # } ``` -In this example *BAD REQUEST* response get generated for `MYError` error. +In this example *BAD REQUEST* response get generated for `MyError` error. From 18f384178305908134d0a32aa72c6d6cc5851ec9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Dec 2017 23:36:52 -0800 Subject: [PATCH 168/279] update test --- src/httpresponse.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 4b7b25a29..69bb71a72 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -740,12 +740,11 @@ mod tests { #[test] fn test_basic_builder() { let resp = HttpResponse::Ok() - .status(StatusCode::NO_CONTENT) .header("X-TEST", "value") .version(Version::HTTP_10) .finish().unwrap(); assert_eq!(resp.version(), Some(Version::HTTP_10)); - assert_eq!(resp.status(), StatusCode::NO_CONTENT); + assert_eq!(resp.status(), StatusCode::OK); } #[test] From c35d2946119448ff1222e9963b4257db1038d1ed Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Dec 2017 12:54:18 -0800 Subject: [PATCH 169/279] fix compression --- src/encoding.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/encoding.rs b/src/encoding.rs index 85cd7fd6a..4e9427999 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -394,7 +394,8 @@ impl PayloadEncoder { }, Body::Binary(ref mut bytes) => { if compression { - let transfer = TransferEncoding::eof(SharedBytes::default()); + let mut buf = SharedBytes::default(); + let transfer = TransferEncoding::eof(buf.clone()); let mut enc = match encoding { ContentEncoding::Deflate => ContentEncoder::Deflate( DeflateEncoder::new(transfer, Compression::Default)), From eaab28cd3b30c49bf1daa071d4faee09bd0474e1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Dec 2017 12:57:59 -0800 Subject: [PATCH 170/279] proper fix for compression --- src/encoding.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/encoding.rs b/src/encoding.rs index 4e9427999..291cb8e6e 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -410,7 +410,7 @@ impl PayloadEncoder { let _ = enc.write(bytes.as_ref()); let _ = enc.write_eof(); - *bytes = Binary::from(enc.get_mut().take()); + *bytes = Binary::from(buf.get_mut().take()); encoding = ContentEncoding::Identity; } resp.headers_mut().remove(CONTENT_LENGTH); From 9f9c75d832c3f6c0da9ea8197758eabdb61deb97 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 24 Dec 2017 11:58:09 -0800 Subject: [PATCH 171/279] simplify drain feature --- examples/basic.rs | 2 +- src/context.rs | 36 ++++++----------------- src/encoding.rs | 2 +- src/pipeline.rs | 73 ++++++----------------------------------------- 4 files changed, 18 insertions(+), 95 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index 009a01a1a..42d29e8ad 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -92,7 +92,7 @@ fn main() { .header("LOCATION", "/index.html") .finish() }))) - .bind("127.0.0.1:8080").unwrap() + .bind("0.0.0.0:8080").unwrap() .start(); println!("Starting http server: 127.0.0.1:8080"); diff --git a/src/context.rs b/src/context.rs index c9f770147..f0e80de1a 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,10 +1,8 @@ use std; -use std::rc::Rc; -use std::cell::RefCell; use std::collections::VecDeque; -use std::marker::PhantomData; -use futures::{Async, Future, Poll}; +use futures::{Async, Poll}; use futures::sync::oneshot::Sender; +use futures::unsync::oneshot; use actix::{Actor, ActorState, ActorContext, AsyncContext, Handler, Subscriber, ResponseType}; @@ -16,7 +14,7 @@ use body::{Body, Binary}; use error::Error; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use pipeline::DrainFut; + pub(crate) trait IoContext: 'static { fn disconnected(&mut self); @@ -27,7 +25,7 @@ pub(crate) trait IoContext: 'static { pub(crate) enum Frame { Message(HttpResponse), Payload(Option), - Drain(Rc>), + Drain(oneshot::Sender<()>), } /// Http actor execution context @@ -161,11 +159,11 @@ impl HttpContext where A: Actor { } /// Returns drain future - pub fn drain(&mut self) -> Drain
    { - let fut = Rc::new(RefCell::new(DrainFut::default())); - self.stream.push_back(Frame::Drain(Rc::clone(&fut))); + pub fn drain(&mut self) -> oneshot::Receiver<()> { + let (tx, rx) = oneshot::channel(); self.modified = true; - Drain{ a: PhantomData, inner: fut } + self.stream.push_back(Frame::Drain(tx)); + rx } /// Check if connection still open @@ -305,21 +303,3 @@ impl ToEnvelope for HttpContext RemoteEnvelope::new(msg, tx).into() } } - - -pub struct Drain { - a: PhantomData, - inner: Rc> -} - -impl ActorFuture for Drain - where A: Actor -{ - type Item = (); - type Error = (); - type Actor = A; - - fn poll(&mut self, _: &mut A, _: &mut ::Context) -> Poll<(), ()> { - self.inner.borrow_mut().poll() - } -} diff --git a/src/encoding.rs b/src/encoding.rs index 291cb8e6e..3254f6404 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -394,7 +394,7 @@ impl PayloadEncoder { }, Body::Binary(ref mut bytes) => { if compression { - let mut buf = SharedBytes::default(); + let buf = SharedBytes::default(); let transfer = TransferEncoding::eof(buf.clone()); let mut enc = match encoding { ContentEncoding::Deflate => ContentEncoder::Deflate( diff --git a/src/pipeline.rs b/src/pipeline.rs index c04968dc1..71b3e7aff 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -1,10 +1,9 @@ use std::{io, mem}; use std::rc::Rc; -use std::cell::RefCell; use std::marker::PhantomData; use futures::{Async, Poll, Future, Stream}; -use futures::task::{Task as FutureTask, current as current_task}; +use futures::unsync::oneshot; use channel::HttpHandlerTask; use body::{Body, BodyStream}; @@ -76,49 +75,6 @@ enum PipelineResponse { Response(Box>), } -/// Future that resolves when all buffered data get sent -#[doc(hidden)] -#[derive(Debug)] -pub struct DrainFut { - drained: bool, - task: Option, -} - -impl Default for DrainFut { - - fn default() -> DrainFut { - DrainFut { - drained: false, - task: None, - } - } -} - -impl DrainFut { - - fn set(&mut self) { - self.drained = true; - if let Some(task) = self.task.take() { - task.notify() - } - } -} - -impl Future for DrainFut { - type Item = (); - type Error = (); - - fn poll(&mut self) -> Poll<(), ()> { - if self.drained { - Ok(Async::Ready(())) - } else { - self.task = Some(current_task()); - Ok(Async::NotReady) - } - } -} - - impl Pipeline { pub fn new(req: HttpRequest, @@ -554,7 +510,7 @@ struct ProcessResponse { resp: HttpResponse, iostate: IOState, running: RunningState, - drain: DrainVec, + drain: Option>, _s: PhantomData, } @@ -587,16 +543,6 @@ enum IOState { Done, } -struct DrainVec(Vec>>); - -impl Drop for DrainVec { - fn drop(&mut self) { - for drain in &mut self.0 { - drain.borrow_mut().set() - } - } -} - impl ProcessResponse { #[inline] @@ -606,14 +552,14 @@ impl ProcessResponse { ProcessResponse{ resp: resp, iostate: IOState::Response, running: RunningState::Running, - drain: DrainVec(Vec::new()), + drain: None, _s: PhantomData}) } fn poll_io(mut self, io: &mut Writer, info: &mut PipelineInfo) -> Result, PipelineState> { - if self.drain.0.is_empty() && self.running != RunningState::Paused { + if self.drain.is_none() && self.running != RunningState::Paused { // if task is paused, write buffer is probably full loop { @@ -712,7 +658,7 @@ impl ProcessResponse { } }, Frame::Drain(fut) => { - self.drain.0.push(fut); + self.drain = Some(fut); break } } @@ -748,7 +694,7 @@ impl ProcessResponse { } // flush io but only if we need to - if self.running == RunningState::Paused || !self.drain.0.is_empty() { + if self.running == RunningState::Paused || self.drain.is_some() { match io.poll_completed() { Ok(Async::Ready(_)) => self.running.resume(), @@ -763,11 +709,8 @@ impl ProcessResponse { } // drain futures - if !self.drain.0.is_empty() { - for fut in &mut self.drain.0 { - fut.borrow_mut().set() - } - self.drain.0.clear(); + if let Some(tx) = self.drain.take() { + let _ = tx.send(()); } // response is completed From ddd9c24bb23738c602be262aecb78b8e41f57fcf Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 24 Dec 2017 14:29:19 -0800 Subject: [PATCH 172/279] optimize payload type detection --- src/h1.rs | 75 +++++++++++++++++++++---------------------------------- 1 file changed, 28 insertions(+), 47 deletions(-) diff --git a/src/h1.rs b/src/h1.rs index 9af1d8cdd..861ad9919 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -20,7 +20,6 @@ use h1writer::{Writer, H1Writer}; use server::WorkerSettings; use httpcodes::HTTPNotFound; use httprequest::HttpRequest; -use helpers::SharedHttpMessage; use error::{ParseError, PayloadError, ResponseError}; use payload::{Payload, PayloadWriter, DEFAULT_BUFFER_SIZE}; @@ -47,7 +46,6 @@ bitflags! { } } - pub(crate) enum Http1Result { Done, Switch, @@ -583,58 +581,41 @@ impl Reader { msg }; - let decoder = if upgrade(&msg) { - Decoder::eof() - } else { - let has_len = msg.get_mut().headers.contains_key(header::CONTENT_LENGTH); - - // Chunked encoding - if chunked(&msg.get_mut().headers)? { - if has_len { - return Err(ParseError::Header) - } - Decoder::chunked() - } else { - if !has_len { - return Ok(Message::Http1(HttpRequest::from_message(msg), None)) - } - - // Content-Length - let len = msg.get_mut().headers.get(header::CONTENT_LENGTH).unwrap(); - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - Decoder::length(len) - } else { - debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header) - } + let decoder = if let Some(len) = msg.get_ref().headers.get(header::CONTENT_LENGTH) { + // Content-Length + if let Ok(s) = len.to_str() { + if let Ok(len) = s.parse::() { + Some(Decoder::length(len)) } else { debug!("illegal Content-Length: {:?}", len); return Err(ParseError::Header) } + } else { + debug!("illegal Content-Length: {:?}", len); + return Err(ParseError::Header) } - }; - - let (psender, payload) = Payload::new(false); - let info = PayloadInfo { - tx: PayloadType::new(&msg.get_mut().headers, psender), - decoder: decoder, - }; - msg.get_mut().payload = Some(payload); - Ok(Message::Http1(HttpRequest::from_message(msg), Some(info))) - } -} - -/// Check if request is UPGRADE -fn upgrade(msg: &SharedHttpMessage) -> bool { - if let Some(conn) = msg.get_ref().headers.get(header::CONNECTION) { - if let Ok(s) = conn.to_str() { - s.to_lowercase().contains("upgrade") - } else { + } else if chunked(&msg.get_mut().headers)? { + // Chunked encoding + Some(Decoder::chunked()) + } else if msg.get_ref().headers.contains_key(header::UPGRADE) || msg.get_ref().method == Method::CONNECT + { + Some(Decoder::eof()) + } else { + None + }; + + if let Some(decoder) = decoder { + let (psender, payload) = Payload::new(false); + let info = PayloadInfo { + tx: PayloadType::new(&msg.get_mut().headers, psender), + decoder: decoder, + }; + msg.get_mut().payload = Some(payload); + Ok(Message::Http1(HttpRequest::from_message(msg), Some(info))) + } else { + Ok(Message::Http1(HttpRequest::from_message(msg), None)) } - } else { - msg.get_ref().method == Method::CONNECT } } From f1e82ebc1ee12a7bcff1f8bd90cf325433c85551 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 24 Dec 2017 16:15:40 -0800 Subject: [PATCH 173/279] better connect handling --- src/h1.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/h1.rs b/src/h1.rs index 861ad9919..4d96c7033 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -581,7 +581,9 @@ impl Reader { msg }; - let decoder = if let Some(len) = msg.get_ref().headers.get(header::CONTENT_LENGTH) { + let decoder = if msg.get_ref().method == Method::CONNECT { + Some(Decoder::eof()) + } else if let Some(len) = msg.get_ref().headers.get(header::CONTENT_LENGTH) { // Content-Length if let Ok(s) = len.to_str() { if let Ok(len) = s.parse::() { @@ -597,9 +599,7 @@ impl Reader { } else if chunked(&msg.get_mut().headers)? { // Chunked encoding Some(Decoder::chunked()) - } else if msg.get_ref().headers.contains_key(header::UPGRADE) || - msg.get_ref().method == Method::CONNECT - { + } else if msg.get_ref().headers.contains_key(header::UPGRADE) { Some(Decoder::eof()) } else { None @@ -1215,6 +1215,7 @@ mod tests { fn test_conn_upgrade() { let mut buf = Buffer::new( "GET /test HTTP/1.1\r\n\ + upgrade: websockets\r\n\ connection: upgrade\r\n\r\n"); let req = parse_ready!(&mut buf); @@ -1226,6 +1227,7 @@ mod tests { fn test_conn_upgrade_connect_method() { let mut buf = Buffer::new( "CONNECT /test HTTP/1.1\r\n\ + upgrade: websockets\r\n\ content-length: 0\r\n\r\n"); let req = parse_ready!(&mut buf); From 012d55e424999d6fffac845ba836bb6b7a3dff6a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 25 Dec 2017 05:49:56 -0800 Subject: [PATCH 174/279] Set nightly version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8cfdaefd6..61b45a396 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ rust: - 1.20.0 - stable - beta - - nightly + - nightly-2017-12-21 sudo: required dist: trusty From 98b0e023f3f9b824268fd8983f3642ab075b3b2d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 25 Dec 2017 07:31:12 -0800 Subject: [PATCH 175/279] optimize payload detection --- src/h1.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/h1.rs b/src/h1.rs index 4d96c7033..95fbb8c40 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -581,9 +581,7 @@ impl Reader { msg }; - let decoder = if msg.get_ref().method == Method::CONNECT { - Some(Decoder::eof()) - } else if let Some(len) = msg.get_ref().headers.get(header::CONTENT_LENGTH) { + let decoder = if let Some(len) = msg.get_ref().headers.get(header::CONTENT_LENGTH) { // Content-Length if let Ok(s) = len.to_str() { if let Ok(len) = s.parse::() { @@ -599,7 +597,9 @@ impl Reader { } else if chunked(&msg.get_mut().headers)? { // Chunked encoding Some(Decoder::chunked()) - } else if msg.get_ref().headers.contains_key(header::UPGRADE) { + } else if msg.get_ref().headers.contains_key(header::UPGRADE) || + msg.get_ref().method == Method::CONNECT + { Some(Decoder::eof()) } else { None @@ -1227,8 +1227,7 @@ mod tests { fn test_conn_upgrade_connect_method() { let mut buf = Buffer::new( "CONNECT /test HTTP/1.1\r\n\ - upgrade: websockets\r\n\ - content-length: 0\r\n\r\n"); + content-type: text/plain\r\n\r\n"); let req = parse_ready!(&mut buf); assert!(req.upgrade()); From b0c8fa03f030d442bfdf8f674ae58d020a007384 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 25 Dec 2017 07:33:05 -0800 Subject: [PATCH 176/279] use specific nightly version for appveyor --- .appveyor.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 69165cf5c..e1fa120d4 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -3,6 +3,15 @@ environment: PROJECT_NAME: actix matrix: # Stable channel + - TARGET: i686-pc-windows-gnu + CHANNEL: 1.20.0 + - TARGET: i686-pc-windows-msvc + CHANNEL: 1.20.0 + - TARGET: x86_64-pc-windows-gnu + CHANNEL: 1.20.0 + - TARGET: x86_64-pc-windows-msvc + CHANNEL: 1.20.0 + # Stable channel - TARGET: i686-pc-windows-gnu CHANNEL: stable - TARGET: i686-pc-windows-msvc @@ -22,13 +31,13 @@ environment: CHANNEL: beta # Nightly channel - TARGET: i686-pc-windows-gnu - CHANNEL: nightly + CHANNEL: nightly-2017-12-21 - TARGET: i686-pc-windows-msvc - CHANNEL: nightly + CHANNEL: nightly-2017-12-21 - TARGET: x86_64-pc-windows-gnu - CHANNEL: nightly + CHANNEL: nightly-2017-12-21 - TARGET: x86_64-pc-windows-msvc - CHANNEL: nightly + CHANNEL: nightly-2017-12-21 # Install Rust and Cargo # (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml) From a578262f735a470c85f27f49c604a4f03a94ed9e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 25 Dec 2017 08:12:13 -0800 Subject: [PATCH 177/279] update json example and guide info --- examples/json/src/main.rs | 41 +++++++++++++++++++++++++++++++++++++-- guide/src/qs_5.md | 6 +++--- guide/src/qs_7.md | 19 ++++++++++++++---- 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/examples/json/src/main.rs b/examples/json/src/main.rs index fae29053d..9bc8bebcd 100644 --- a/examples/json/src/main.rs +++ b/examples/json/src/main.rs @@ -1,12 +1,14 @@ extern crate actix; extern crate actix_web; +extern crate bytes; extern crate futures; extern crate env_logger; extern crate serde_json; #[macro_use] extern crate serde_derive; use actix_web::*; -use futures::Future; +use bytes::BytesMut; +use futures::{Future, Stream}; #[derive(Debug, Serialize, Deserialize)] struct MyObj { @@ -14,8 +16,10 @@ struct MyObj { number: i32, } +/// This handler uses `HttpRequest::json()` for loading json object. fn index(mut req: HttpRequest) -> Box> { - req.json().from_err() + req.json() + .from_err() // convert all errors into `Error` .and_then(|val: MyObj| { println!("model: {:?}", val); Ok(httpcodes::HTTPOk.build().json(val)?) // <- send response @@ -23,6 +27,38 @@ fn index(mut req: HttpRequest) -> Box> { .responder() } + +const MAX_SIZE: usize = 262_144; // max payload size is 256k + +/// This handler manually load request payload and parse json +fn index_manual(mut req: HttpRequest) -> Box> { + // readany() returns asynchronous stream of Bytes objects + req.payload_mut().readany() + // `Future::from_err` acts like `?` in that it coerces the error type from + // the future into the final error type + .from_err() + + // `fold` will asynchronously read each chunk of the request body and + // call supplied closure, then it resolves to result of closure + .fold(BytesMut::new(), move |mut body, chunk| { + // limit max size of in-memory payload + if (body.len() + chunk.len()) > MAX_SIZE { + Err(error::ErrorBadRequest("overflow")) + } else { + body.extend_from_slice(&chunk); + Ok(body) + } + }) + // `Future::and_then` can be used to merge an asynchronous workflow with a + // synchronous workflow + .and_then(|body| { + // body is loaded, now we can deserialize json + let obj = serde_json::from_slice::(&body)?; + Ok(httpcodes::HTTPOk.build().json(obj)?) // <- send response + }) + .responder() +} + fn main() { ::std::env::set_var("RUST_LOG", "actix_web=info"); let _ = env_logger::init(); @@ -32,6 +68,7 @@ fn main() { Application::new() // enable logger .middleware(middlewares::Logger::default()) + .resource("/manual", |r| r.method(Method::POST).f(index_manual)) .resource("/", |r| r.method(Method::POST).f(index))}) .bind("127.0.0.1:8080").unwrap() .start(); diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index 65ee24c3f..5f8042179 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -13,9 +13,9 @@ a response object. More informatin is available in [handler section](../qs_4.htm Resource configuraiton is the act of adding a new resource to an application. A resource has a name, which acts as an identifier to be used for URL generation. The name also allows developers to add routes to existing resources. -A resource also has a pattern, meant to match against the *PATH* portion of a *URL* -(the portion following the scheme and port, e.g., */foo/bar* in the -*URL* *http://localhost:8080/foo/bar*). +A resource also has a pattern, meant to match against the *PATH* portion of a *URL*, +it does not match against *QUERY* portion (the portion following the scheme and +port, e.g., */foo/bar* in the *URL* *http://localhost:8080/foo/bar?q=value*). The [Application::resource](../actix_web/struct.Application.html#method.resource) methods add a single resource to application routing table. This method accepts *path pattern* diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 8385bdbd4..a51c5c8a6 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -69,7 +69,6 @@ deserialized value. # extern crate actix; # extern crate actix_web; # extern crate futures; -# extern crate env_logger; # extern crate serde_json; # #[macro_use] extern crate serde_derive; # use actix_web::*; @@ -95,8 +94,18 @@ Or you can manually load payload into memory and ther deserialize it. Here is simple example. We will deserialize *MyObj* struct. We need to load request body first and then deserialize json into object. -```rust,ignore -fn index(mut req: HttpRequest) -> Future { +```rust +# extern crate actix_web; +# extern crate futures; +# use actix_web::*; +# #[macro_use] extern crate serde_derive; +extern crate serde_json; +use futures::{Future, Stream}; + +#[derive(Serialize, Deserialize)] +struct MyObj {name: String, number: i32} + +fn index(mut req: HttpRequest) -> Box> { // `concat2` will asynchronously read each chunk of the request body and // return a single, concatenated, chunk req.payload_mut().readany().concat2() @@ -109,10 +118,12 @@ fn index(mut req: HttpRequest) -> Future { let obj = serde_json::from_slice::(&body)?; Ok(httpcodes::HTTPOk.build().json(obj)?) // <- send response }) + .responder() } +# fn main() {} ``` -Example is available in +Complete example for both options is available in [examples directory](https://github.com/actix/actix-web/tree/master/examples/json/). From 89c9dfb5bc0f06107f6f94a70c85b63fef4bd810 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 25 Dec 2017 08:19:33 -0800 Subject: [PATCH 178/279] update getting started guide section --- guide/src/qs_2.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md index a3b9e8155..c42eaac1c 100644 --- a/guide/src/qs_2.md +++ b/guide/src/qs_2.md @@ -48,9 +48,8 @@ request handler with the application's `resource` on a particular *HTTP method* # "Hello world!" # } # fn main() { - let app = Application::new() - .resource("/", |r| r.f(index)) - .finish(); + Application::new() + .resource("/", |r| r.f(index)); # } ``` @@ -58,7 +57,9 @@ After that, application instance can be used with `HttpServer` to listen for inc connections. Server accepts function that should return `HttpHandler` instance: ```rust,ignore - HttpServer::new(|| app) + HttpServer::new( + || Application::new() + .resource("/", |r| r.f(index))) .bind("127.0.0.1:8088")? .start(); ``` @@ -83,7 +84,7 @@ fn main() { HttpServer::new( || Application::new() .resource("/", |r| r.f(index))) - .bind("127.0.0.1:8088").unwrap() + .bind("127.0.0.1:8088").expect("Can not bind to 127.0.0.1:8088") .start(); println!("Started http server: 127.0.0.1:8088"); From 5b65987f6aff806fa0abbb272b1c0c937f64f975 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 25 Dec 2017 13:40:06 -0800 Subject: [PATCH 179/279] write response optimizations --- src/h1writer.rs | 55 +++++++++++++++--------------------- src/helpers.rs | 74 ++++++++++++++++++++++++++----------------------- 2 files changed, 62 insertions(+), 67 deletions(-) diff --git a/src/h1writer.rs b/src/h1writer.rs index 3b2415fda..e33020fba 100644 --- a/src/h1writer.rs +++ b/src/h1writer.rs @@ -1,8 +1,9 @@ use std::io; +use bytes::BufMut; use futures::{Async, Poll}; use tokio_io::AsyncWrite; use http::Version; -use http::header::{HeaderValue, CONNECTION, DATE, CONTENT_LENGTH}; +use http::header::{HeaderValue, CONNECTION, DATE}; use helpers; use body::Body; @@ -124,8 +125,6 @@ impl Writer for H1Writer { fn start(&mut self, req: &mut HttpMessage, msg: &mut HttpResponse) -> Result { - //trace!("Prepare response with status: {:?}", msg.status()); - // prepare task self.flags.insert(Flags::STARTED); self.encoder = PayloadEncoder::new(self.buffer.clone(), req, msg); @@ -157,53 +156,42 @@ impl Writer for H1Writer { buffer.reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE); } - match version { - Version::HTTP_11 => buffer.extend_from_slice(b"HTTP/1.1 "), - Version::HTTP_2 => buffer.extend_from_slice(b"HTTP/2.0 "), - Version::HTTP_10 => buffer.extend_from_slice(b"HTTP/1.0 "), - Version::HTTP_09 => buffer.extend_from_slice(b"HTTP/0.9 "), - } - helpers::convert_u16(msg.status().as_u16(), &mut buffer); - buffer.extend_from_slice(b" "); + // status line + helpers::write_status_line(version, msg.status().as_u16(), &mut buffer); buffer.extend_from_slice(msg.reason().as_bytes()); - buffer.extend_from_slice(b"\r\n"); - - for (key, value) in msg.headers() { - buffer.extend_from_slice(key.as_str().as_bytes()); - buffer.extend_from_slice(b": "); - buffer.extend_from_slice(value.as_ref()); - buffer.extend_from_slice(b"\r\n"); - } match body { Body::Empty => { - buffer.extend_from_slice(CONTENT_LENGTH.as_str().as_bytes()); - buffer.extend_from_slice(b": 0\r\n"); + buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n"); } Body::Binary(ref bytes) => { - buffer.extend_from_slice(CONTENT_LENGTH.as_str().as_bytes()); - buffer.extend_from_slice(b": "); + buffer.extend_from_slice(b"\r\ncontent-length: "); helpers::convert_usize(bytes.len(), &mut buffer); - buffer.extend_from_slice(b"\r\n"); } - _ => () + _ => buffer.extend_from_slice(b"\r\n"), } - + + // write headers + for (key, value) in msg.headers() { + let v = value.as_ref(); + let k = key.as_str().as_bytes(); + buffer.reserve(k.len() + v.len() + 4); + buffer.put_slice(k); + buffer.put_slice(b": "); + buffer.put_slice(v); + buffer.put_slice(b"\r\n"); + } + // using helpers::date is quite a lot faster if !msg.headers().contains_key(DATE) { - buffer.reserve(helpers::DATE_VALUE_LENGTH + 8); - buffer.extend_from_slice(b"Date: "); helpers::date(&mut buffer); + } else { + // msg eof buffer.extend_from_slice(b"\r\n"); } - - // msg eof - buffer.extend_from_slice(b"\r\n"); self.headers_size = buffer.len() as u32; } - // trace!("Response: {:?}", msg); - if let Body::Binary(bytes) = body { self.encoder.write(bytes.as_ref())?; return Ok(WriterState::Done) @@ -218,6 +206,7 @@ impl Writer for H1Writer { if self.flags.contains(Flags::STARTED) { // TODO: add warning, write after EOF self.encoder.write(payload)?; + return Ok(WriterState::Done) } else { // might be response to EXCEPT self.encoder.get_mut().extend_from_slice(payload) diff --git a/src/helpers.rs b/src/helpers.rs index 30b41c8dd..147ab457b 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -6,6 +6,7 @@ use std::ops::{Deref, DerefMut}; use std::collections::VecDeque; use time; use bytes::BytesMut; +use http::Version; use httprequest::HttpMessage; @@ -14,7 +15,11 @@ pub const DATE_VALUE_LENGTH: usize = 29; pub fn date(dst: &mut BytesMut) { CACHED.with(|cache| { - dst.extend_from_slice(cache.borrow().buffer()); + let mut buf: [u8; 39] = unsafe { mem::uninitialized() }; + buf[..6].copy_from_slice(b"date: "); + buf[6..35].copy_from_slice(cache.borrow().buffer()); + buf[35..].copy_from_slice(b"\r\n\r\n"); + dst.extend_from_slice(&buf); }) } @@ -234,26 +239,31 @@ const DEC_DIGITS_LUT: &[u8] = 6061626364656667686970717273747576777879\ 8081828384858687888990919293949596979899"; -pub(crate) fn convert_u16(mut n: u16, bytes: &mut BytesMut) { - let mut buf: [u8; 39] = unsafe { mem::uninitialized() }; - let mut curr: isize = 39; +pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) { + let mut buf: [u8; 14] = [b'H', b'T', b'T', b'P', b'/', b'1', b'.', b'1', + b' ', b' ', b' ', b' ', b' ', b' ']; + match version { + Version::HTTP_2 => buf[5] = b'2', + Version::HTTP_10 => buf[7] = b'0', + Version::HTTP_09 => {buf[5] = b'0'; buf[7] = b'9';}, + _ => (), + } + + let mut curr: isize = 13; let buf_ptr = buf.as_mut_ptr(); let lut_ptr = DEC_DIGITS_LUT.as_ptr(); unsafe { - // need at least 16 bits for the 4-characters-at-a-time to work. - if mem::size_of::() >= 2 { - // eagerly decode 4 characters at a time - while n >= 10_000 { - let rem = (n % 10_000) as isize; - n /= 10_000; + // eagerly decode 4 characters at a time + while n >= 10_000 { + let rem = (n % 10_000) as isize; + n /= 10_000; - let d1 = (rem / 100) << 1; - let d2 = (rem % 100) << 1; - curr -= 4; - ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); - ptr::copy_nonoverlapping(lut_ptr.offset(d2), buf_ptr.offset(curr + 2), 2); - } + let d1 = (rem / 100) << 1; + let d2 = (rem % 100) << 1; + curr -= 4; + ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); + ptr::copy_nonoverlapping(lut_ptr.offset(d2), buf_ptr.offset(curr + 2), 2); } // if we reach here numbers are <= 9999, so at most 4 chars long @@ -278,32 +288,28 @@ pub(crate) fn convert_u16(mut n: u16, bytes: &mut BytesMut) { } } - unsafe { - bytes.extend_from_slice( - slice::from_raw_parts(buf_ptr.offset(curr), buf.len() - curr as usize)); - } + bytes.extend_from_slice(&buf); } pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) { let mut curr: isize = 39; - let mut buf: [u8; 39] = unsafe { mem::uninitialized() }; + let mut buf: [u8; 41] = unsafe { mem::uninitialized() }; + buf[39] = b'\r'; + buf[40] = b'\n'; let buf_ptr = buf.as_mut_ptr(); let lut_ptr = DEC_DIGITS_LUT.as_ptr(); unsafe { - // need at least 16 bits for the 4-characters-at-a-time to work. - if mem::size_of::() >= 2 { - // eagerly decode 4 characters at a time - while n >= 10_000 { - let rem = (n % 10_000) as isize; - n /= 10_000; + // eagerly decode 4 characters at a time + while n >= 10_000 { + let rem = (n % 10_000) as isize; + n /= 10_000; - let d1 = (rem / 100) << 1; - let d2 = (rem % 100) << 1; - curr -= 4; - ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); - ptr::copy_nonoverlapping(lut_ptr.offset(d2), buf_ptr.offset(curr + 2), 2); - } + let d1 = (rem / 100) << 1; + let d2 = (rem % 100) << 1; + curr -= 4; + ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); + ptr::copy_nonoverlapping(lut_ptr.offset(d2), buf_ptr.offset(curr + 2), 2); } // if we reach here numbers are <= 9999, so at most 4 chars long @@ -330,7 +336,7 @@ pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) { unsafe { bytes.extend_from_slice( - slice::from_raw_parts(buf_ptr.offset(curr), buf.len() - curr as usize)); + slice::from_raw_parts(buf_ptr.offset(curr), 41 - curr as usize)); } } From 465a87a7cf99d196eef804d580f3086fa4a8ecfb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 25 Dec 2017 13:44:50 -0800 Subject: [PATCH 180/279] right version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 61b45a396..67d27b917 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,7 +46,7 @@ script: # Upload docs after_success: - | - if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; then + if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "nightly-2017-12-21" ]]; then cargo doc --features alpn --no-deps && echo "" > target/doc/index.html && cargo install mdbook && From ffb5742b7138dd81ebec2eadd468b4c8baace7cd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 25 Dec 2017 19:42:55 -0800 Subject: [PATCH 181/279] fix tests --- src/h1writer.rs | 14 +++--- src/helpers.rs | 110 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 79 insertions(+), 45 deletions(-) diff --git a/src/h1writer.rs b/src/h1writer.rs index e33020fba..2deca9d14 100644 --- a/src/h1writer.rs +++ b/src/h1writer.rs @@ -161,14 +161,12 @@ impl Writer for H1Writer { buffer.extend_from_slice(msg.reason().as_bytes()); match body { - Body::Empty => { - buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n"); - } - Body::Binary(ref bytes) => { - buffer.extend_from_slice(b"\r\ncontent-length: "); - helpers::convert_usize(bytes.len(), &mut buffer); - } - _ => buffer.extend_from_slice(b"\r\n"), + Body::Empty => + buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n"), + Body::Binary(ref bytes) => + helpers::write_content_length(bytes.len(), &mut buffer), + _ => + buffer.extend_from_slice(b"\r\n"), } // write headers diff --git a/src/helpers.rs b/src/helpers.rs index 147ab457b..8211b03d1 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -5,7 +5,7 @@ use std::rc::Rc; use std::ops::{Deref, DerefMut}; use std::collections::VecDeque; use time; -use bytes::BytesMut; +use bytes::{BufMut, BytesMut}; use http::Version; use httprequest::HttpMessage; @@ -240,8 +240,8 @@ const DEC_DIGITS_LUT: &[u8] = 8081828384858687888990919293949596979899"; pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) { - let mut buf: [u8; 14] = [b'H', b'T', b'T', b'P', b'/', b'1', b'.', b'1', - b' ', b' ', b' ', b' ', b' ', b' ']; + let mut buf: [u8; 13] = [b'H', b'T', b'T', b'P', b'/', b'1', b'.', b'1', + b' ', b' ', b' ', b' ', b' ']; match version { Version::HTTP_2 => buf[5] = b'2', Version::HTTP_10 => buf[7] = b'0', @@ -249,33 +249,17 @@ pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesM _ => (), } - let mut curr: isize = 13; + let mut curr: isize = 12; let buf_ptr = buf.as_mut_ptr(); let lut_ptr = DEC_DIGITS_LUT.as_ptr(); + let four = n > 999; unsafe { - // eagerly decode 4 characters at a time - while n >= 10_000 { - let rem = (n % 10_000) as isize; - n /= 10_000; - - let d1 = (rem / 100) << 1; - let d2 = (rem % 100) << 1; - curr -= 4; - ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); - ptr::copy_nonoverlapping(lut_ptr.offset(d2), buf_ptr.offset(curr + 2), 2); - } - - // if we reach here numbers are <= 9999, so at most 4 chars long - let mut n = n as isize; // possibly reduce 64bit math - // decode 2 more chars, if > 2 chars - if n >= 100 { - let d1 = (n % 100) << 1; - n /= 100; - curr -= 2; - ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); - } + let d1 = (n % 100) << 1; + n /= 100; + curr -= 2; + ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2); // decode last 1 or 2 chars if n < 10 { @@ -284,11 +268,57 @@ pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesM } else { let d1 = n << 1; curr -= 2; - ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); + ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2); } } bytes.extend_from_slice(&buf); + if four { + bytes.put(b' '); + } +} + +pub(crate) fn write_content_length(mut n: usize, bytes: &mut BytesMut) { + if n < 10 { + let mut buf: [u8; 21] = [b'\r',b'\n',b'c',b'o',b'n',b't',b'e', + b'n',b't',b'-',b'l',b'e',b'n',b'g', + b't',b'h',b':',b' ',b'0',b'\r',b'\n']; + buf[18] = (n as u8) + b'0'; + bytes.extend_from_slice(&buf); + } else if n < 100 { + let mut buf: [u8; 22] = [b'\r',b'\n',b'c',b'o',b'n',b't',b'e', + b'n',b't',b'-',b'l',b'e',b'n',b'g', + b't',b'h',b':',b' ',b'0',b'0',b'\r',b'\n']; + let d1 = n << 1; + unsafe { + ptr::copy_nonoverlapping( + DEC_DIGITS_LUT.as_ptr().offset(d1 as isize), buf.as_mut_ptr().offset(18), 2); + } + bytes.extend_from_slice(&buf); + } else if n < 1000 { + let mut buf: [u8; 23] = [b'\r',b'\n',b'c',b'o',b'n',b't',b'e', + b'n',b't',b'-',b'l',b'e',b'n',b'g', + b't',b'h',b':',b' ',b'0',b'0',b'0',b'\r',b'\n']; + // decode 2 more chars, if > 2 chars + let d1 = (n % 100) << 1; + n /= 100; + unsafe {ptr::copy_nonoverlapping( + DEC_DIGITS_LUT.as_ptr().offset(d1 as isize), buf.as_mut_ptr().offset(18), 2)}; + + // decode last 1 or 2 chars + if n < 10 { + buf[20] = (n as u8) + b'0'; + } else { + let d1 = n << 1; + unsafe {ptr::copy_nonoverlapping( + DEC_DIGITS_LUT.as_ptr().offset(d1 as isize), buf.as_mut_ptr().offset(17), 2)}; + } + + bytes.extend_from_slice(&buf); + } else { + bytes.extend_from_slice(b"\r\ncontent-length: "); + convert_usize(n, bytes); + } } pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) { @@ -340,16 +370,22 @@ pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) { } } -#[test] -fn test_date_len() { - assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len()); -} -#[test] -fn test_date() { - let mut buf1 = BytesMut::new(); - date(&mut buf1); - let mut buf2 = BytesMut::new(); - date(&mut buf2); - assert_eq!(buf1, buf2); +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_date_len() { + assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len()); + } + + #[test] + fn test_date() { + let mut buf1 = BytesMut::new(); + date(&mut buf1); + let mut buf2 = BytesMut::new(); + date(&mut buf2); + assert_eq!(buf1, buf2); + } } From b61a07a320f230f6a7e3cb148d40831aebae142f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Dec 2017 07:58:21 -0800 Subject: [PATCH 182/279] more info for middleware guide --- guide/src/qs_10.md | 62 +++++++++++++++++++++++++++++++++++++++++++++- guide/src/qs_14.md | 15 +++++------ 2 files changed, 67 insertions(+), 10 deletions(-) diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index f7c499458..435e2966e 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -1,8 +1,68 @@ # Middlewares +Actix middlewares system allows to add additional behaviour to request/response processing. +Middleware can hook into incomnig request process and modify request or halt request +processing and return response early. Also it can hook into response processing. + +Typically middlewares involves in following actions: + +* Pre-process the Request +* Post-process a Response +* Modify application state +* Access external services (redis, logging, sessions) + +Middlewares are registered for each application and get executed in same order as +registraton order. In general, *middleware* is a type that implements +[*Middleware trait*](../actix_web/middlewares/trait.Middleware.html). Each method +in this trait has default implementation. Each method can return result immidietly +or *future* object. + +Here is example of simple middleware that adds request and response headers: + +```rust +# extern crate http; +# extern crate actix_web; +use http::{header, HttpTryFrom}; +use actix_web::*; +use actix_web::middlewares::{Middleware, Started, Response}; + +struct Headers; // <- Our middleware + +/// Middleware implementation, middlewares are generic over application state, +/// so you can access state with `HttpRequest::state()` method. +impl Middleware for Headers { + + /// Method is called when request is ready. It may return + /// future, which should resolve before next middleware get called. + fn start(&self, req: &mut HttpRequest) -> Started { + req.headers_mut().insert( + header::CONTENT_TYPE, header::HeaderValue::from_static("text/plain")); + Started::Done + } + + /// Method is called when handler returns response, + /// but before sending http message to peer. + fn response(&self, req: &mut HttpRequest, mut resp: HttpResponse) -> Response { + resp.headers_mut().insert( + header::HeaderName::try_from("X-VERSION").unwrap(), + header::HeaderValue::from_static("0.2")); + Response::Done(resp) + } +} + +fn main() { + Application::new() + .middleware(Headers) // <- Register middleware, this method could be called multiple times + .resource("/", |r| r.h(httpcodes::HTTPOk)); +} +``` + +Active provides several useful middlewares, like *logging*, *user sessions*, etc. + + ## Logging -Logging is implemented as middleware. Middlewares get executed in same order as registraton order. +Logging is implemented as middleware. It is common to register logging middleware as first middleware for application. Logging middleware has to be registered for each application. diff --git a/guide/src/qs_14.md b/guide/src/qs_14.md index 0378c6b42..6c6fd1aaa 100644 --- a/guide/src/qs_14.md +++ b/guide/src/qs_14.md @@ -8,7 +8,7 @@ Technically sync actors are worker style actors, multiple of them can be run in parallel and process messages from same queue (sync actors work in mpmc mode). Let's create simple db api that can insert new user row into sqlite table. -We need to define sync actor and connection that this actor will use. Same approach +We have to define sync actor and connection that this actor will use. Same approach could used for other databases. ```rust,ignore @@ -24,14 +24,11 @@ impl Actor for DbExecutor { This is definition of our actor. Now we need to define *create user* message and response. ```rust,ignore +#[derive(Message)] +#[rtype(User, Error)] struct CreateUser { name: String, } - -impl ResponseType for CreateUser { - type Item = models::User; - type Error = Error; -} ``` We can send `CreateUser` message to `DbExecutor` actor, and as result we can get @@ -68,11 +65,11 @@ impl Handler for DbExecutor { ``` That is it. Now we can use *DbExecutor* actor from any http handler or middleware. -All we need is to start *DbExecutor* actors and store address in state where http handler +All we need is to start *DbExecutor* actors and store address in a state where http handler can access it. ```rust,ignore -/// This is state where we sill store *DbExecutor* address. +/// This is state where we store *DbExecutor* address. struct State { db: SyncAddress, } @@ -97,7 +94,7 @@ fn main() { } ``` -And finally we can use this state in handler function. We get message response +And finally we can use this state in a requst handler. We get message response asynchronously, so handler needs to return future object, also `Route::a()` needs to be used for async handler registration. From cf8c2ca95ec04121f04f928d0cea151f240d4c3a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Dec 2017 09:00:45 -0800 Subject: [PATCH 183/279] refactor Handler trait, use mut self --- guide/src/qs_4.md | 75 ++++++++++++++++++++++++++++++++++++++++++++++ src/application.rs | 26 +++++++++------- src/channel.rs | 2 +- src/fs.rs | 2 +- src/h1.rs | 2 +- src/h2.rs | 2 +- src/handler.rs | 20 ++++++------- src/httpcodes.rs | 4 +-- src/httprequest.rs | 16 +++++----- src/pipeline.rs | 8 ++--- src/resource.rs | 4 +-- src/route.rs | 2 +- src/router.rs | 25 ++++++++-------- src/server.rs | 9 +++--- 14 files changed, 138 insertions(+), 59 deletions(-) diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index 910e83f88..9ef785023 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -39,6 +39,81 @@ fn index(req: HttpRequest) -> Box> { } ``` +Some notes on shared application state and handler state. If you noticed +*Handler* trait is generic over *S*, which defines application state type. So +application state is accessible from handler with `HttpRequest::state()` method. +But state is accessible as a read-only reference, if you need mutable access to state +you have to implement it yourself. On other hand handler can mutable access it's own state +as `handle` method takes mutable reference to *self*. Beware, actix creates multiple copies +of application state and handlers, unique for each thread, so if you run your +application in several threads actix will create same amount as number of threads +of application state objects and handler objects. + +Here is example of handler that stores number of processed requests: + +```rust +# extern crate actix; +# extern crate actix_web; +use actix_web::*; +use actix_web::dev::Handler; + +struct MyHandler(usize); + +impl Handler for MyHandler { + type Result = HttpResponse; + + /// Handle request + fn handle(&mut self, req: HttpRequest) -> Self::Result { + self.0 += 1; + httpcodes::HTTPOk.response() + } +} +# fn main() {} +``` + +This handler will work, but `self.0` value will be different depends on number of threads and +number of requests processed per thread. Proper implementation would use `Arc` and `AtomicUsize` + +```rust +# extern crate actix; +# extern crate actix_web; +use actix_web::*; +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; + +struct MyHandler(Arc); + +impl Handler for MyHandler { + type Result = HttpResponse; + + /// Handle request + fn handle(&mut self, req: HttpRequest) -> Self::Result { + let num = self.0.load(Ordering::Relaxed) + 1; + self.0.store(num, Ordering::Relaxed); + httpcodes::HTTPOk.response() + } +} + +fn main() { + let sys = actix::System::new("example"); + + let inc = Arc::new(AtomicUsize::new(0)); + + HttpServer::new( + move || { + let cloned = inc.clone(); + Application::new() + .resource("/", move |r| r.h(MyHandler(cloned))) + }) + .bind("127.0.0.1:8088").unwrap() + .start(); + + println!("Started http server: 127.0.0.1:8088"); +# actix::Arbiter::system().send(actix::msgs::SystemExit(0)); + let _ = sys.run(); +} +``` + ## Response with custom type To return custom type directly from handler function type needs to implement `Responder` trait. diff --git a/src/application.rs b/src/application.rs index 381ecac9e..80b8173ce 100644 --- a/src/application.rs +++ b/src/application.rs @@ -15,7 +15,8 @@ pub struct HttpApplication { state: Rc, prefix: String, default: Resource, - router: Router, + router: Router, + resources: Vec>, middlewares: Rc>>>, } @@ -25,9 +26,9 @@ impl HttpApplication { req.with_state(Rc::clone(&self.state), self.router.clone()) } - pub(crate) fn run(&self, mut req: HttpRequest) -> Reply { - if let Some(h) = self.router.recognize(&mut req) { - h.handle(req.clone(), Some(&self.default)) + pub(crate) fn run(&mut self, mut req: HttpRequest) -> Reply { + if let Some(idx) = self.router.recognize(&mut req) { + self.resources[idx].handle(req.clone(), Some(&mut self.default)) } else { self.default.handle(req, None) } @@ -36,11 +37,12 @@ impl HttpApplication { impl HttpHandler for HttpApplication { - fn handle(&self, req: HttpRequest) -> Result, HttpRequest> { + fn handle(&mut self, req: HttpRequest) -> Result, HttpRequest> { if req.path().starts_with(&self.prefix) { let req = self.prepare_request(req); + // TODO: redesign run callback Ok(Box::new(Pipeline::new(req, Rc::clone(&self.middlewares), - &|req: HttpRequest| self.run(req)))) + &mut |req: HttpRequest| self.run(req)))) } else { Err(req) } @@ -264,11 +266,13 @@ impl Application where S: 'static { resources.insert(pattern, None); } + let (router, resources) = Router::new(prefix, resources); HttpApplication { state: Rc::new(parts.state), prefix: prefix.to_owned(), default: parts.default, - router: Router::new(prefix, resources), + router: router, + resources: resources, middlewares: Rc::new(parts.middlewares), } } @@ -314,7 +318,7 @@ mod tests { #[test] fn test_default_resource() { - let app = Application::new() + let mut app = Application::new() .resource("/test", |r| r.h(httpcodes::HTTPOk)) .finish(); @@ -330,7 +334,7 @@ mod tests { let resp = app.run(req); assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); - let app = Application::new() + let mut app = Application::new() .default_resource(|r| r.h(httpcodes::HTTPMethodNotAllowed)) .finish(); let req = HttpRequest::new( @@ -342,7 +346,7 @@ mod tests { #[test] fn test_unhandled_prefix() { - let app = Application::new() + let mut app = Application::new() .prefix("/test") .resource("/test", |r| r.h(httpcodes::HTTPOk)) .finish(); @@ -351,7 +355,7 @@ mod tests { #[test] fn test_state() { - let app = Application::with_state(10) + let mut app = Application::with_state(10) .resource("/", |r| r.h(httpcodes::HTTPOk)) .finish(); let req = HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone()); diff --git a/src/channel.rs b/src/channel.rs index 6503e82f8..9940a297d 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -18,7 +18,7 @@ use server::{ServerSettings, WorkerSettings}; pub trait HttpHandler: 'static { /// Handle request - fn handle(&self, req: HttpRequest) -> Result, HttpRequest>; + fn handle(&mut self, req: HttpRequest) -> Result, HttpRequest>; /// Set server settings fn server_settings(&mut self, settings: ServerSettings) {} diff --git a/src/fs.rs b/src/fs.rs index 78f3b4e72..c7dc68dc7 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -252,7 +252,7 @@ impl StaticFiles { impl Handler for StaticFiles { type Result = Result; - fn handle(&self, req: HttpRequest) -> Self::Result { + fn handle(&mut self, req: HttpRequest) -> Self::Result { if !self.accessible { Err(io::Error::new(io::ErrorKind::NotFound, "not found")) } else { diff --git a/src/h1.rs b/src/h1.rs index 95fbb8c40..3f23029b0 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -231,7 +231,7 @@ impl Http1 // start request processing let mut pipe = None; - for h in self.settings.handlers().iter() { + for h in self.settings.handlers().iter_mut() { req = match h.handle(req) { Ok(t) => { pipe = Some(t); diff --git a/src/h2.rs b/src/h2.rs index 5a3e81ac2..f0ac8cb77 100644 --- a/src/h2.rs +++ b/src/h2.rs @@ -261,7 +261,7 @@ impl Entry { // start request processing let mut task = None; - for h in settings.handlers().iter() { + for h in settings.handlers().iter_mut() { req = match h.handle(req) { Ok(t) => { task = Some(t); diff --git a/src/handler.rs b/src/handler.rs index f58513a95..a5e34223b 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -19,7 +19,7 @@ pub trait Handler: 'static { type Result: Responder; /// Handle request - fn handle(&self, req: HttpRequest) -> Self::Result; + fn handle(&mut self, req: HttpRequest) -> Self::Result; } /// Trait implemented by types that generate responses for clients. @@ -59,7 +59,7 @@ impl Handler for F { type Result = R; - fn handle(&self, req: HttpRequest) -> R { + fn handle(&mut self, req: HttpRequest) -> R { (self)(req) } } @@ -207,7 +207,7 @@ impl Responder for Box> /// Trait defines object that could be regestered as resource route pub(crate) trait RouteHandler: 'static { - fn handle(&self, req: HttpRequest) -> Reply; + fn handle(&mut self, req: HttpRequest) -> Reply; } /// Route handler wrapper for Handler @@ -236,7 +236,7 @@ impl RouteHandler for WrapHandler R: Responder + 'static, S: 'static, { - fn handle(&self, req: HttpRequest) -> Reply { + fn handle(&mut self, req: HttpRequest) -> Reply { let req2 = req.clone_without_state(); match self.h.handle(req).respond_to(req2) { Ok(reply) => reply.into(), @@ -277,7 +277,7 @@ impl RouteHandler for AsyncHandler E: Into + 'static, S: 'static, { - fn handle(&self, req: HttpRequest) -> Reply { + fn handle(&mut self, req: HttpRequest) -> Reply { let req2 = req.clone_without_state(); let fut = (self.h)(req) .map_err(|e| e.into()) @@ -368,7 +368,7 @@ impl NormalizePath { impl Handler for NormalizePath { type Result = Result; - fn handle(&self, req: HttpRequest) -> Self::Result { + fn handle(&mut self, req: HttpRequest) -> Self::Result { if let Some(router) = req.router() { let query = req.query_string(); if self.merge { @@ -420,7 +420,7 @@ mod tests { #[test] fn test_normalize_path_trailing_slashes() { - let app = Application::new() + let mut app = Application::new() .resource("/resource1", |r| r.method(Method::GET).f(index)) .resource("/resource2/", |r| r.method(Method::GET).f(index)) .default_resource(|r| r.h(NormalizePath::default())) @@ -452,7 +452,7 @@ mod tests { #[test] fn test_normalize_path_trailing_slashes_disabled() { - let app = Application::new() + let mut app = Application::new() .resource("/resource1", |r| r.method(Method::GET).f(index)) .resource("/resource2/", |r| r.method(Method::GET).f(index)) .default_resource(|r| r.h( @@ -479,7 +479,7 @@ mod tests { #[test] fn test_normalize_path_merge_slashes() { - let app = Application::new() + let mut app = Application::new() .resource("/resource1", |r| r.method(Method::GET).f(index)) .resource("/resource1/a/b", |r| r.method(Method::GET).f(index)) .default_resource(|r| r.h(NormalizePath::default())) @@ -515,7 +515,7 @@ mod tests { #[test] fn test_normalize_path_merge_and_append_slashes() { - let app = Application::new() + let mut app = Application::new() .resource("/resource1", |r| r.method(Method::GET).f(index)) .resource("/resource2/", |r| r.method(Method::GET).f(index)) .resource("/resource1/a/b", |r| r.method(Method::GET).f(index)) diff --git a/src/httpcodes.rs b/src/httpcodes.rs index 274167c99..abe226cbe 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -70,13 +70,13 @@ impl StaticResponse { impl Handler for StaticResponse { type Result = HttpResponse; - fn handle(&self, _: HttpRequest) -> HttpResponse { + fn handle(&mut self, _: HttpRequest) -> HttpResponse { HttpResponse::new(self.0, Body::Empty) } } impl RouteHandler for StaticResponse { - fn handle(&self, _: HttpRequest) -> Reply { + fn handle(&mut self, _: HttpRequest) -> Reply { Reply::response(HttpResponse::new(self.0, Body::Empty)) } } diff --git a/src/httprequest.rs b/src/httprequest.rs index 1f2e9eb05..cf79ab33d 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -87,7 +87,7 @@ impl HttpMessage { } /// An HTTP Request -pub struct HttpRequest(SharedHttpMessage, Option>, Option>); +pub struct HttpRequest(SharedHttpMessage, Option>, Option); impl HttpRequest<()> { /// Construct a new Request. @@ -146,7 +146,7 @@ impl HttpRequest<()> { #[inline] /// Construct new http request with state. - pub fn with_state(self, state: Rc, router: Router) -> HttpRequest { + pub fn with_state(self, state: Rc, router: Router) -> HttpRequest { HttpRequest(self.0, Some(state), Some(router)) } } @@ -277,7 +277,7 @@ impl HttpRequest { /// This method returns reference to current `Router` object. #[inline] - pub fn router(&self) -> Option<&Router> { + pub fn router(&self) -> Option<&Router> { self.2.as_ref() } @@ -736,11 +736,11 @@ mod tests { let mut req = HttpRequest::new(Method::GET, Uri::from_str("/value/?id=test").unwrap(), Version::HTTP_11, HeaderMap::new(), None); - let mut resource = Resource::default(); + let mut resource = Resource::<()>::default(); resource.name("index"); let mut map = HashMap::new(); map.insert(Pattern::new("index", "/{key}/"), Some(resource)); - let router = Router::new("", map); + let (router, _) = Router::new("", map); assert!(router.recognize(&mut req).is_some()); assert_eq!(req.match_info().get("key"), Some("value")); @@ -843,11 +843,11 @@ mod tests { let req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); - let mut resource = Resource::default(); + let mut resource = Resource::<()>::default(); resource.name("index"); let mut map = HashMap::new(); map.insert(Pattern::new("index", "/user/{name}.{ext}"), Some(resource)); - let router = Router::new("", map); + let (router, _) = Router::new("", map); assert!(router.has_route("/user/test.html")); assert!(!router.has_route("/test/unknown")); @@ -874,7 +874,7 @@ mod tests { resource.name("index"); let mut map = HashMap::new(); map.insert(Pattern::new("youtube", "https://youtube.com/watch/{video_id}"), None); - let router = Router::new("", map); + let (router, _) = Router::new::<()>("", map); assert!(!router.has_route("https://youtube.com/watch/unknown")); let req = req.with_state(Rc::new(()), router); diff --git a/src/pipeline.rs b/src/pipeline.rs index 71b3e7aff..cc17d2f33 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -15,8 +15,8 @@ use httprequest::HttpRequest; use httpresponse::HttpResponse; use middlewares::{Middleware, Finished, Started, Response}; -type Handler = Fn(HttpRequest) -> Reply; -pub(crate) type PipelineHandler<'a, S> = &'a Fn(HttpRequest) -> Reply; +type Handler = FnMut(HttpRequest) -> Reply; +pub(crate) type PipelineHandler<'a, S> = &'a mut FnMut(HttpRequest) -> Reply; pub struct Pipeline(PipelineInfo, PipelineState); @@ -287,7 +287,7 @@ impl StartMiddlewares { let len = info.mws.len(); loop { if info.count == len { - let reply = (&*handler)(info.req.clone()); + let reply = (&mut *handler)(info.req.clone()); return WaitingResponse::init(info, reply) } else { match info.mws[info.count].start(&mut info.req) { @@ -329,7 +329,7 @@ impl StartMiddlewares { return Ok(RunMiddlewares::init(info, resp)); } if info.count == len { - let reply = (unsafe{&*self.hnd})(info.req.clone()); + let reply = (unsafe{&mut *self.hnd})(info.req.clone()); return Ok(WaitingResponse::init(info, reply)); } else { loop { diff --git a/src/resource.rs b/src/resource.rs index 937c28251..ee6d682e5 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -126,10 +126,10 @@ impl Resource { self.routes.last_mut().unwrap().f(handler) } - pub(crate) fn handle(&self, mut req: HttpRequest, default: Option<&Resource>) + pub(crate) fn handle(&mut self, mut req: HttpRequest, default: Option<&mut Resource>) -> Reply { - for route in &self.routes { + for route in &mut self.routes { if route.check(&mut req) { return route.handle(req) } diff --git a/src/route.rs b/src/route.rs index 404fa16d3..64b60603d 100644 --- a/src/route.rs +++ b/src/route.rs @@ -36,7 +36,7 @@ impl Route { true } - pub(crate) fn handle(&self, req: HttpRequest) -> Reply { + pub(crate) fn handle(&mut self, req: HttpRequest) -> Reply { self.handler.handle(req) } diff --git a/src/router.rs b/src/router.rs index b5cdd8330..7fd9f2a65 100644 --- a/src/router.rs +++ b/src/router.rs @@ -12,20 +12,20 @@ use server::ServerSettings; /// Interface for application router. -pub struct Router(Rc>); +pub struct Router(Rc); -struct Inner { +struct Inner { prefix: String, regset: RegexSet, named: HashMap, patterns: Vec, - resources: Vec>, srv: ServerSettings, } -impl Router { +impl Router { /// Create new router - pub fn new(prefix: &str, map: HashMap>>) -> Router + pub fn new(prefix: &str, map: HashMap>>) + -> (Router, Vec>) { let prefix = prefix.trim().trim_right_matches('/').to_owned(); let mut named = HashMap::new(); @@ -46,13 +46,12 @@ impl Router { } } - Router(Rc::new( + (Router(Rc::new( Inner{ prefix: prefix, regset: RegexSet::new(&paths).unwrap(), named: named, patterns: patterns, - resources: resources, - srv: ServerSettings::default() })) + srv: ServerSettings::default() })), resources) } pub(crate) fn set_server_settings(&mut self, settings: ServerSettings) { @@ -72,7 +71,7 @@ impl Router { } /// Query for matched resource - pub fn recognize(&self, req: &mut HttpRequest) -> Option<&Resource> { + pub fn recognize(&self, req: &mut HttpRequest) -> Option { let mut idx = None; { let path = &req.path()[self.0.prefix.len()..]; @@ -88,7 +87,7 @@ impl Router { if let Some(idx) = idx { let path: &str = unsafe{ mem::transmute(&req.path()[self.0.prefix.len()..]) }; self.0.patterns[idx].update_match_info(path, req); - return Some(&self.0.resources[idx]) + return Some(idx) } else { None } @@ -128,8 +127,8 @@ impl Router { } } -impl Clone for Router { - fn clone(&self) -> Router { +impl Clone for Router { + fn clone(&self) -> Router { Router(Rc::clone(&self.0)) } } @@ -315,7 +314,7 @@ mod tests { routes.insert(Pattern::new("", "/v{val}/{val2}/index.html"), Some(Resource::default())); routes.insert(Pattern::new("", "/v/{tail:.*}"), Some(Resource::default())); routes.insert(Pattern::new("", "{test}/index.html"), Some(Resource::default())); - let rec = Router::new("", routes); + let (rec, _) = Router::new::<()>("", routes); let mut req = HttpRequest::new( Method::GET, Uri::from_str("/name").unwrap(), diff --git a/src/server.rs b/src/server.rs index 08d6645c6..fcdad1e6f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,5 +1,6 @@ use std::{io, net, thread}; use std::rc::Rc; +use std::cell::{RefCell, RefMut}; use std::sync::Arc; use std::time::Duration; use std::marker::PhantomData; @@ -447,7 +448,7 @@ struct Worker { } pub(crate) struct WorkerSettings { - h: Vec, + h: RefCell>, enabled: bool, keep_alive: u64, bytes: Rc, @@ -457,7 +458,7 @@ pub(crate) struct WorkerSettings { impl WorkerSettings { pub(crate) fn new(h: Vec, keep_alive: Option) -> WorkerSettings { WorkerSettings { - h: h, + h: RefCell::new(h), enabled: if let Some(ka) = keep_alive { ka > 0 } else { false }, keep_alive: keep_alive.unwrap_or(0), bytes: Rc::new(helpers::SharedBytesPool::new()), @@ -465,8 +466,8 @@ impl WorkerSettings { } } - pub fn handlers(&self) -> &Vec { - &self.h + pub fn handlers(&self) -> RefMut> { + self.h.borrow_mut() } pub fn keep_alive(&self) -> u64 { self.keep_alive From e4bfef9d262d849c77a34771bb1932e59a50c17a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Dec 2017 09:28:24 -0800 Subject: [PATCH 184/279] fix tests --- guide/src/qs_4.md | 1 + 1 file changed, 1 insertion(+) diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index 9ef785023..b96d64bff 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -78,6 +78,7 @@ number of requests processed per thread. Proper implementation would use `Arc` a # extern crate actix; # extern crate actix_web; use actix_web::*; +use actix_web::dev::Handler; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; From 030a70841aeaff43ef3d89fdbcc69069ab877e19 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Dec 2017 11:04:25 -0800 Subject: [PATCH 185/279] add link to gitter --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b5478b90..cd9d5a828 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](http://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) +# Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](http://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Actix web is a small, fast, down-to-earth, open source rust web framework. From 5e17a846af5ed1a044f5f1e4489d017e5390f506 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Dec 2017 11:19:08 -0800 Subject: [PATCH 186/279] add notes on sync primitives --- guide/src/qs_4.md | 11 ++++++++--- guide/src/qs_7.md | 5 +++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index b96d64bff..e44483d07 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -1,7 +1,7 @@ # Handler -A request handler can by any object that implements -[`Handler` trait](../actix_web/dev/trait.Handler.html#implementors). +A request handler can by any object that implements +[*Handler trait*](../actix_web/dev/trait.Handler.html). Request handling happen in two stages. First handler object get called. Handle can return any object that implements [*Responder trait*](../actix_web/trait.Responder.html#foreign-impls). @@ -11,7 +11,7 @@ result of the `respond_to()` call get converted to `Reply` object. By default actix provides `Responder` implementations for some standard types, like `&'static str`, `String`, etc. For complete list of implementations check -[Responder documentation](../actix_web/trait.Responder.html#foreign-impls). +[*Responder documentation*](../actix_web/trait.Responder.html#foreign-impls). Examples of valid handlers: @@ -115,6 +115,11 @@ fn main() { } ``` +Be careful with synchronization primitives like *Mutex* or *RwLock*. Actix web framework +handles request asynchronously, by blocking thread execution all concurrent +request handling processes would block. If you need to share or update some state +from multiple threads consider using [actix](https://actix.github.io/actix/actix/) actor system. + ## Response with custom type To return custom type directly from handler function type needs to implement `Responder` trait. diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index a51c5c8a6..f4fdf5e97 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -6,8 +6,9 @@ Builder-like patter is used to construct an instance of `HttpResponse`. `HttpResponse` provides several method that returns `HttpResponseBuilder` instance, which is implements various convinience methods that helps build response. Check [documentation](../actix_web/dev/struct.HttpResponseBuilder.html) -for type description. Methods `.body`, `.finish`, `.json` finalizes response creation, -if this methods get call for the same builder instance, builder will panic. +for type description. Methods `.body`, `.finish`, `.json` finalizes response creation and +returns constructed *HttpResponse* instance. if this methods get called for the same +builder instance multiple times, builder will panic. ```rust # extern crate actix_web; From cce9c68a10b4fd0d0312cce7d623e71a8350974f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Dec 2017 12:46:27 -0800 Subject: [PATCH 187/279] add doc string --- src/server.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/server.rs b/src/server.rs index fcdad1e6f..d1ce80a6a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -264,6 +264,27 @@ impl HttpServer /// For each address this method starts separate thread which does `accept()` in a loop. /// /// This methods panics if no socket addresses get bound. + /// + /// This method requires to run within properly configured `Actix` system. + /// + /// ```rust + /// extern crate actix; + /// extern crate actix_web; + /// use actix_web::*; + /// + /// fn main() { + /// let sys = actix::System::new("example"); // <- create Actix system + /// + /// HttpServer::new( + /// || Application::new() + /// .resource("/", |r| r.h(httpcodes::HTTPOk))) + /// .bind("127.0.0.1:8088").expect("Can not bind to 127.0.0.1:8088") + /// .start(); + /// # actix::Arbiter::system().send(actix::msgs::SystemExit(0)); + /// + /// let _ = sys.run(); // <- Run actix system, this method actually starts all async processes + /// } + /// ``` pub fn start(mut self) -> SyncAddress { if self.sockets.is_empty() { From dd3a2aa68acedffb81631cd6e8ca7be4294955e1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Dec 2017 14:36:03 -0800 Subject: [PATCH 188/279] add HttpServer::server_hostname method --- examples/basic.rs | 3 +-- src/info.rs | 4 +--- src/server.rs | 26 ++++++++++++++++++++------ 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index 42d29e8ad..9beecbcde 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -87,8 +87,7 @@ fn main() { .resource("/", |r| r.method(Method::GET).f(|req| { println!("{:?}", req); - httpcodes::HTTPFound - .build() + HttpResponse::Found() .header("LOCATION", "/index.html") .finish() }))) diff --git a/src/info.rs b/src/info.rs index 76cf678ea..92ffa4d6c 100644 --- a/src/info.rs +++ b/src/info.rs @@ -8,9 +8,6 @@ const X_FORWARDED_PROTO: &str = "X-FORWARDED-PROTO"; /// `HttpRequest` connection information -/// -/// While it is possible to create `ConnectionInfo` directly, -/// consider using `HttpRequest::load_connection_info()` which cache result. pub struct ConnectionInfo<'a> { scheme: &'a str, host: &'a str, @@ -138,6 +135,7 @@ impl<'a> ConnectionInfo<'a> { /// - X-Forwarded-Host /// - Host /// - Uri + /// - Server hostname pub fn host(&self) -> &str { self.host } diff --git a/src/server.rs b/src/server.rs index d1ce80a6a..e5ee5c50e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -53,11 +53,13 @@ impl Default for ServerSettings { impl ServerSettings { /// Crate server settings instance - fn new(addr: Option, secure: bool) -> Self { - let host = if let Some(ref addr) = addr { + fn new(addr: Option, host: &Option, secure: bool) -> Self { + let host = if let Some(ref host) = *host { + host.clone() + } else if let Some(ref addr) = addr { format!("{}", addr) } else { - "unknown".to_owned() + "localhost".to_owned() }; ServerSettings { addr: addr, @@ -97,6 +99,7 @@ pub struct HttpServer addr: PhantomData, threads: usize, backlog: i32, + host: Option, keep_alive: Option, factory: Arc U + Send + Sync>, workers: Vec>>, @@ -134,6 +137,7 @@ impl HttpServer addr: PhantomData, threads: num_cpus::get(), backlog: 2048, + host: None, keep_alive: None, factory: Arc::new(factory), workers: Vec::new(), @@ -175,6 +179,16 @@ impl HttpServer self } + /// Set server host name. + /// + /// Host name is used by application router aa a hostname for url generation. + /// Check [ConnectionInfo](./dev/struct.ConnectionInfo.html#method.host) documentation + /// for more information. + pub fn server_hostname(mut self, val: String) -> Self { + self.host = Some(val); + self + } + /// The socket address to bind /// /// To mind multiple addresses this method can be call multiple times. @@ -291,7 +305,7 @@ impl HttpServer panic!("HttpServer::bind() has to be called befor start()"); } else { let addrs: Vec<(net::SocketAddr, Socket)> = self.sockets.drain().collect(); - let settings = ServerSettings::new(Some(addrs[0].0), false); + let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false); let workers = self.start_workers(&settings, &StreamHandlerType::Normal); // start acceptors threads @@ -395,7 +409,7 @@ impl HttpServer { if !self.sockets.is_empty() { let addrs: Vec<(net::SocketAddr, Socket)> = self.sockets.drain().collect(); - let settings = ServerSettings::new(Some(addrs[0].0), false); + let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false); let workers = self.start_workers(&settings, &StreamHandlerType::Normal); // start acceptors threads @@ -407,7 +421,7 @@ impl HttpServer // set server settings let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); - let settings = ServerSettings::new(Some(addr), secure); + let settings = ServerSettings::new(Some(addr), &self.host, secure); let mut apps: Vec<_> = (*self.factory)().into_iter().map(|h| h.into_handler()).collect(); for app in &mut apps { app.server_settings(settings.clone()); From 9521de5746a74fca53438540d01541e70dfa834a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Dec 2017 14:45:38 -0800 Subject: [PATCH 189/279] HttpServer::addrs() return all bound socket addresses --- src/server.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/server.rs b/src/server.rs index e5ee5c50e..ab024c8c1 100644 --- a/src/server.rs +++ b/src/server.rs @@ -189,6 +189,11 @@ impl HttpServer self } + /// Get addresses of bound sockets. + pub fn addrs(&self) -> Vec { + self.sockets.keys().map(|addr| addr.clone()).collect() + } + /// The socket address to bind /// /// To mind multiple addresses this method can be call multiple times. From e3b0f027942a40015ccced5f7e2a75490db2a24d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Dec 2017 15:17:20 -0800 Subject: [PATCH 190/279] fix type for disable feartures --- src/server.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server.rs b/src/server.rs index ab024c8c1..023ed9916 100644 --- a/src/server.rs +++ b/src/server.rs @@ -336,7 +336,7 @@ impl HttpServer, net::SocketAddr, H, Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound")) } else { let addrs: Vec<(net::SocketAddr, Socket)> = self.sockets.drain().collect(); - let settings = ServerSettings::new(Some(addrs[0].0), false); + let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false); let acceptor = match TlsAcceptor::builder(pkcs12) { Ok(builder) => { match builder.build() { @@ -373,7 +373,7 @@ impl HttpServer, net::SocketAddr, H, Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound")) } else { let addrs: Vec<(net::SocketAddr, Socket)> = self.sockets.drain().collect(); - let settings = ServerSettings::new(Some(addrs[0].0), false); + let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false); let acceptor = match SslAcceptorBuilder::mozilla_intermediate( SslMethod::tls(), &identity.pkey, &identity.cert, &identity.chain) { From f6510161b577c6777bb33b6f9bbae99f98354a23 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Dec 2017 16:35:00 -0800 Subject: [PATCH 191/279] add simple TestServer for integrational tests cases --- src/lib.rs | 1 + src/server.rs | 2 +- src/test/mod.rs | 164 +++++++++++++++++++++++++++++++++++++++++++ tests/test_server.rs | 60 +++------------- 4 files changed, 176 insertions(+), 51 deletions(-) create mode 100644 src/test/mod.rs diff --git a/src/lib.rs b/src/lib.rs index 56240a8cc..e8e93eb57 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,6 +117,7 @@ pub mod httpcodes; pub mod multipart; pub mod middlewares; pub mod pred; +pub mod test; pub mod payload; pub use error::{Error, Result, ResponseError}; pub use body::{Body, Binary}; diff --git a/src/server.rs b/src/server.rs index 023ed9916..399115076 100644 --- a/src/server.rs +++ b/src/server.rs @@ -191,7 +191,7 @@ impl HttpServer /// Get addresses of bound sockets. pub fn addrs(&self) -> Vec { - self.sockets.keys().map(|addr| addr.clone()).collect() + self.sockets.keys().cloned().collect() } /// The socket address to bind diff --git a/src/test/mod.rs b/src/test/mod.rs new file mode 100644 index 000000000..e3e420827 --- /dev/null +++ b/src/test/mod.rs @@ -0,0 +1,164 @@ +//! Various helpers for Actix applications to use during testing. + +use std::{net, thread}; +use std::sync::mpsc; + +use actix::{Arbiter, SyncAddress, System, msgs}; +use tokio_core::net::TcpListener; + +use server::HttpServer; +use handler::Handler; +use channel::IntoHttpHandler; +use middlewares::Middleware; +use application::{Application, HttpApplication}; + + +/// The `TestServer` type. +/// +/// `TestServer` is very simple test server that simplify process of writing +/// integrational tests cases for actix web applications. +/// +/// # Examples +/// +/// ```rust +/// # extern crate actix; +/// # extern crate actix_web; +/// # use actix_web::*; +/// # extern crate reqwest; +/// # +/// # fn my_handler(req: HttpRequest) -> HttpResponse { +/// # httpcodes::HTTPOk.response() +/// # } +/// # +/// # fn main() { +/// use actix_web::test::TestServer; +/// +/// let srv = TestServer::new(|app| app.handler(my_handler)); +/// +/// assert!(reqwest::get(&srv.url("/")).unwrap().status().is_success()); +/// # } +/// ``` +pub struct TestServer { + addr: net::SocketAddr, + thread: Option>, + sys: SyncAddress, +} + +impl TestServer { + + /// Start new test server + /// + /// This methos accepts configuration method. You can add + /// middlewares or set handlers for test application. + pub fn new(config: F) -> Self + where F: Sync + Send + 'static + Fn(&mut TestApp<()>), + { + TestServer::with_state(||(), config) + } + + /// Start new test server with custom application state + /// + /// This methos accepts state factory and configuration method. + pub fn with_state(state: FS, config: F) -> Self + where S: 'static, + FS: Sync + Send + 'static + Fn() -> S, + F: Sync + Send + 'static + Fn(&mut TestApp), + { + let (tx, rx) = mpsc::channel(); + + // run server in separate thread + let join = thread::spawn(move || { + let sys = System::new("actix-test-server"); + + let tcp = net::TcpListener::bind("0.0.0.0:0").unwrap(); + let local_addr = tcp.local_addr().unwrap(); + let tcp = TcpListener::from_listener(tcp, &local_addr, Arbiter::handle()).unwrap(); + + HttpServer::new(move || { + let mut app = TestApp::new(state()); + config(&mut app); + app} + ).start_incoming(tcp.incoming(), false); + + tx.send((Arbiter::system(), local_addr)).unwrap(); + let _ = sys.run(); + }); + + let (sys, addr) = rx.recv().unwrap(); + TestServer { + addr: addr, + thread: Some(join), + sys: sys, + } + } + + /// Construct test server url + pub fn url(&self, uri: &str) -> String { + if uri.starts_with('/') { + format!("http://{}{}", self.addr, uri) + } else { + format!("http://{}/{}", self.addr, uri) + } + } + + /// Stop http server + fn stop(&mut self) { + if let Some(handle) = self.thread.take() { + self.sys.send(msgs::SystemExit(0)); + let _ = handle.join(); + } + } +} + +impl Drop for TestServer { + fn drop(&mut self) { + self.stop() + } +} + + +/// Test application helper for testing request handlers. +pub struct TestApp { + app: Option>, +} + +impl TestApp { + fn new(state: S) -> TestApp { + let app = Application::with_state(state); + TestApp{app: Some(app)} + } + + /// Register handler for "/" + pub fn handler>(&mut self, handler: H) { + self.app = Some(self.app.take().unwrap().resource("/", |r| r.h(handler))); + } + + /// Register middleware + pub fn middleware(&mut self, mw: T) -> &mut TestApp + where T: Middleware + 'static + { + self.app = Some(self.app.take().unwrap().middleware(mw)); + self + } +} + +impl IntoHttpHandler for TestApp { + type Handler = HttpApplication; + + fn into_handler(self) -> HttpApplication { + self.app.unwrap().finish() + } +} + +#[doc(hidden)] +impl Iterator for TestApp { + type Item = HttpApplication; + + fn next(&mut self) -> Option { + if let Some(mut app) = self.app.take() { + Some(app.finish()) + } else { + None + } + } +} diff --git a/tests/test_server.rs b/tests/test_server.rs index 14369da37..0c0a6bd75 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -3,48 +3,15 @@ extern crate actix_web; extern crate tokio_core; extern crate reqwest; -use std::{net, thread}; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; -use tokio_core::net::TcpListener; -use actix::*; use actix_web::*; #[test] fn test_serve() { - thread::spawn(|| { - let sys = System::new("test"); - let srv = HttpServer::new( - || vec![Application::new() - .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))]); - srv.bind("127.0.0.1:58902").unwrap().start(); - sys.run(); - }); - assert!(reqwest::get("http://localhost:58902/").unwrap().status().is_success()); -} - -#[test] -fn test_serve_incoming() { - let loopback = net::Ipv4Addr::new(127, 0, 0, 1); - let socket = net::SocketAddrV4::new(loopback, 0); - let tcp = net::TcpListener::bind(socket).unwrap(); - let addr1 = tcp.local_addr().unwrap(); - let addr2 = tcp.local_addr().unwrap(); - - thread::spawn(move || { - let sys = System::new("test"); - - let srv = HttpServer::new( - || Application::new() - .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))); - let tcp = TcpListener::from_listener(tcp, &addr2, Arbiter::handle()).unwrap(); - srv.start_incoming(tcp.incoming(), false); - sys.run(); - }); - - assert!(reqwest::get(&format!("http://{}/", addr1)) - .unwrap().status().is_success()); + let srv = test::TestServer::new(|app| app.handler(httpcodes::HTTPOk)); + assert!(reqwest::get(&srv.url("/")).unwrap().status().is_success()); } struct MiddlewareTest { @@ -80,21 +47,14 @@ fn test_middlewares() { let act_num2 = Arc::clone(&num2); let act_num3 = Arc::clone(&num3); - thread::spawn(move || { - let sys = System::new("test"); - - HttpServer::new( - move || vec![Application::new() - .middleware(MiddlewareTest{start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3)}) - .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))]) - .bind("127.0.0.1:58904").unwrap() - .start(); - sys.run(); - }); - - assert!(reqwest::get("http://localhost:58904/").unwrap().status().is_success()); + let srv = test::TestServer::new( + move |app| app.middleware(MiddlewareTest{start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3)}) + .handler(httpcodes::HTTPOk) + ); + + assert!(reqwest::get(&srv.url("/")).unwrap().status().is_success()); assert_eq!(num1.load(Ordering::Relaxed), 1); assert_eq!(num2.load(Ordering::Relaxed), 1); assert_eq!(num3.load(Ordering::Relaxed), 1); From d3b7d2d6b3512887cf4e8c9ac52df0d4280ccec4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Dec 2017 16:47:55 -0800 Subject: [PATCH 192/279] allow to use application factory for test server --- src/test/mod.rs | 32 +++++++++++++++++++++++++++++++- tests/test_server.rs | 9 ++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/test/mod.rs b/src/test/mod.rs index e3e420827..247753b9c 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -8,7 +8,7 @@ use tokio_core::net::TcpListener; use server::HttpServer; use handler::Handler; -use channel::IntoHttpHandler; +use channel::{HttpHandler, IntoHttpHandler}; use middlewares::Middleware; use application::{Application, HttpApplication}; @@ -56,6 +56,36 @@ impl TestServer { TestServer::with_state(||(), config) } + /// Start new test server with application factory + pub fn with_factory(factory: F) -> Self + where H: HttpHandler, + F: Sync + Send + 'static + Fn() -> U, + U: IntoIterator + 'static, + V: IntoHttpHandler, + { + let (tx, rx) = mpsc::channel(); + + // run server in separate thread + let join = thread::spawn(move || { + let sys = System::new("actix-test-server"); + let tcp = net::TcpListener::bind("0.0.0.0:0").unwrap(); + let local_addr = tcp.local_addr().unwrap(); + let tcp = TcpListener::from_listener(tcp, &local_addr, Arbiter::handle()).unwrap(); + + HttpServer::new(factory).start_incoming(tcp.incoming(), false); + + tx.send((Arbiter::system(), local_addr)).unwrap(); + let _ = sys.run(); + }); + + let (sys, addr) = rx.recv().unwrap(); + TestServer { + addr: addr, + thread: Some(join), + sys: sys, + } + } + /// Start new test server with custom application state /// /// This methos accepts state factory and configuration method. diff --git a/tests/test_server.rs b/tests/test_server.rs index 0c0a6bd75..d148e9ef8 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -9,11 +9,18 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use actix_web::*; #[test] -fn test_serve() { +fn test_simple() { let srv = test::TestServer::new(|app| app.handler(httpcodes::HTTPOk)); assert!(reqwest::get(&srv.url("/")).unwrap().status().is_success()); } +#[test] +fn test_application() { + let srv = test::TestServer::with_factory( + || Application::new().resource("/", |r| r.h(httpcodes::HTTPOk))); + assert!(reqwest::get(&srv.url("/")).unwrap().status().is_success()); +} + struct MiddlewareTest { start: Arc, response: Arc, From 7f77ba557d811c80d89313f9ae9a5a550c1451b1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Dec 2017 17:14:37 -0800 Subject: [PATCH 193/279] add testing section to guide --- build.rs | 1 + guide/src/SUMMARY.md | 1 + guide/src/qs_8.md | 60 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 guide/src/qs_8.md diff --git a/build.rs b/build.rs index 081d2b509..2346ab169 100644 --- a/build.rs +++ b/build.rs @@ -20,6 +20,7 @@ fn main() { "guide/src/qs_4_5.md", "guide/src/qs_5.md", "guide/src/qs_7.md", + "guide/src/qs_8.md", "guide/src/qs_9.md", "guide/src/qs_10.md", "guide/src/qs_12.md", diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 275392118..8e78a0c3e 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -8,6 +8,7 @@ - [Errors](./qs_4_5.md) - [URL Dispatch](./qs_5.md) - [Request & Response](./qs_7.md) +- [Testing](./qs_8.md) - [WebSockets](./qs_9.md) - [Middlewares](./qs_10.md) - [Static file handling](./qs_12.md) diff --git a/guide/src/qs_8.md b/guide/src/qs_8.md new file mode 100644 index 000000000..be08e755e --- /dev/null +++ b/guide/src/qs_8.md @@ -0,0 +1,60 @@ +# Testing + +Every application should be well tested and. Actix provides the tools to perform unit and +integration tests. + +## Integration tests + +There are several methods how you can test your application. Actix provides +[*TestServer*](../actix_web/test/struct.TestServer.html) +server that could be used to run whole application of just specific handlers +in real http server. At the moment it is required to use third-party libraries +to make actual requests, libraries like [reqwest](https://crates.io/crates/reqwest). + +In simple form *TestServer* could be configured to use handler. *TestServer::new* method +accepts configuration function, only argument for this function is *test application* +instance. You can check [api documentation](../actix_web/test/struct.TestApp.html) +for more information. + +```rust +# extern crate actix_web; +extern crate reqwest; +use actix_web::*; +use actix_web::test::TestServer; + +fn index(req: HttpRequest) -> HttpResponse { + httpcodes::HTTPOk.response() +} + +fn main() { + let srv = TestServer::new(|app| app.handler(index)); // <- Start new test server + let url = srv.url("/"); // <- get handler url + assert!(reqwest::get(&url).unwrap().status().is_success()); // <- make request +} +``` + +Other option is to use application factory. In this case you need to pass factory function +same as you use for real http server configuration. + +```rust +# extern crate actix_web; +extern crate reqwest; +use actix_web::*; +use actix_web::test::TestServer; + +fn index(req: HttpRequest) -> HttpResponse { + httpcodes::HTTPOk.response() +} + +/// This function get called by http server. +fn create_app() -> Application { + Application::new() + .resource("/test", |r| r.h(index)) +} + +fn main() { + let srv = TestServer::with_factory(create_app); // <- Start new test server + let url = srv.url("/test"); // <- get handler url + assert!(reqwest::get(&url).unwrap().status().is_success()); // <- make request +} +``` From 743235b8fd3a21a522435977cb0746f93bdf5b22 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Dec 2017 19:48:02 -0800 Subject: [PATCH 194/279] add unit test helper --- guide/src/qs_8.md | 37 +++++++++ src/application.rs | 5 +- src/handler.rs | 9 +- src/httprequest.rs | 54 ++---------- src/test/mod.rs | 202 ++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 253 insertions(+), 54 deletions(-) diff --git a/guide/src/qs_8.md b/guide/src/qs_8.md index be08e755e..7661b9ad4 100644 --- a/guide/src/qs_8.md +++ b/guide/src/qs_8.md @@ -3,6 +3,43 @@ Every application should be well tested and. Actix provides the tools to perform unit and integration tests. +## Unit tests + +For unit testing actix provides request builder type and simple handler runner. +[*TestRequest*](../actix_web/test/struct.TestRequest.html) implements builder-like pattern. +You can generate `HttpRequest` instance with `finish()` method or you can +run your handler with `run()` or `run_async()` methods. + +```rust +# extern crate http; +# extern crate actix_web; +use http::{header, StatusCode}; +use actix_web::*; +use actix_web::test::TestRequest; + +fn index(req: HttpRequest) -> HttpResponse { + if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { + if let Ok(s) = hdr.to_str() { + return httpcodes::HTTPOk.response() + } + } + httpcodes::HTTPBadRequest.response() +} + +fn main() { + let resp = TestRequest::with_header("content-type", "text/plain") + .run(index) + .unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let resp = TestRequest::default() + .run(index) + .unwrap(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); +} +``` + + ## Integration tests There are several methods how you can test your application. Actix provides diff --git a/src/application.rs b/src/application.rs index 80b8173ce..1de0ff5b6 100644 --- a/src/application.rs +++ b/src/application.rs @@ -313,6 +313,7 @@ mod tests { use std::str::FromStr; use http::{Method, Version, Uri, HeaderMap, StatusCode}; use super::*; + use test::TestRequest; use httprequest::HttpRequest; use httpcodes; @@ -322,9 +323,7 @@ mod tests { .resource("/test", |r| r.h(httpcodes::HTTPOk)) .finish(); - let req = HttpRequest::new( - Method::GET, Uri::from_str("/test").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); + let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); diff --git a/src/handler.rs b/src/handler.rs index a5e34223b..deab468ea 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -412,6 +412,7 @@ impl Handler for NormalizePath { mod tests { use super::*; use http::{header, Method}; + use test::TestRequest; use application::Application; fn index(_req: HttpRequest) -> HttpResponse { @@ -438,7 +439,7 @@ mod tests { ("/resource2/?p1=1&p2=2", "", StatusCode::OK) ]; for (path, target, code) in params { - let req = app.prepare_request(HttpRequest::from_path(path)); + let req = app.prepare_request(TestRequest::with_uri(path).finish()); let resp = app.run(req); let r = resp.as_response().unwrap(); assert_eq!(r.status(), code); @@ -470,7 +471,7 @@ mod tests { ("/resource2/?p1=1&p2=2", StatusCode::OK) ]; for (path, code) in params { - let req = app.prepare_request(HttpRequest::from_path(path)); + let req = app.prepare_request(TestRequest::with_uri(path).finish()); let resp = app.run(req); let r = resp.as_response().unwrap(); assert_eq!(r.status(), code); @@ -501,7 +502,7 @@ mod tests { ("/////resource1/a//b/?p=1", "", StatusCode::NOT_FOUND), ]; for (path, target, code) in params { - let req = app.prepare_request(HttpRequest::from_path(path)); + let req = app.prepare_request(TestRequest::with_uri(path).finish()); let resp = app.run(req); let r = resp.as_response().unwrap(); assert_eq!(r.status(), code); @@ -558,7 +559,7 @@ mod tests { ("/////resource2/a///b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), ]; for (path, target, code) in params { - let req = app.prepare_request(HttpRequest::from_path(path)); + let req = app.prepare_request(TestRequest::with_uri(path).finish()); let resp = app.run(req); let r = resp.as_response().unwrap(); assert_eq!(r.status(), code); diff --git a/src/httprequest.rs b/src/httprequest.rs index cf79ab33d..ddaa4e981 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -119,31 +119,6 @@ impl HttpRequest<()> { HttpRequest(msg, None, None) } - /// Construct a new Request. - #[inline] - #[cfg(test)] - pub fn from_path(path: &str) -> HttpRequest - { - use std::str::FromStr; - - HttpRequest( - SharedHttpMessage::from_message(HttpMessage { - method: Method::GET, - uri: Uri::from_str(path).unwrap(), - version: Version::HTTP_11, - headers: HeaderMap::new(), - params: Params::default(), - cookies: None, - addr: None, - payload: None, - extensions: Extensions::new(), - info: None, - }), - None, - None, - ) - } - #[inline] /// Construct new http request with state. pub fn with_state(self, state: Rc, router: Router) -> HttpRequest { @@ -163,7 +138,7 @@ impl HttpRequest { // mutable reference should not be returned as result for request's method #[inline(always)] #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] - fn as_mut(&self) -> &mut HttpMessage { + pub(crate) fn as_mut(&self) -> &mut HttpMessage { self.0.get_mut() } @@ -657,30 +632,25 @@ mod tests { use std::str::FromStr; use router::Pattern; use resource::Resource; + use test::TestRequest; #[test] fn test_debug() { - let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, HeaderMap::new(), None); + let req = TestRequest::with_header("content-type", "text/plain").finish(); let dbg = format!("{:?}", req); assert!(dbg.contains("HttpRequest")); } #[test] fn test_no_request_cookies() { - let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, HeaderMap::new(), None); + let req = HttpRequest::default(); assert!(req.cookies().unwrap().is_empty()); } #[test] fn test_request_cookies() { - let mut headers = HeaderMap::new(); - headers.insert(header::COOKIE, - header::HeaderValue::from_static("cookie1=value1; cookie2=value2")); - - let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); + let req = TestRequest::with_header( + header::COOKIE, "cookie1=value1; cookie2=value2").finish(); { let cookies = req.cookies().unwrap(); assert_eq!(cookies.len(), 2); @@ -733,8 +703,7 @@ mod tests { #[test] fn test_request_match_info() { - let mut req = HttpRequest::new(Method::GET, Uri::from_str("/value/?id=test").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); + let mut req = TestRequest::with_uri("/value/?id=test").finish(); let mut resource = Resource::<()>::default(); resource.name("index"); @@ -748,15 +717,10 @@ mod tests { #[test] fn test_chunked() { - let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, HeaderMap::new(), None); + let req = HttpRequest::default(); assert!(!req.chunked().unwrap()); - let mut headers = HeaderMap::new(); - headers.insert(header::TRANSFER_ENCODING, - header::HeaderValue::from_static("chunked")); - let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); + let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish(); assert!(req.chunked().unwrap()); let mut headers = HeaderMap::new(); diff --git a/src/test/mod.rs b/src/test/mod.rs index 247753b9c..0d3c596f3 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -1,17 +1,30 @@ //! Various helpers for Actix applications to use during testing. use std::{net, thread}; +use std::rc::Rc; use std::sync::mpsc; +use std::str::FromStr; +use std::collections::HashMap; use actix::{Arbiter, SyncAddress, System, msgs}; +use cookie::Cookie; +use http::{Uri, Method, Version, HeaderMap, HttpTryFrom}; +use http::header::{HeaderName, HeaderValue}; +use futures::Future; use tokio_core::net::TcpListener; +use tokio_core::reactor::Core; +use error::Error; use server::HttpServer; -use handler::Handler; +use handler::{Handler, Responder, ReplyItem}; use channel::{HttpHandler, IntoHttpHandler}; use middlewares::Middleware; use application::{Application, HttpApplication}; - +use param::Params; +use router::Router; +use payload::Payload; +use httprequest::HttpRequest; +use httpresponse::HttpResponse; /// The `TestServer` type. /// @@ -192,3 +205,188 @@ impl Iterator for TestApp { } } } + +/// Test `HttpRequest` builder +/// +/// ```rust +/// # extern crate http; +/// # extern crate actix_web; +/// # use http::{header, StatusCode}; +/// # use actix_web::*; +/// use actix_web::test::TestRequest; +/// +/// fn index(req: HttpRequest) -> HttpResponse { +/// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { +/// httpcodes::HTTPOk.response() +/// } else { +/// httpcodes::HTTPBadRequest.response() +/// } +/// } +/// +/// fn main() { +/// let resp = TestRequest::with_header("content-type", "text/plain") +/// .run(index).unwrap(); +/// assert_eq!(resp.status(), StatusCode::OK); +/// +/// let resp = TestRequest::default() +/// .run(index).unwrap(); +/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); +/// } +/// ``` +pub struct TestRequest { + state: S, + version: Version, + method: Method, + uri: Uri, + headers: HeaderMap, + params: Params<'static>, + cookies: Option>>, + payload: Option, +} + +impl Default for TestRequest<()> { + + fn default() -> TestRequest<()> { + TestRequest { + state: (), + method: Method::GET, + uri: Uri::from_str("/").unwrap(), + version: Version::HTTP_11, + headers: HeaderMap::new(), + params: Params::default(), + cookies: None, + payload: None, + } + } +} + +impl TestRequest<()> { + + /// Create TestReqeust and set request uri + pub fn with_uri(path: &str) -> TestRequest<()> { + TestRequest::default().uri(path) + } + + /// Create TestReqeust and set header + pub fn with_header(key: K, value: V) -> TestRequest<()> + where HeaderName: HttpTryFrom, + HeaderValue: HttpTryFrom + { + TestRequest::default().header(key, value) + } +} + +impl TestRequest { + + /// Start HttpRequest build process with application state + pub fn with_state(state: S) -> TestRequest { + TestRequest { + state: state, + method: Method::GET, + uri: Uri::from_str("/").unwrap(), + version: Version::HTTP_11, + headers: HeaderMap::new(), + params: Params::default(), + cookies: None, + payload: None, + } + } + + /// Set HTTP version of this request + pub fn version(mut self, ver: Version) -> Self { + self.version = ver; + self + } + + /// Set HTTP method of this request + pub fn method(mut self, meth: Method) -> Self { + self.method = meth; + self + } + + /// Set HTTP Uri of this request + pub fn uri(mut self, path: &str) -> Self { + self.uri = Uri::from_str(path).unwrap(); + self + } + + /// Set a header + pub fn header(mut self, key: K, value: V) -> Self + where HeaderName: HttpTryFrom, + HeaderValue: HttpTryFrom + { + if let Ok(key) = HeaderName::try_from(key) { + if let Ok(value) = HeaderValue::try_from(value) { + self.headers.append(key, value); + return self + } + } + panic!("Can not create header"); + } + + /// Set request path pattern parameter + pub fn param(mut self, name: &'static str, value: &'static str) -> Self { + self.params.add(name, value); + self + } + + /// Complete request creation and generate `HttpRequest` instance + pub fn finish(self) -> HttpRequest { + let TestRequest { state, method, uri, version, headers, params, cookies, payload } = self; + let req = HttpRequest::new(method, uri, version, headers, payload); + req.as_mut().cookies = cookies; + req.as_mut().params = params; + let (router, _) = Router::new::("/", HashMap::new()); + req.with_state(Rc::new(state), router) + } + + /// This method generates `HttpRequest` instance and runs handler + /// with generated request. + /// + /// This method panics is handler returns actor or async result. + pub fn run>(self, mut h: H) -> + Result>::Result as Responder>::Error> + { + let req = self.finish(); + let resp = h.handle(req.clone()); + + match resp.respond_to(req.clone_without_state()) { + Ok(resp) => { + match resp.into().into() { + ReplyItem::Message(resp) => Ok(resp), + ReplyItem::Actor(_) => panic!("Actor handler is not supported."), + ReplyItem::Future(_) => panic!("Async handler is not supported."), + } + }, + Err(err) => Err(err), + } + } + + /// This method generates `HttpRequest` instance and runs handler + /// with generated request. + /// + /// This method panics is handler returns actor. + pub fn run_async(self, h: H) -> Result + where H: Fn(HttpRequest) -> F + 'static, + F: Future + 'static, + R: Responder + 'static, + E: Into + 'static + { + let req = self.finish(); + let fut = h(req.clone()); + + let mut core = Core::new().unwrap(); + match core.run(fut) { + Ok(r) => { + match r.respond_to(req.clone_without_state()) { + Ok(reply) => match reply.into().into() { + ReplyItem::Message(resp) => Ok(resp), + _ => panic!("Nested async replies are not supported"), + }, + Err(e) => Err(e), + } + }, + Err(err) => Err(err), + } + } +} From 29adc205815c7c215751c9f17fa7874f5ecccf0e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Dec 2017 19:59:41 -0800 Subject: [PATCH 195/279] rename module --- examples/basic.rs | 8 ++-- examples/diesel/src/main.rs | 2 +- examples/json/src/main.rs | 2 +- examples/multipart/src/main.rs | 2 +- examples/state.rs | 2 +- examples/template_tera/src/main.rs | 2 +- examples/tls/src/main.rs | 2 +- examples/websocket.rs | 2 +- guide/src/qs_10.md | 38 +++++++++---------- guide/src/qs_3.md | 8 ++-- src/application.rs | 2 +- src/lib.rs | 2 +- .../defaultheaders.rs | 4 +- src/{middlewares => middleware}/logger.rs | 6 +-- src/{middlewares => middleware}/mod.rs | 0 src/{middlewares => middleware}/session.rs | 14 +++---- src/pipeline.rs | 2 +- src/test/mod.rs | 2 +- tests/test_server.rs | 14 +++---- 19 files changed, 57 insertions(+), 57 deletions(-) rename src/{middlewares => middleware}/defaultheaders.rs (97%) rename src/{middlewares => middleware}/logger.rs (98%) rename src/{middlewares => middleware}/mod.rs (100%) rename src/{middlewares => middleware}/session.rs (97%) diff --git a/examples/basic.rs b/examples/basic.rs index 9beecbcde..7328a5a96 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -8,7 +8,7 @@ extern crate futures; use futures::Stream; use actix_web::*; -use actix_web::middlewares::RequestSession; +use actix_web::middleware::RequestSession; use futures::future::{FutureResult, result}; /// simple handler @@ -60,10 +60,10 @@ fn main() { HttpServer::new( || Application::new() // enable logger - .middleware(middlewares::Logger::default()) + .middleware(middleware::Logger::default()) // cookie session middleware - .middleware(middlewares::SessionStorage::new( - middlewares::CookieSessionBackend::build(&[0; 32]) + .middleware(middleware::SessionStorage::new( + middleware::CookieSessionBackend::build(&[0; 32]) .secure(false) .finish() )) diff --git a/examples/diesel/src/main.rs b/examples/diesel/src/main.rs index c05857c1a..350a9ee5a 100644 --- a/examples/diesel/src/main.rs +++ b/examples/diesel/src/main.rs @@ -62,7 +62,7 @@ fn main() { HttpServer::new(move || { Application::with_state(State{db: addr.clone()}) // enable logger - .middleware(middlewares::Logger::default()) + .middleware(middleware::Logger::default()) .resource("/{name}", |r| r.method(Method::GET).a(index))}) .bind("127.0.0.1:8080").unwrap() .start(); diff --git a/examples/json/src/main.rs b/examples/json/src/main.rs index 9bc8bebcd..c49bfa152 100644 --- a/examples/json/src/main.rs +++ b/examples/json/src/main.rs @@ -67,7 +67,7 @@ fn main() { HttpServer::new(|| { Application::new() // enable logger - .middleware(middlewares::Logger::default()) + .middleware(middleware::Logger::default()) .resource("/manual", |r| r.method(Method::POST).f(index_manual)) .resource("/", |r| r.method(Method::POST).f(index))}) .bind("127.0.0.1:8080").unwrap() diff --git a/examples/multipart/src/main.rs b/examples/multipart/src/main.rs index 41204bef0..1af329c2f 100644 --- a/examples/multipart/src/main.rs +++ b/examples/multipart/src/main.rs @@ -48,7 +48,7 @@ fn main() { HttpServer::new( || Application::new() - .middleware(middlewares::Logger::default()) // <- logger + .middleware(middleware::Logger::default()) // <- logger .resource("/multipart", |r| r.method(Method::POST).a(index))) .bind("127.0.0.1:8080").unwrap() .start(); diff --git a/examples/state.rs b/examples/state.rs index 0a10b77bd..dfa201f0c 100644 --- a/examples/state.rs +++ b/examples/state.rs @@ -63,7 +63,7 @@ fn main() { HttpServer::new( || Application::with_state(AppState{counter: Cell::new(0)}) // enable logger - .middleware(middlewares::Logger::default()) + .middleware(middleware::Logger::default()) // websocket route .resource( "/ws/", |r| diff --git a/examples/template_tera/src/main.rs b/examples/template_tera/src/main.rs index 86b37e0d6..1925ad4e2 100644 --- a/examples/template_tera/src/main.rs +++ b/examples/template_tera/src/main.rs @@ -35,7 +35,7 @@ fn main() { Application::with_state(State{template: tera}) // enable logger - .middleware(middlewares::Logger::default()) + .middleware(middleware::Logger::default()) .resource("/", |r| r.method(Method::GET).f(index))}) .bind("127.0.0.1:8080").unwrap() .start(); diff --git a/examples/tls/src/main.rs b/examples/tls/src/main.rs index 4ab0cbca2..8dce633e6 100644 --- a/examples/tls/src/main.rs +++ b/examples/tls/src/main.rs @@ -32,7 +32,7 @@ fn main() { HttpServer::new( || Application::new() // enable logger - .middleware(middlewares::Logger::default()) + .middleware(middleware::Logger::default()) // register simple handler, handle all methods .resource("/index.html", |r| r.f(index)) // with path parameters diff --git a/examples/websocket.rs b/examples/websocket.rs index 4b416b541..2a80add1b 100644 --- a/examples/websocket.rs +++ b/examples/websocket.rs @@ -63,7 +63,7 @@ fn main() { HttpServer::new( || Application::new() // enable logger - .middleware(middlewares::Logger::default()) + .middleware(middleware::Logger::default()) // websocket route .resource("/ws/", |r| r.method(Method::GET).f(ws_index)) // static files diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index 435e2966e..89bf8c6d3 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -11,8 +11,8 @@ Typically middlewares involves in following actions: * Modify application state * Access external services (redis, logging, sessions) -Middlewares are registered for each application and get executed in same order as -registraton order. In general, *middleware* is a type that implements +Middlewares are registered for each application and get executed in same order as +registraton order. In general, *middleware* is a type that implements [*Middleware trait*](../actix_web/middlewares/trait.Middleware.html). Each method in this trait has default implementation. Each method can return result immidietly or *future* object. @@ -24,7 +24,7 @@ Here is example of simple middleware that adds request and response headers: # extern crate actix_web; use http::{header, HttpTryFrom}; use actix_web::*; -use actix_web::middlewares::{Middleware, Started, Response}; +use actix_web::middleware::{Middleware, Started, Response}; struct Headers; // <- Our middleware @@ -68,7 +68,7 @@ Logging middleware has to be registered for each application. ### Usage -Create `Logger` middlewares with the specified `format`. +Create `Logger` middleware with the specified `format`. Default `Logger` could be created with `default` method, it uses the default format: ```ignore @@ -77,7 +77,7 @@ Default `Logger` could be created with `default` method, it uses the default for ```rust # extern crate actix_web; use actix_web::Application; -use actix_web::middlewares::Logger; +use actix_web::middleware::Logger; fn main() { Application::new() @@ -90,8 +90,8 @@ fn main() { Here is example of default logging format: ``` -INFO:actix_web::middlewares::logger: 127.0.0.1:59934 [02/Dec/2017:00:21:43 -0800] "GET / HTTP/1.1" 302 0 "-" "curl/7.54.0" 0.000397 -INFO:actix_web::middlewares::logger: 127.0.0.1:59947 [02/Dec/2017:00:22:40 -0800] "GET /index.html HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:57.0) Gecko/20100101 Firefox/57.0" 0.000646 +INFO:actix_web::middleware::logger: 127.0.0.1:59934 [02/Dec/2017:00:21:43 -0800] "GET / HTTP/1.1" 302 0 "-" "curl/7.54.0" 0.000397 +INFO:actix_web::middleware::logger: 127.0.0.1:59947 [02/Dec/2017:00:22:40 -0800] "GET /index.html HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:57.0) Gecko/20100101 Firefox/57.0" 0.000646 ``` ### Format @@ -134,7 +134,7 @@ use actix_web::*; fn main() { let app = Application::new() .middleware( - middlewares::DefaultHeaders::build() + middleware::DefaultHeaders::build() .header("X-Version", "0.2") .finish()) .resource("/test", |r| { @@ -148,14 +148,14 @@ fn main() { ## User sessions Actix provides general solution for session management. -[*Session storage*](../actix_web/middlewares/struct.SessionStorage.html) middleare can be +[*Session storage*](../actix_web/middleware/struct.SessionStorage.html) middleare can be use with different backend types to store session data in different backends. By default only cookie session backend is implemented. Other backend implementations could be added later. -[*Cookie session backend*](../actix_web/middlewares/struct.CookieSessionBackend.html) -uses signed cookies as session storage. *Cookie session backend* creates sessions which -are limited to storing fewer than 4000 bytes of data (as the payload must fit into a +[*Cookie session backend*](../actix_web/middleware/struct.CookieSessionBackend.html) +uses signed cookies as session storage. *Cookie session backend* creates sessions which +are limited to storing fewer than 4000 bytes of data (as the payload must fit into a single cookie). Internal server error get generated if session contains more than 4000 bytes. You need to pass a random value to the constructor of *CookieSessionBackend*. @@ -163,19 +163,19 @@ This is private key for cookie session. When this value is changed, all session Note that whatever you write into your session is visible by the user (but not modifiable). In general case, you cretate -[*Session storage*](../actix_web/middlewares/struct.SessionStorage.html) middleware -and initializes it with specific backend implementation, like *CookieSessionBackend*. +[*Session storage*](../actix_web/middleware/struct.SessionStorage.html) middleware +and initializes it with specific backend implementation, like *CookieSessionBackend*. To access session data -[*HttpRequest::session()*](../actix_web/middlewares/trait.RequestSession.html#tymethod.session) +[*HttpRequest::session()*](../actix_web/middleware/trait.RequestSession.html#tymethod.session) method has to be used. This method returns -[*Session*](../actix_web/middlewares/struct.Session.html) object, which allows to get or set +[*Session*](../actix_web/middleware/struct.Session.html) object, which allows to get or set session data. ```rust # extern crate actix; # extern crate actix_web; use actix_web::*; -use actix_web::middlewares::{RequestSession, SessionStorage, CookieSessionBackend}; +use actix_web::middleware::{RequestSession, SessionStorage, CookieSessionBackend}; fn index(mut req: HttpRequest) -> Result<&'static str> { // access session data @@ -193,11 +193,11 @@ fn main() { # let sys = actix::System::new("basic-example"); HttpServer::new( || Application::new() - .middleware(SessionStorage::new( // <- create session middlewares + .middleware(SessionStorage::new( // <- create session middleware CookieSessionBackend::build(&[0; 32]) // <- create cookie session backend .secure(false) .finish() - ))) + ))) .bind("127.0.0.1:59880").unwrap() .start(); # actix::Arbiter::system().send(actix::msgs::SystemExit(0)); diff --git a/guide/src/qs_3.md b/guide/src/qs_3.md index 40c6dfb95..38383084b 100644 --- a/guide/src/qs_3.md +++ b/guide/src/qs_3.md @@ -62,16 +62,16 @@ All `/app1` requests route to first application, `/app2` to second and then all Application state is shared with all routes and resources within same application. State could be accessed with `HttpRequest::state()` method as a read-only item but interior mutability pattern with `RefCell` could be used to archive state mutability. -State could be accessed with `HttpContext::state()` in case of http actor. +State could be accessed with `HttpContext::state()` in case of http actor. State also available to route matching predicates and middlewares. Let's write simple application that uses shared state. We are going to store requests count -in the state: - +in the state: + ```rust # extern crate actix; # extern crate actix_web; -# +# use actix_web::*; use std::cell::Cell; diff --git a/src/application.rs b/src/application.rs index 1de0ff5b6..21de726e7 100644 --- a/src/application.rs +++ b/src/application.rs @@ -7,7 +7,7 @@ use resource::Resource; use httprequest::HttpRequest; use channel::{HttpHandler, IntoHttpHandler, HttpHandlerTask}; use pipeline::Pipeline; -use middlewares::Middleware; +use middleware::Middleware; use server::ServerSettings; /// Application diff --git a/src/lib.rs b/src/lib.rs index e8e93eb57..2689bc111 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,7 +115,7 @@ pub mod ws; pub mod error; pub mod httpcodes; pub mod multipart; -pub mod middlewares; +pub mod middleware; pub mod pred; pub mod test; pub mod payload; diff --git a/src/middlewares/defaultheaders.rs b/src/middleware/defaultheaders.rs similarity index 97% rename from src/middlewares/defaultheaders.rs rename to src/middleware/defaultheaders.rs index 6968c8978..a013b7a57 100644 --- a/src/middlewares/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -4,7 +4,7 @@ use http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use middlewares::{Response, Middleware}; +use middleware::{Response, Middleware}; /// `Middleware` for setting default response headers. /// @@ -17,7 +17,7 @@ use middlewares::{Response, Middleware}; /// fn main() { /// let app = Application::new() /// .middleware( -/// middlewares::DefaultHeaders::build() +/// middleware::DefaultHeaders::build() /// .header("X-Version", "0.2") /// .finish()) /// .resource("/test", |r| { diff --git a/src/middlewares/logger.rs b/src/middleware/logger.rs similarity index 98% rename from src/middlewares/logger.rs rename to src/middleware/logger.rs index 507908148..e498ad4c9 100644 --- a/src/middlewares/logger.rs +++ b/src/middleware/logger.rs @@ -9,13 +9,13 @@ use regex::Regex; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use middlewares::{Middleware, Started, Finished}; +use middleware::{Middleware, Started, Finished}; /// `Middleware` for logging request and response info to the terminal. /// /// ## Usage /// -/// Create `Logger` middlewares with the specified `format`. +/// Create `Logger` middleware with the specified `format`. /// Default `Logger` could be created with `default` method, it uses the default format: /// /// ```ignore @@ -24,7 +24,7 @@ use middlewares::{Middleware, Started, Finished}; /// ```rust /// # extern crate actix_web; /// use actix_web::Application; -/// use actix_web::middlewares::Logger; +/// use actix_web::middleware::Logger; /// /// fn main() { /// let app = Application::new() diff --git a/src/middlewares/mod.rs b/src/middleware/mod.rs similarity index 100% rename from src/middlewares/mod.rs rename to src/middleware/mod.rs diff --git a/src/middlewares/session.rs b/src/middleware/session.rs similarity index 97% rename from src/middlewares/session.rs rename to src/middleware/session.rs index 6edba1983..8ac068888 100644 --- a/src/middlewares/session.rs +++ b/src/middleware/session.rs @@ -17,13 +17,13 @@ use futures::future::{FutureResult, ok as FutOk, err as FutErr}; use error::{Result, Error, ResponseError}; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use middlewares::{Middleware, Started, Response}; +use middleware::{Middleware, Started, Response}; /// The helper trait to obtain your session data from a request. /// /// ```rust /// use actix_web::*; -/// use actix_web::middlewares::RequestSession; +/// use actix_web::middleware::RequestSession; /// /// fn index(mut req: HttpRequest) -> Result<&'static str> { /// // access session data @@ -62,7 +62,7 @@ impl RequestSession for HttpRequest { /// /// ```rust /// use actix_web::*; -/// use actix_web::middlewares::RequestSession; +/// use actix_web::middleware::RequestSession; /// /// fn index(mut req: HttpRequest) -> Result<&'static str> { /// // access session data @@ -118,12 +118,12 @@ unsafe impl Sync for SessionImplBox {} /// ```rust /// # extern crate actix; /// # extern crate actix_web; -/// # use actix_web::middlewares::{SessionStorage, CookieSessionBackend}; +/// # use actix_web::middleware::{SessionStorage, CookieSessionBackend}; /// use actix_web::*; /// /// fn main() { /// let app = Application::new().middleware( -/// SessionStorage::new( // <- create session middlewares +/// SessionStorage::new( // <- create session middleware /// CookieSessionBackend::build(&[0; 32]) // <- create cookie session backend /// .secure(false) /// .finish()) @@ -358,7 +358,7 @@ impl CookieSessionBackend { /// # Example /// /// ``` - /// use actix_web::middlewares::CookieSessionBackend; + /// use actix_web::middleware::CookieSessionBackend; /// /// let backend = CookieSessionBackend::build(&[0; 32]).finish(); /// ``` @@ -396,7 +396,7 @@ impl SessionBackend for CookieSessionBackend { /// ```rust /// # extern crate actix_web; /// -/// use actix_web::middlewares::CookieSessionBackend; +/// use actix_web::middleware::CookieSessionBackend; /// /// # fn main() { /// let backend: CookieSessionBackend = CookieSessionBackend::build(&[0; 32]) diff --git a/src/pipeline.rs b/src/pipeline.rs index cc17d2f33..d4a8edd84 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -13,7 +13,7 @@ use handler::{Reply, ReplyItem}; use h1writer::{Writer, WriterState}; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use middlewares::{Middleware, Finished, Started, Response}; +use middleware::{Middleware, Finished, Started, Response}; type Handler = FnMut(HttpRequest) -> Reply; pub(crate) type PipelineHandler<'a, S> = &'a mut FnMut(HttpRequest) -> Reply; diff --git a/src/test/mod.rs b/src/test/mod.rs index 0d3c596f3..1d388eda8 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -18,7 +18,7 @@ use error::Error; use server::HttpServer; use handler::{Handler, Responder, ReplyItem}; use channel::{HttpHandler, IntoHttpHandler}; -use middlewares::Middleware; +use middleware::Middleware; use application::{Application, HttpApplication}; use param::Params; use router::Router; diff --git a/tests/test_server.rs b/tests/test_server.rs index d148e9ef8..c5e166713 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -27,20 +27,20 @@ struct MiddlewareTest { finish: Arc, } -impl middlewares::Middleware for MiddlewareTest { - fn start(&self, _: &mut HttpRequest) -> middlewares::Started { +impl middleware::Middleware for MiddlewareTest { + fn start(&self, _: &mut HttpRequest) -> middleware::Started { self.start.store(self.start.load(Ordering::Relaxed) + 1, Ordering::Relaxed); - middlewares::Started::Done + middleware::Started::Done } - fn response(&self, _: &mut HttpRequest, resp: HttpResponse) -> middlewares::Response { + fn response(&self, _: &mut HttpRequest, resp: HttpResponse) -> middleware::Response { self.response.store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed); - middlewares::Response::Done(resp) + middleware::Response::Done(resp) } - fn finish(&self, _: &mut HttpRequest, _: &HttpResponse) -> middlewares::Finished { + fn finish(&self, _: &mut HttpRequest, _: &HttpResponse) -> middleware::Finished { self.finish.store(self.finish.load(Ordering::Relaxed) + 1, Ordering::Relaxed); - middlewares::Finished::Done + middleware::Finished::Done } } From 3abd0db6b195bdbcb8313d318e383de92ee7195e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Dec 2017 20:07:31 -0800 Subject: [PATCH 196/279] restore server start test --- src/{test/mod.rs => test.rs} | 10 ++++++++-- tests/test_server.rs | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) rename src/{test/mod.rs => test.rs} (97%) diff --git a/src/test/mod.rs b/src/test.rs similarity index 97% rename from src/test/mod.rs rename to src/test.rs index 1d388eda8..dc834a59e 100644 --- a/src/test/mod.rs +++ b/src/test.rs @@ -81,7 +81,7 @@ impl TestServer { // run server in separate thread let join = thread::spawn(move || { let sys = System::new("actix-test-server"); - let tcp = net::TcpListener::bind("0.0.0.0:0").unwrap(); + let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); let local_addr = tcp.local_addr().unwrap(); let tcp = TcpListener::from_listener(tcp, &local_addr, Arbiter::handle()).unwrap(); @@ -113,7 +113,7 @@ impl TestServer { let join = thread::spawn(move || { let sys = System::new("actix-test-server"); - let tcp = net::TcpListener::bind("0.0.0.0:0").unwrap(); + let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); let local_addr = tcp.local_addr().unwrap(); let tcp = TcpListener::from_listener(tcp, &local_addr, Arbiter::handle()).unwrap(); @@ -135,6 +135,12 @@ impl TestServer { } } + /// Get firat available unused address + pub fn unused_addr() -> net::SocketAddr { + let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); + tcp.local_addr().unwrap() + } + /// Construct test server url pub fn url(&self, uri: &str) -> String { if uri.starts_with('/') { diff --git a/tests/test_server.rs b/tests/test_server.rs index c5e166713..7605847ca 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -3,10 +3,27 @@ extern crate actix_web; extern crate tokio_core; extern crate reqwest; +use std::thread; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; use actix_web::*; +use actix::System; + +#[test] +fn test_start() { + let addr = test::TestServer::unused_addr(); + let srv_addr = addr.clone(); + thread::spawn(move || { + let sys = System::new("test"); + let srv = HttpServer::new( + || vec![Application::new() + .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))]); + srv.bind(srv_addr).unwrap().start(); + sys.run(); + }); + assert!(reqwest::get(&format!("http://{}/", addr)).unwrap().status().is_success()); +} #[test] fn test_simple() { From 183bcd38f8bec0d536005ccec34a524cc2d53b1b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Dec 2017 20:52:21 -0800 Subject: [PATCH 197/279] modify unused_addr method; update websockt guide section --- guide/src/SUMMARY.md | 2 +- guide/src/qs_9.md | 47 +++++++++++++++++++++++++++++++++++++++++++- src/test.rs | 7 ++++++- src/ws.rs | 30 +++++++++++++--------------- 4 files changed, 67 insertions(+), 19 deletions(-) diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 8e78a0c3e..d76840f9c 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -9,8 +9,8 @@ - [URL Dispatch](./qs_5.md) - [Request & Response](./qs_7.md) - [Testing](./qs_8.md) -- [WebSockets](./qs_9.md) - [Middlewares](./qs_10.md) - [Static file handling](./qs_12.md) +- [WebSockets](./qs_9.md) - [HTTP/2](./qs_13.md) - [Database integration](./qs_14.md) diff --git a/guide/src/qs_9.md b/guide/src/qs_9.md index cde41c746..4f0b38cc8 100644 --- a/guide/src/qs_9.md +++ b/guide/src/qs_9.md @@ -1,4 +1,49 @@ # WebSockets -[WIP] +Actix supports WebSockets out-of-the-box. It is possible to convert request's `Payload` +to a stream of [*ws::Message*](../actix_web/ws/enum.Message.html) with +a [*ws::WsStream*](../actix_web/ws/struct.WsStream.html) and then use stream +combinators to handle actual messages. But it is simplier to handle websocket communication +with http actor. +```rust +extern crate actix; +extern crate actix_web; + +use actix::*; +use actix_web::*; + +/// Define http actor +struct Ws; + +impl Actor for Ws { + type Context = HttpContext; +} + +/// Define Handler for ws::Message message +# impl StreamHandler for WsRoute {} +impl Handler for WsRoute { + fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext) -> Response + { + match msg { + ws::Message::Ping(msg) => ws::WsWriter::pong(ctx, &msg), + ws::Message::Text(text) => ws::WsWriter::text(ctx, &text), + ws::Message::Binary(bin) => ws::WsWriter::binary(ctx, bin), + _ => (), + } + Self::empty() + } +} + +fn main() { + Application::new() + .resource("/ws/", |r| r.f(|req| ws::start(req, WS)) // <- register websocket route + .finish(); +} +``` + +Simple websocket echo server example is available in +[examples directory](https://github.com/actix/actix-web/blob/master/examples/websocket.rs). + +Example chat server with ability to chat over websocket connection or tcp connection +is available in [websocket-chat directory](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/) diff --git a/src/test.rs b/src/test.rs index dc834a59e..333f216b9 100644 --- a/src/test.rs +++ b/src/test.rs @@ -11,6 +11,7 @@ use cookie::Cookie; use http::{Uri, Method, Version, HeaderMap, HttpTryFrom}; use http::header::{HeaderName, HeaderValue}; use futures::Future; +use socket2::{Socket, Domain, Type}; use tokio_core::net::TcpListener; use tokio_core::reactor::Core; @@ -137,7 +138,11 @@ impl TestServer { /// Get firat available unused address pub fn unused_addr() -> net::SocketAddr { - let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); + let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); + let socket = Socket::new(Domain::ipv4(), Type::stream(), None).unwrap(); + socket.bind(&addr.into()).unwrap(); + socket.set_reuse_address(true).unwrap(); + let tcp = socket.into_tcp_listener(); tcp.local_addr().unwrap() } diff --git a/src/ws.rs b/src/ws.rs index 097ec7997..5832e5c29 100644 --- a/src/ws.rs +++ b/src/ws.rs @@ -6,28 +6,26 @@ //! ## Example //! //! ```rust -//! extern crate actix; -//! extern crate actix_web; -//! +//! # extern crate actix; +//! # extern crate actix_web; //! use actix::*; //! use actix_web::*; //! //! // do websocket handshake and start actor //! fn ws_index(req: HttpRequest) -> Result { -//! ws::start(req, WsRoute) +//! ws::start(req, Ws) //! } //! -//! // WebSocket Route -//! struct WsRoute; +//! struct Ws; //! -//! impl Actor for WsRoute { +//! impl Actor for Ws { //! type Context = HttpContext; //! } //! //! // Define Handler for ws::Message message -//! impl StreamHandler for WsRoute {} -//! -//! impl Handler for WsRoute { +//! # impl StreamHandler for Ws {} +//! # +//! impl Handler for Ws { //! fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext) //! -> Response //! { @@ -40,12 +38,12 @@ //! Self::empty() //! } //! } -//! -//! fn main() { -//! Application::new() -//! .resource("/ws/", |r| r.method(Method::GET).f(ws_index)) // <- register websocket route -//! .finish(); -//! } +//! # +//! # fn main() { +//! # Application::new() +//! # .resource("/ws/", |r| r.f(ws_index)) // <- register websocket route +//! # .finish(); +//! # } //! ``` use std::vec::Vec; use http::{Method, StatusCode, header}; From 0d21c2da220575306f52fe5d728cc41d5d228bfb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Dec 2017 21:07:51 -0800 Subject: [PATCH 198/279] various typos --- guide/src/qs_14.md | 8 ++++---- guide/src/qs_4.md | 2 +- guide/src/qs_9.md | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/guide/src/qs_14.md b/guide/src/qs_14.md index 6c6fd1aaa..e9e489be7 100644 --- a/guide/src/qs_14.md +++ b/guide/src/qs_14.md @@ -31,7 +31,7 @@ struct CreateUser { } ``` -We can send `CreateUser` message to `DbExecutor` actor, and as result we can get +We can send `CreateUser` message to `DbExecutor` actor, and as result we get `User` model. Now we need to define actual handler implementation for this message. ```rust,ignore @@ -69,7 +69,7 @@ All we need is to start *DbExecutor* actors and store address in a state where h can access it. ```rust,ignore -/// This is state where we store *DbExecutor* address. +/// This is state where we will store *DbExecutor* address. struct State { db: SyncAddress, } @@ -77,7 +77,7 @@ struct State { fn main() { let sys = actix::System::new("diesel-example"); - // Start 3 db executors + // Start 3 parallele db executors let addr = SyncArbiter::start(3, || { DbExecutor(SqliteConnection::establish("test.db").unwrap()) }); @@ -94,7 +94,7 @@ fn main() { } ``` -And finally we can use this state in a requst handler. We get message response +And finally we can use address in a requst handler. We get message response asynchronously, so handler needs to return future object, also `Route::a()` needs to be used for async handler registration. diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index e44483d07..1a82d51bd 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -122,7 +122,7 @@ from multiple threads consider using [actix](https://actix.github.io/actix/actix ## Response with custom type -To return custom type directly from handler function type needs to implement `Responder` trait. +To return custom type directly from handler function, type needs to implement `Responder` trait. Let's create response for custom type that serializes to `application/json` response: ```rust diff --git a/guide/src/qs_9.md b/guide/src/qs_9.md index 4f0b38cc8..aa8bfd5f6 100644 --- a/guide/src/qs_9.md +++ b/guide/src/qs_9.md @@ -3,7 +3,7 @@ Actix supports WebSockets out-of-the-box. It is possible to convert request's `Payload` to a stream of [*ws::Message*](../actix_web/ws/enum.Message.html) with a [*ws::WsStream*](../actix_web/ws/struct.WsStream.html) and then use stream -combinators to handle actual messages. But it is simplier to handle websocket communication +combinators to handle actual messages. But it is simplier to handle websocket communications with http actor. ```rust From 5df5cc7374d451e0577c38a34ea819eaf921d5a9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Dec 2017 21:33:23 -0800 Subject: [PATCH 199/279] fix guide example --- .travis.yml | 8 +++++++- guide/src/qs_9.md | 6 +++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 67d27b917..6251bcedb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,13 @@ before_script: - export PATH=$PATH:~/.cargo/bin script: - - USE_SKEPTIC=1 cargo test --features=alpn + - | + if [[ "$TRAVIS_RUST_VERSION" == "nightly-2017-12-21" ]]; then + USE_SKEPTIC=1 cargo test --features=alpn + else + cargo test --features=alpn + fi + - | if [[ "$TRAVIS_RUST_VERSION" == "beta" ]]; then cd examples/diesel && cargo check && cd ../.. diff --git a/guide/src/qs_9.md b/guide/src/qs_9.md index aa8bfd5f6..7feb7d94b 100644 --- a/guide/src/qs_9.md +++ b/guide/src/qs_9.md @@ -21,8 +21,8 @@ impl Actor for Ws { } /// Define Handler for ws::Message message -# impl StreamHandler for WsRoute {} -impl Handler for WsRoute { +# impl StreamHandler for Ws {} +impl Handler for Ws { fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext) -> Response { match msg { @@ -37,7 +37,7 @@ impl Handler for WsRoute { fn main() { Application::new() - .resource("/ws/", |r| r.f(|req| ws::start(req, WS)) // <- register websocket route + .resource("/ws/", |r| r.f(|req| ws::start(req, Ws))) // <- register websocket route .finish(); } ``` From e1fb32c6e5e72f3c0e688c35adc4101df15ca698 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 27 Dec 2017 07:36:24 -0800 Subject: [PATCH 200/279] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6251bcedb..325fa879f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ before_script: script: - | - if [[ "$TRAVIS_RUST_VERSION" == "nightly-2017-12-21" ]]; then + if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then USE_SKEPTIC=1 cargo test --features=alpn else cargo test --features=alpn From be1cd2936d3d3cff20208c6376289c09b6ccfbca Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 27 Dec 2017 09:49:59 -0800 Subject: [PATCH 201/279] check example in 1.20 --- .travis.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 325fa879f..242769d4e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,12 +35,15 @@ script: cargo test --features=alpn fi + - | + if [[ "$TRAVIS_RUST_VERSION" == "1.20.0" ]]; then + cd examples/multipart && cargo check && cd ../.. + cd examples/json && cargo check && cd ../.. + cd examples/template_tera && cargo check && cd ../.. + fi - | if [[ "$TRAVIS_RUST_VERSION" == "beta" ]]; then cd examples/diesel && cargo check && cd ../.. - cd examples/multipart && cargo check && cd ../.. - cd examples/json && cargo check && cd ../.. - cd examples/template_tera && cargo check && cd ../.. cd examples/tls && cargo check && cd ../.. cd examples/websocket-chat && cargo check && cd ../.. fi From da8aa8b988fd5dec1b3fddbd09406a78f0304751 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 27 Dec 2017 11:22:27 -0800 Subject: [PATCH 202/279] use mio for accept loop --- Cargo.toml | 5 +- src/lib.rs | 3 +- src/server.rs | 151 ++++++++++++++++++++++++++----------------- src/test.rs | 10 +-- tests/test_server.rs | 12 ++-- 5 files changed, 109 insertions(+), 72 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e6190d3f1..43c286f71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,6 @@ regex = "0.2" sha1 = "0.2" url = "1.5" libc = "0.2" -socket2 = "0.2" serde = "1.0" serde_json = "1.0" flate2 = "0.2" @@ -57,7 +56,9 @@ bitflags = "1.0" num_cpus = "1.0" cookie = { version="0.10", features=["percent-encode", "secure"] } -# tokio +# io +mio = "0.6" +net2 = "0.2" bytes = "0.4" futures = "0.1" tokio-io = "0.1" diff --git a/src/lib.rs b/src/lib.rs index 2689bc111..ec2dafa84 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,6 +50,8 @@ extern crate bitflags; extern crate futures; extern crate tokio_io; extern crate tokio_core; +extern crate mio; +extern crate net2; extern crate failure; #[macro_use] extern crate failure_derive; @@ -69,7 +71,6 @@ extern crate brotli2; extern crate percent_encoding; extern crate smallvec; extern crate num_cpus; -extern crate socket2; extern crate actix; extern crate h2 as http2; diff --git a/src/server.rs b/src/server.rs index 399115076..082fb5dec 100644 --- a/src/server.rs +++ b/src/server.rs @@ -10,9 +10,11 @@ use actix::dev::*; use futures::Stream; use futures::sync::mpsc; use tokio_io::{AsyncRead, AsyncWrite}; +use tokio_core::reactor::Handle; use tokio_core::net::TcpStream; +use mio; use num_cpus; -use socket2::{Socket, Domain, Type}; +use net2::{TcpBuilder, TcpStreamExt}; #[cfg(feature="tls")] use futures::{future, Future}; @@ -103,7 +105,7 @@ pub struct HttpServer keep_alive: Option, factory: Arc U + Send + Sync>, workers: Vec>>, - sockets: HashMap, + sockets: HashMap, } impl Actor for HttpServer { @@ -160,6 +162,8 @@ impl HttpServer /// attempting to connect. It should only affect servers under significant load. /// /// Generally set in the 64-2048 range. Default value is 2048. + /// + /// This method should be called before `bind()` method call. pub fn backlog(mut self, num: i32) -> Self { self.backlog = num; self @@ -202,34 +206,22 @@ impl HttpServer let mut succ = false; if let Ok(iter) = addr.to_socket_addrs() { for addr in iter { - let socket = match addr { - net::SocketAddr::V4(a) => { - let socket = Socket::new(Domain::ipv4(), Type::stream(), None)?; - match socket.bind(&a.into()) { - Ok(_) => socket, - Err(e) => { - err = Some(e); - continue; - } - } - } - net::SocketAddr::V6(a) => { - let socket = Socket::new(Domain::ipv6(), Type::stream(), None)?; - match socket.bind(&a.into()) { - Ok(_) => socket, - Err(e) => { - err = Some(e); - continue - } - } - } + let builder = match addr { + net::SocketAddr::V4(_) => TcpBuilder::new_v4()?, + net::SocketAddr::V6(_) => TcpBuilder::new_v6()?, }; - succ = true; - socket.listen(self.backlog) - .expect("failed to set socket backlog"); - socket.set_reuse_address(true) - .expect("failed to set socket reuse address"); - self.sockets.insert(addr, socket); + match builder.bind(addr) { + Ok(builder) => match builder.reuse_address(true) { + Ok(builder) => { + succ = true; + let lst = builder.listen(self.backlog) + .expect("failed to set socket backlog"); + self.sockets.insert(lst.local_addr().unwrap(), lst); + }, + Err(e) => err = Some(e) + }, + Err(e) => err = Some(e), + } } } @@ -245,13 +237,13 @@ impl HttpServer } fn start_workers(&mut self, settings: &ServerSettings, handler: &StreamHandlerType) - -> Vec>> + -> Vec>> { // start workers let mut workers = Vec::new(); for _ in 0..self.threads { let s = settings.clone(); - let (tx, rx) = mpsc::unbounded::>(); + let (tx, rx) = mpsc::unbounded::>(); let h = handler.clone(); let ka = self.keep_alive; @@ -309,7 +301,8 @@ impl HttpServer if self.sockets.is_empty() { panic!("HttpServer::bind() has to be called befor start()"); } else { - let addrs: Vec<(net::SocketAddr, Socket)> = self.sockets.drain().collect(); + let addrs: Vec<(net::SocketAddr, net::TcpListener)> = + self.sockets.drain().collect(); let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false); let workers = self.start_workers(&settings, &StreamHandlerType::Normal); @@ -413,7 +406,8 @@ impl HttpServer where S: Stream + 'static { if !self.sockets.is_empty() { - let addrs: Vec<(net::SocketAddr, Socket)> = self.sockets.drain().collect(); + let addrs: Vec<(net::SocketAddr, net::TcpListener)> = + self.sockets.drain().collect(); let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false); let workers = self.start_workers(&settings, &StreamHandlerType::Normal); @@ -484,6 +478,7 @@ impl Handler, io::Error> for HttpServer /// Worker accepts Socket objects via unbounded channel and start requests processing. struct Worker { h: Rc>, + hnd: Handle, handler: StreamHandlerType, } @@ -528,6 +523,7 @@ impl Worker { fn new(h: Vec, handler: StreamHandlerType, keep_alive: Option) -> Worker { Worker { h: Rc::new(WorkerSettings::new(h, keep_alive)), + hnd: Arbiter::handle().clone(), handler: handler, } } @@ -546,21 +542,21 @@ impl Actor for Worker { } } -impl StreamHandler> for Worker +impl StreamHandler> for Worker where H: HttpHandler + 'static {} -impl Handler> for Worker +impl Handler> for Worker where H: HttpHandler + 'static, { - fn handle(&mut self, msg: IoStream, _: &mut Context) - -> Response> + fn handle(&mut self, msg: IoStream, _: &mut Context) + -> Response> { if !self.h.keep_alive_enabled() && msg.io.set_keepalive(Some(Duration::new(75, 0))).is_err() { error!("Can not set socket keep-alive option"); } - self.handler.handle(Rc::clone(&self.h), msg); + self.handler.handle(Rc::clone(&self.h), &self.hnd, msg); Self::empty() } } @@ -576,25 +572,27 @@ enum StreamHandlerType { impl StreamHandlerType { - fn handle(&mut self, h: Rc>, msg: IoStream) { + fn handle(&mut self, + h: Rc>, + hnd: &Handle, + msg: IoStream) { match *self { StreamHandlerType::Normal => { - let io = TcpStream::from_stream(msg.io.into_tcp_stream(), Arbiter::handle()) + let io = TcpStream::from_stream(msg.io, hnd) .expect("failed to associate TCP stream"); - Arbiter::handle().spawn(HttpChannel::new(h, io, msg.peer, msg.http2)); + hnd.spawn(HttpChannel::new(h, io, msg.peer, msg.http2)); } #[cfg(feature="tls")] StreamHandlerType::Tls(ref acceptor) => { let IoStream { io, peer, http2 } = msg; - let io = TcpStream::from_stream(io.into_tcp_stream(), Arbiter::handle()) + let io = TcpStream::from_stream(io, hnd) .expect("failed to associate TCP stream"); Arbiter::handle().spawn( TlsAcceptorExt::accept_async(acceptor, io).then(move |res| { match res { - Ok(io) => Arbiter::handle().spawn( - HttpChannel::new(h, io, peer, http2)), + Ok(io) => hnd.spawn(HttpChannel::new(h, io, peer, http2)), Err(err) => trace!("Error during handling tls connection: {}", err), }; @@ -605,10 +603,10 @@ impl StreamHandlerType { #[cfg(feature="alpn")] StreamHandlerType::Alpn(ref acceptor) => { let IoStream { io, peer, .. } = msg; - let io = TcpStream::from_stream(io.into_tcp_stream(), Arbiter::handle()) + let io = TcpStream::from_stream(io, hnd) .expect("failed to associate TCP stream"); - Arbiter::handle().spawn( + hnd.spawn( SslAcceptorExt::accept_async(acceptor, io).then(move |res| { match res { Ok(io) => { @@ -631,24 +629,57 @@ impl StreamHandlerType { } } -fn start_accept_thread(sock: Socket, addr: net::SocketAddr, - workers: Vec>>) { - // start acceptors thread +fn start_accept_thread(sock: net::TcpListener, addr: net::SocketAddr, + workers: Vec>>) { + // start accept thread let _ = thread::Builder::new().name(format!("Accept on {}", addr)).spawn(move || { let mut next = 0; + let server = mio::net::TcpListener::from_listener(sock, &addr) + .expect("Can not create mio::net::TcpListener"); + const SERVER: mio::Token = mio::Token(0); + + // Create a poll instance + let poll = match mio::Poll::new() { + Ok(poll) => poll, + Err(err) => panic!("Can not create mio::Poll: {}", err), + }; + + // Start listening for incoming connections + if let Err(err) = poll.register(&server, SERVER, + mio::Ready::readable(), mio::PollOpt::edge()) { + panic!("Can not register io: {}", err); + } + + // Create storage for events + let mut events = mio::Events::with_capacity(128); + loop { - match sock.accept() { - Ok((socket, addr)) => { - let addr = if let Some(addr) = addr.as_inet() { - net::SocketAddr::V4(addr) - } else { - net::SocketAddr::V6(addr.as_inet6().unwrap()) - }; - let msg = IoStream{io: socket, peer: Some(addr), http2: false}; - workers[next].unbounded_send(msg).expect("worker thread died"); - next = (next + 1) % workers.len(); + if let Err(err) = poll.poll(&mut events, None) { + panic!("Poll error: {}", err); + } + + for event in events.iter() { + match event.token() { + SERVER => { + loop { + match server.accept_std() { + Ok((sock, addr)) => { + let msg = IoStream{io: sock, peer: Some(addr), http2: false}; + workers[next] + .unbounded_send(msg).expect("worker thread died"); + next = (next + 1) % workers.len(); + }, + Err(err) => if err.kind() == io::ErrorKind::WouldBlock { + break + } else { + error!("Error accepting connection: {:?}", err); + return + } + } + } + } + _ => unreachable!(), } - Err(err) => error!("Error accepting connection: {:?}", err), } } }); diff --git a/src/test.rs b/src/test.rs index 333f216b9..1ff954e76 100644 --- a/src/test.rs +++ b/src/test.rs @@ -11,9 +11,9 @@ use cookie::Cookie; use http::{Uri, Method, Version, HeaderMap, HttpTryFrom}; use http::header::{HeaderName, HeaderValue}; use futures::Future; -use socket2::{Socket, Domain, Type}; use tokio_core::net::TcpListener; use tokio_core::reactor::Core; +use net2::TcpBuilder; use error::Error; use server::HttpServer; @@ -139,10 +139,10 @@ impl TestServer { /// Get firat available unused address pub fn unused_addr() -> net::SocketAddr { let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); - let socket = Socket::new(Domain::ipv4(), Type::stream(), None).unwrap(); - socket.bind(&addr.into()).unwrap(); - socket.set_reuse_address(true).unwrap(); - let tcp = socket.into_tcp_listener(); + let socket = TcpBuilder::new_v4().unwrap(); + socket.bind(&addr).unwrap(); + socket.reuse_address(true).unwrap(); + let tcp = socket.to_tcp_listener().unwrap(); tcp.local_addr().unwrap() } diff --git a/tests/test_server.rs b/tests/test_server.rs index 7605847ca..6884d30f1 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -4,7 +4,7 @@ extern crate tokio_core; extern crate reqwest; use std::thread; -use std::sync::Arc; +use std::sync::{Arc, mpsc}; use std::sync::atomic::{AtomicUsize, Ordering}; use actix_web::*; @@ -12,16 +12,20 @@ use actix::System; #[test] fn test_start() { - let addr = test::TestServer::unused_addr(); - let srv_addr = addr.clone(); + let _ = test::TestServer::unused_addr(); + let (tx, rx) = mpsc::channel(); + thread::spawn(move || { let sys = System::new("test"); let srv = HttpServer::new( || vec![Application::new() .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))]); - srv.bind(srv_addr).unwrap().start(); + let srv = srv.bind("127.0.0.1:0").unwrap(); + let _ = tx.send(srv.addrs()[0].clone()); + srv.start(); sys.run(); }); + let addr = rx.recv().unwrap(); assert!(reqwest::get(&format!("http://{}/", addr)).unwrap().status().is_success()); } From 0589f2ee495a31654c8d641390ce220ab0818655 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 27 Dec 2017 12:58:32 -0800 Subject: [PATCH 203/279] add server management commands --- Cargo.toml | 2 +- src/lib.rs | 5 +- src/server.rs | 226 +++++++++++++++++++++++++++++++++---------- tests/test_server.rs | 20 +++- 4 files changed, 196 insertions(+), 57 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 43c286f71..2eb523183 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,7 @@ tokio-tls = { version="0.1", optional = true } tokio-openssl = { version="0.1", optional = true } [dependencies.actix] -version = "^0.3.4" +version = "^0.3.5" default-features = false features = [] diff --git a/src/lib.rs b/src/lib.rs index ec2dafa84..f0178178d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,8 +71,8 @@ extern crate brotli2; extern crate percent_encoding; extern crate smallvec; extern crate num_cpus; -extern crate actix; extern crate h2 as http2; +#[macro_use] extern crate actix; #[cfg(test)] #[macro_use] extern crate serde_derive; @@ -173,7 +173,8 @@ pub mod dev { pub use pipeline::Pipeline; pub use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; pub use param::{FromParam, Params}; - pub use server::ServerSettings; pub use httprequest::UrlEncoded; pub use httpresponse::HttpResponseBuilder; + + pub use server::{ServerSettings, PauseServer, ResumeServer, StopServer}; } diff --git a/src/server.rs b/src/server.rs index 082fb5dec..ff0aa1dee 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,7 +1,7 @@ use std::{io, net, thread}; use std::rc::Rc; use std::cell::{RefCell, RefMut}; -use std::sync::Arc; +use std::sync::{Arc, mpsc as sync_mpsc}; use std::time::Duration; use std::marker::PhantomData; use std::collections::HashMap; @@ -106,6 +106,7 @@ pub struct HttpServer factory: Arc U + Send + Sync>, workers: Vec>>, sockets: HashMap, + accept: Vec<(mio::SetReadiness, sync_mpsc::Sender)>, } impl Actor for HttpServer { @@ -144,6 +145,7 @@ impl HttpServer factory: Arc::new(factory), workers: Vec::new(), sockets: HashMap::new(), + accept: Vec::new(), } } @@ -206,19 +208,10 @@ impl HttpServer let mut succ = false; if let Ok(iter) = addr.to_socket_addrs() { for addr in iter { - let builder = match addr { - net::SocketAddr::V4(_) => TcpBuilder::new_v4()?, - net::SocketAddr::V6(_) => TcpBuilder::new_v6()?, - }; - match builder.bind(addr) { - Ok(builder) => match builder.reuse_address(true) { - Ok(builder) => { - succ = true; - let lst = builder.listen(self.backlog) - .expect("failed to set socket backlog"); - self.sockets.insert(lst.local_addr().unwrap(), lst); - }, - Err(e) => err = Some(e) + match create_tcp_listener(addr, self.backlog) { + Ok(lst) => { + succ = true; + self.sockets.insert(lst.local_addr().unwrap(), lst); }, Err(e) => err = Some(e), } @@ -309,7 +302,8 @@ impl HttpServer // start acceptors threads for (addr, sock) in addrs { info!("Starting http server on {}", addr); - start_accept_thread(sock, addr, workers.clone()); + self.accept.push( + start_accept_thread(sock, addr, self.backlog, workers.clone())); } // start http server actor @@ -328,7 +322,7 @@ impl HttpServer, net::SocketAddr, H, if self.sockets.is_empty() { Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound")) } else { - let addrs: Vec<(net::SocketAddr, Socket)> = self.sockets.drain().collect(); + let addrs: Vec<(net::SocketAddr, net::TcpListener)> = self.sockets.drain().collect(); let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false); let acceptor = match TlsAcceptor::builder(pkcs12) { Ok(builder) => { @@ -344,7 +338,8 @@ impl HttpServer, net::SocketAddr, H, // start acceptors threads for (addr, sock) in addrs { info!("Starting tls http server on {}", addr); - start_accept_thread(sock, addr, workers.clone()); + self.accept.push( + start_accept_thread(sock, addr, self.backlog, workers.clone())); } // start http server actor @@ -365,7 +360,7 @@ impl HttpServer, net::SocketAddr, H, if self.sockets.is_empty() { Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound")) } else { - let addrs: Vec<(net::SocketAddr, Socket)> = self.sockets.drain().collect(); + let addrs: Vec<(net::SocketAddr, net::TcpListener)> = self.sockets.drain().collect(); let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false); let acceptor = match SslAcceptorBuilder::mozilla_intermediate( SslMethod::tls(), &identity.pkey, &identity.cert, &identity.chain) @@ -383,7 +378,8 @@ impl HttpServer, net::SocketAddr, H, // start acceptors threads for (addr, sock) in addrs { info!("Starting tls http server on {}", addr); - start_accept_thread(sock, addr, workers.clone()); + self.accept.push( + start_accept_thread(sock, addr, workers.clone(), self.backlog)); } // start http server actor @@ -414,7 +410,8 @@ impl HttpServer // start acceptors threads for (addr, sock) in addrs { info!("Starting http server on {}", addr); - start_accept_thread(sock, addr, workers.clone()); + self.accept.push( + start_accept_thread(sock, addr, self.backlog, workers.clone())); } } @@ -436,18 +433,13 @@ impl HttpServer } } +#[derive(Message)] struct IoStream { io: T, peer: Option, http2: bool, } -impl ResponseType for IoStream -{ - type Item = (); - type Error = (); -} - impl StreamHandler, io::Error> for HttpServer where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static, @@ -473,6 +465,67 @@ impl Handler, io::Error> for HttpServer } } +/// Pause connection accepting process +#[derive(Message)] +pub struct PauseServer; + +/// Resume connection accepting process +#[derive(Message)] +pub struct ResumeServer; + +/// Stop connection processing and exit +#[derive(Message)] +pub struct StopServer; + +impl Handler for HttpServer + where T: AsyncRead + AsyncWrite + 'static, + H: HttpHandler + 'static, + U: 'static, + A: 'static, +{ + fn handle(&mut self, _: PauseServer, _: &mut Context) -> Response + { + for item in &self.accept { + let _ = item.1.send(Command::Pause); + let _ = item.0.set_readiness(mio::Ready::readable()); + } + Self::empty() + } +} + +impl Handler for HttpServer + where T: AsyncRead + AsyncWrite + 'static, + H: HttpHandler + 'static, + U: 'static, + A: 'static, +{ + fn handle(&mut self, _: ResumeServer, _: &mut Context) -> Response + { + for item in &self.accept { + let _ = item.1.send(Command::Resume); + let _ = item.0.set_readiness(mio::Ready::readable()); + } + Self::empty() + } +} + +impl Handler for HttpServer + where T: AsyncRead + AsyncWrite + 'static, + H: HttpHandler + 'static, + U: 'static, + A: 'static, +{ + fn handle(&mut self, _: StopServer, ctx: &mut Context) -> Response + { + for item in &self.accept { + let _ = item.1.send(Command::Stop); + let _ = item.0.set_readiness(mio::Ready::readable()); + } + ctx.stop(); + Self::empty() + } +} + /// Http worker /// /// Worker accepts Socket objects via unbounded channel and start requests processing. @@ -589,10 +642,11 @@ impl StreamHandlerType { let io = TcpStream::from_stream(io, hnd) .expect("failed to associate TCP stream"); - Arbiter::handle().spawn( + hnd.spawn( TlsAcceptorExt::accept_async(acceptor, io).then(move |res| { match res { - Ok(io) => hnd.spawn(HttpChannel::new(h, io, peer, http2)), + Ok(io) => Arbiter::handle().spawn( + HttpChannel::new(h, io, peer, http2)), Err(err) => trace!("Error during handling tls connection: {}", err), }; @@ -629,14 +683,27 @@ impl StreamHandlerType { } } -fn start_accept_thread(sock: net::TcpListener, addr: net::SocketAddr, - workers: Vec>>) { +enum Command { + Pause, + Resume, + Stop, +} + +fn start_accept_thread(sock: net::TcpListener, addr: net::SocketAddr, backlog: i32, + workers: Vec>>) + -> (mio::SetReadiness, sync_mpsc::Sender) +{ + let (tx, rx) = sync_mpsc::channel(); + let (reg, readiness) = mio::Registration::new2(); + // start accept thread let _ = thread::Builder::new().name(format!("Accept on {}", addr)).spawn(move || { - let mut next = 0; - let server = mio::net::TcpListener::from_listener(sock, &addr) - .expect("Can not create mio::net::TcpListener"); - const SERVER: mio::Token = mio::Token(0); + const SRV: mio::Token = mio::Token(0); + const CMD: mio::Token = mio::Token(1); + + let mut server = Some( + mio::net::TcpListener::from_listener(sock, &addr) + .expect("Can not create mio::net::TcpListener")); // Create a poll instance let poll = match mio::Poll::new() { @@ -645,14 +712,23 @@ fn start_accept_thread(sock: net::TcpListener, addr: net::SocketAddr, }; // Start listening for incoming connections - if let Err(err) = poll.register(&server, SERVER, + if let Some(ref srv) = server { + if let Err(err) = poll.register( + srv, SRV, mio::Ready::readable(), mio::PollOpt::edge()) { + panic!("Can not register io: {}", err); + } + } + + // Start listening for incommin commands + if let Err(err) = poll.register(®, CMD, mio::Ready::readable(), mio::PollOpt::edge()) { - panic!("Can not register io: {}", err); + panic!("Can not register Registration: {}", err); } // Create storage for events let mut events = mio::Events::with_capacity(128); + let mut next = 0; loop { if let Err(err) = poll.poll(&mut events, None) { panic!("Poll error: {}", err); @@ -660,27 +736,77 @@ fn start_accept_thread(sock: net::TcpListener, addr: net::SocketAddr, for event in events.iter() { match event.token() { - SERVER => { - loop { - match server.accept_std() { - Ok((sock, addr)) => { - let msg = IoStream{io: sock, peer: Some(addr), http2: false}; - workers[next] - .unbounded_send(msg).expect("worker thread died"); - next = (next + 1) % workers.len(); - }, - Err(err) => if err.kind() == io::ErrorKind::WouldBlock { - break - } else { - error!("Error accepting connection: {:?}", err); - return + SRV => { + if let Some(ref server) = server { + loop { + match server.accept_std() { + Ok((sock, addr)) => { + let msg = IoStream{ + io: sock, peer: Some(addr), http2: false}; + workers[next].unbounded_send(msg) + .expect("worker thread died"); + next = (next + 1) % workers.len(); + }, + Err(err) => if err.kind() == io::ErrorKind::WouldBlock { + break + } else { + error!("Error accepting connection: {:?}", err); + return + } } } } + }, + CMD => match rx.try_recv() { + Ok(cmd) => match cmd { + Command::Pause => if let Some(server) = server.take() { + if let Err(err) = poll.deregister(&server) { + error!("Can not deregister server socket {}", err); + } else { + info!("Paused accepting connections on {}", addr); + } + }, + Command::Resume => { + let lst = create_tcp_listener(addr, backlog) + .expect("Can not create net::TcpListener"); + + server = Some( + mio::net::TcpListener::from_listener(lst, &addr) + .expect("Can not create mio::net::TcpListener")); + + if let Some(ref server) = server { + if let Err(err) = poll.register( + server, SRV, mio::Ready::readable(), mio::PollOpt::edge()) + { + error!("Can not resume socket accept process: {}", err); + } else { + info!("Accepting connections on {} has been resumed", + addr); + } + } + }, + Command::Stop => return, + }, + Err(err) => match err { + sync_mpsc::TryRecvError::Empty => (), + sync_mpsc::TryRecvError::Disconnected => return, + } } _ => unreachable!(), } } } }); + + (readiness, tx) +} + +fn create_tcp_listener(addr: net::SocketAddr, backlog: i32) -> io::Result { + let builder = match addr { + net::SocketAddr::V4(_) => TcpBuilder::new_v4()?, + net::SocketAddr::V6(_) => TcpBuilder::new_v6()?, + }; + builder.bind(addr)?; + builder.reuse_address(true)?; + Ok(builder.listen(backlog)?) } diff --git a/tests/test_server.rs b/tests/test_server.rs index 6884d30f1..cfea669ba 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -2,10 +2,12 @@ extern crate actix; extern crate actix_web; extern crate tokio_core; extern crate reqwest; +extern crate futures; -use std::thread; +use std::{net, thread}; use std::sync::{Arc, mpsc}; use std::sync::atomic::{AtomicUsize, Ordering}; +use futures::Future; use actix_web::*; use actix::System; @@ -20,12 +22,22 @@ fn test_start() { let srv = HttpServer::new( || vec![Application::new() .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))]); + let srv = srv.bind("127.0.0.1:0").unwrap(); - let _ = tx.send(srv.addrs()[0].clone()); - srv.start(); + let addr = srv.addrs()[0].clone(); + let srv_addr = srv.start(); + let _ = tx.send((addr, srv_addr)); sys.run(); }); - let addr = rx.recv().unwrap(); + let (addr, srv_addr) = rx.recv().unwrap(); + assert!(reqwest::get(&format!("http://{}/", addr)).unwrap().status().is_success()); + + // pause + let _ = srv_addr.call_fut(dev::PauseServer).wait(); + assert!(net::TcpStream::connect(addr).is_err()); + + // resume + let _ = srv_addr.call_fut(dev::ResumeServer).wait(); assert!(reqwest::get(&format!("http://{}/", addr)).unwrap().status().is_success()); } From 4d741b4de57ac077c646fa6b3a3835fe62532475 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 27 Dec 2017 13:26:31 -0800 Subject: [PATCH 204/279] Fix typos --- src/server.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server.rs b/src/server.rs index ff0aa1dee..6552e2e3c 100644 --- a/src/server.rs +++ b/src/server.rs @@ -379,7 +379,7 @@ impl HttpServer, net::SocketAddr, H, for (addr, sock) in addrs { info!("Starting tls http server on {}", addr); self.accept.push( - start_accept_thread(sock, addr, workers.clone(), self.backlog)); + start_accept_thread(sock, addr, self.backlog, workers.clone())); } // start http server actor @@ -791,7 +791,7 @@ fn start_accept_thread(sock: net::TcpListener, addr: net::SocketAddr, backlog: i sync_mpsc::TryRecvError::Empty => (), sync_mpsc::TryRecvError::Disconnected => return, } - } + }, _ => unreachable!(), } } From 556de7293215153e08c33559456a4c786de68bf9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 27 Dec 2017 17:49:10 -0800 Subject: [PATCH 205/279] add server spawn method --- README.md | 2 +- guide/src/qs_3_5.md | 29 +++++++++++++++++ src/server.rs | 78 +++++++++++++++++++++++++++++++++++++-------- 3 files changed, 95 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index cd9d5a828..fff88d049 100644 --- a/README.md +++ b/README.md @@ -68,4 +68,4 @@ This project is licensed under either of at your option. -[![Analytics](https://ga-beacon.appspot.com/UA-110322332-2/actix-web/readme?pixel)](https://github.com/igrigorik/ga-beacon) +[![Analytics](https://ga-beacon.appspot.com/UA-110322332-2/actix-web/readme?flat&useReferer)](https://github.com/igrigorik/ga-beacon) diff --git a/guide/src/qs_3_5.md b/guide/src/qs_3_5.md index 647aa9654..3a3e04c4f 100644 --- a/guide/src/qs_3_5.md +++ b/guide/src/qs_3_5.md @@ -29,6 +29,35 @@ fn main() { } ``` +It is possible to start server in separate thread with *spawn()* method. In that +case server spawns new thread and create new actix system in it. To stop +this server send `StopServer` message. + +Http server is implemented as an actix actor. It is possible to communicate with server +via messaging system. All start methods like `start()`, `start_ssl()`, etc returns +address of the started http server. Actix http server accept several messages: + +* `PauseServer` - Pause accepting incoming connections +* `ResumeServer` - Resume accepting incoming connections +* `StopServer` - Stop incoming connection processing, stop all workers and exit + +```rust +# extern crate futures; +# extern crate actix; +# extern crate actix_web; +# use futures::Future; +use actix_web::*; + +fn main() { + let addr = HttpServer::new( + || Application::new() + .resource("/", |r| r.h(httpcodes::HTTPOk))) + .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") + .spawn(); + + let _ = addr.call_fut(dev::StopServer).wait(); // <- Send `StopServer` message to server. +} +``` ## Multi-threading diff --git a/src/server.rs b/src/server.rs index 6552e2e3c..cf7276259 100644 --- a/src/server.rs +++ b/src/server.rs @@ -7,6 +7,7 @@ use std::marker::PhantomData; use std::collections::HashMap; use actix::dev::*; +use actix::System; use futures::Stream; use futures::sync::mpsc; use tokio_io::{AsyncRead, AsyncWrite}; @@ -107,8 +108,13 @@ pub struct HttpServer workers: Vec>>, sockets: HashMap, accept: Vec<(mio::SetReadiness, sync_mpsc::Sender)>, + spawned: bool, } +unsafe impl Sync for HttpServer where H: 'static {} +unsafe impl Send for HttpServer where H: 'static {} + + impl Actor for HttpServer { type Context = Context; @@ -146,6 +152,7 @@ impl HttpServer workers: Vec::new(), sockets: HashMap::new(), accept: Vec::new(), + spawned: false, } } @@ -206,15 +213,13 @@ impl HttpServer pub fn bind(mut self, addr: S) -> io::Result { let mut err = None; let mut succ = false; - if let Ok(iter) = addr.to_socket_addrs() { - for addr in iter { - match create_tcp_listener(addr, self.backlog) { - Ok(lst) => { - succ = true; - self.sockets.insert(lst.local_addr().unwrap(), lst); - }, - Err(e) => err = Some(e), - } + for addr in addr.to_socket_addrs()? { + match create_tcp_listener(addr, self.backlog) { + Ok(lst) => { + succ = true; + self.sockets.insert(lst.local_addr().unwrap(), lst); + }, + Err(e) => err = Some(e), } } @@ -282,7 +287,7 @@ impl HttpServer /// HttpServer::new( /// || Application::new() /// .resource("/", |r| r.h(httpcodes::HTTPOk))) - /// .bind("127.0.0.1:8088").expect("Can not bind to 127.0.0.1:8088") + /// .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") /// .start(); /// # actix::Arbiter::system().send(actix::msgs::SystemExit(0)); /// @@ -310,6 +315,43 @@ impl HttpServer HttpServer::create(|_| {self}) } } + + /// Spawn new thread and start listening for incomming connections. + /// + /// This method spawns new thread and starts new actix system. Other than that it is + /// similar to `start()` method. This method does not block. + /// + /// This methods panics if no socket addresses get bound. + /// + /// ```rust + /// # extern crate futures; + /// # extern crate actix; + /// # extern crate actix_web; + /// # use futures::Future; + /// use actix_web::*; + /// + /// fn main() { + /// let addr = HttpServer::new( + /// || Application::new() + /// .resource("/", |r| r.h(httpcodes::HTTPOk))) + /// .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") + /// .spawn(); + /// + /// let _ = addr.call_fut(dev::StopServer).wait(); // <- Send `StopServer` message to server. + /// } + /// ``` + pub fn spawn(mut self) -> SyncAddress { + self.spawned = true; + + let (tx, rx) = sync_mpsc::channel(); + thread::spawn(move || { + let sys = System::new("http-server"); + let addr = self.start(); + let _ = tx.send(addr); + sys.run(); + }); + rx.recv().unwrap() + } } #[cfg(feature="tls")] @@ -465,15 +507,20 @@ impl Handler, io::Error> for HttpServer } } -/// Pause connection accepting process +/// Pause accepting incoming connections +/// +/// If socket contains some pending connection, they might be dropped. +/// All opened connection remains active. #[derive(Message)] pub struct PauseServer; -/// Resume connection accepting process +/// Resume accepting incoming connections #[derive(Message)] pub struct ResumeServer; -/// Stop connection processing and exit +/// Stop incoming connection processing, stop all workers and exit. +/// +/// If server starts with `spawn()` method, then spawned thread get terminated. #[derive(Message)] pub struct StopServer; @@ -522,6 +569,11 @@ impl Handler for HttpServer let _ = item.0.set_readiness(mio::Ready::readable()); } ctx.stop(); + + // we need to stop system if server was spawned + if self.spawned { + Arbiter::system().send(msgs::SystemExit(0)) + } Self::empty() } } From 19e1c1b75b90009b3399128330191795d0726472 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 27 Dec 2017 18:41:09 -0800 Subject: [PATCH 206/279] use Cow for Params type --- src/param.rs | 19 +++++++++++-------- src/router.rs | 3 ++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/param.rs b/src/param.rs index 5aaa4f849..1244a2ab6 100644 --- a/src/param.rs +++ b/src/param.rs @@ -2,6 +2,7 @@ use std; use std::ops::Index; use std::path::PathBuf; use std::str::FromStr; +use std::borrow::Cow; use smallvec::SmallVec; use error::{ResponseError, UriSegmentError, ErrorBadRequest}; @@ -20,7 +21,7 @@ pub trait FromParam: Sized { /// /// If resource path contains variable patterns, `Params` stores this variables. #[derive(Debug)] -pub struct Params<'a>(SmallVec<[(&'a str, &'a str); 3]>); +pub struct Params<'a>(SmallVec<[(Cow<'a, str>, Cow<'a, str>); 3]>); impl<'a> Default for Params<'a> { fn default() -> Params<'a> { @@ -34,8 +35,10 @@ impl<'a> Params<'a> { self.0.clear(); } - pub(crate) fn add(&mut self, name: &'a str, value: &'a str) { - self.0.push((name, value)); + pub(crate) fn add(&mut self, name: N, value: V) + where N: Into>, V: Into>, + { + self.0.push((name.into(), value.into())); } /// Check if there are any matched patterns @@ -44,10 +47,10 @@ impl<'a> Params<'a> { } /// Get matched parameter by name without type conversion - pub fn get(&self, key: &str) -> Option<&'a str> { - for item in &self.0 { + pub fn get(&'a self, key: &str) -> Option<&'a str> { + for item in self.0.iter() { if key == item.0 { - return Some(item.1) + return Some(item.1.as_ref()) } } None @@ -66,7 +69,7 @@ impl<'a> Params<'a> { /// } /// # fn main() {} /// ``` - pub fn query(&self, key: &str) -> Result::Err> + pub fn query(&'a self, key: &str) -> Result::Err> { if let Some(s) = self.get(key) { T::from_param(s) @@ -76,7 +79,7 @@ impl<'a> Params<'a> { } } -impl<'a, 'b> Index<&'b str> for Params<'a> { +impl<'a, 'b, 'c: 'a> Index<&'b str> for &'c Params<'a> { type Output = str; fn index(&self, name: &'b str) -> &str { diff --git a/src/router.rs b/src/router.rs index 7fd9f2a65..bbb701623 100644 --- a/src/router.rs +++ b/src/router.rs @@ -190,7 +190,8 @@ impl Pattern { for capture in captures.iter() { if let Some(ref m) = capture { if idx != 0 { - req.match_info_mut().add(&self.names[idx-1], m.as_str()); + req.match_info_mut().add( + self.names[idx-1].as_str(), m.as_str()); } idx += 1; } From 6bb893deabe885b8e3deaed3d727c86f7dc70bf8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 27 Dec 2017 19:02:29 -0800 Subject: [PATCH 207/279] use Params object for query --- guide/src/qs_5.md | 4 ++-- guide/src/qs_7.md | 8 ++++---- src/httprequest.rs | 25 ++++++++++++++++++------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index 5f8042179..dba0b6370 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -146,9 +146,9 @@ and: /{foo}/bar/baz ``` -A *variable part*(replacement marker) is specified in the form *{identifier}*, +A *variable part* (replacement marker) is specified in the form *{identifier}*, where this means "accept any characters up to the next slash character and use this -as the name in the `HttpRequest.match_info` object". +as the name in the `HttpRequest.match_info()` object". A replacement marker in a pattern matches the regular expression `[^{}/]+`. diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index f4fdf5e97..5d3447514 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -262,14 +262,14 @@ At the same time *Payload* implements *Stream* trait, so it could be used with v stream combinators. Also *Payload* provides serveral convinience methods that return future object that resolve to Bytes object. -* *readany* method returns *Stream* of *Bytes* objects. +* *readany()* method returns *Stream* of *Bytes* objects. -* *readexactly* method returns *Future* that resolves when specified number of bytes +* *readexactly()* method returns *Future* that resolves when specified number of bytes get received. -* *readline* method returns *Future* that resolves when `\n` get received. +* *readline()* method returns *Future* that resolves when `\n` get received. -* *readuntil* method returns *Future* that resolves when specified bytes string +* *readuntil()* method returns *Future* that resolves when specified bytes string matches in input bytes stream In this example handle reads request payload chunk by chunk and prints every chunk. diff --git a/src/httprequest.rs b/src/httprequest.rs index ddaa4e981..4b8f18b07 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -1,6 +1,5 @@ //! HTTP Request message related code. use std::{str, fmt, mem}; -use std::borrow::Cow; use std::rc::Rc; use std::net::SocketAddr; use std::collections::HashMap; @@ -30,6 +29,8 @@ pub struct HttpMessage { pub extensions: Extensions, pub params: Params<'static>, pub cookies: Option>>, + pub query: Params<'static>, + pub query_loaded: bool, pub addr: Option, pub payload: Option, pub info: Option>, @@ -44,6 +45,8 @@ impl Default for HttpMessage { version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), params: Params::default(), + query: Params::default(), + query_loaded: false, cookies: None, addr: None, payload: None, @@ -79,6 +82,8 @@ impl HttpMessage { self.headers.clear(); self.extensions.clear(); self.params.clear(); + self.query.clear(); + self.query_loaded = false; self.cookies = None; self.addr = None; self.info = None; @@ -102,6 +107,8 @@ impl HttpRequest<()> { version: version, headers: headers, params: Params::default(), + query: Params::default(), + query_loaded: false, cookies: None, addr: None, payload: payload, @@ -272,13 +279,17 @@ impl HttpRequest { self.as_mut().addr = addr } - /// Return a new iterator that yields pairs of `Cow` for query parameters - pub fn query(&self) -> HashMap, Cow> { - let mut q = HashMap::new(); - for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) { - q.insert(key, val); + /// Get a reference to the Params object. + /// Params is a container for url query parameters. + pub fn query(&self) -> &Params { + if !self.as_ref().query_loaded { + let params: &mut Params = unsafe{ mem::transmute(&mut self.as_mut().query) }; + self.as_mut().query_loaded = true; + for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) { + params.add(key, val); + } } - q + unsafe{ mem::transmute(&self.as_ref().query) } } /// The query string in the URL. From 8941557da6bfa50c5664b19be830baefb981fc20 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 27 Dec 2017 19:09:36 -0800 Subject: [PATCH 208/279] add parameter container iterator --- src/param.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/param.rs b/src/param.rs index 1244a2ab6..b2e7c6029 100644 --- a/src/param.rs +++ b/src/param.rs @@ -2,6 +2,7 @@ use std; use std::ops::Index; use std::path::PathBuf; use std::str::FromStr; +use std::slice::Iter; use std::borrow::Cow; use smallvec::SmallVec; @@ -77,6 +78,11 @@ impl<'a> Params<'a> { T::from_param("") } } + + /// Return iterator to items in paramter container + pub fn iter(&self) -> Iter<(Cow<'a, str>, Cow<'a, str>)> { + self.0.iter() + } } impl<'a, 'b, 'c: 'a> Index<&'b str> for &'c Params<'a> { From 093d0bae401962a298daa86ea3d8c203d16745a5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 27 Dec 2017 19:19:28 -0800 Subject: [PATCH 209/279] Param ctor is private --- src/httprequest.rs | 8 ++++---- src/param.rs | 8 +++----- src/test.rs | 4 ++-- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index 4b8f18b07..6d763e2f3 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -44,8 +44,8 @@ impl Default for HttpMessage { uri: Uri::default(), version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), - params: Params::default(), - query: Params::default(), + params: Params::new(), + query: Params::new(), query_loaded: false, cookies: None, addr: None, @@ -106,8 +106,8 @@ impl HttpRequest<()> { uri: uri, version: version, headers: headers, - params: Params::default(), - query: Params::default(), + params: Params::new(), + query: Params::new(), query_loaded: false, cookies: None, addr: None, diff --git a/src/param.rs b/src/param.rs index b2e7c6029..530e62089 100644 --- a/src/param.rs +++ b/src/param.rs @@ -24,13 +24,11 @@ pub trait FromParam: Sized { #[derive(Debug)] pub struct Params<'a>(SmallVec<[(Cow<'a, str>, Cow<'a, str>); 3]>); -impl<'a> Default for Params<'a> { - fn default() -> Params<'a> { +impl<'a> Params<'a> { + + pub(crate) fn new() -> Params<'a> { Params(SmallVec::new()) } -} - -impl<'a> Params<'a> { pub(crate) fn clear(&mut self) { self.0.clear(); diff --git a/src/test.rs b/src/test.rs index 1ff954e76..11c03f35e 100644 --- a/src/test.rs +++ b/src/test.rs @@ -264,7 +264,7 @@ impl Default for TestRequest<()> { uri: Uri::from_str("/").unwrap(), version: Version::HTTP_11, headers: HeaderMap::new(), - params: Params::default(), + params: Params::new(), cookies: None, payload: None, } @@ -297,7 +297,7 @@ impl TestRequest { uri: Uri::from_str("/").unwrap(), version: Version::HTTP_11, headers: HeaderMap::new(), - params: Params::default(), + params: Params::new(), cookies: None, payload: None, } From b714e1f4ce5e17d56fed840c471516c3b47ea6b3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 27 Dec 2017 19:29:04 -0800 Subject: [PATCH 210/279] fix example --- examples/template_tera/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/template_tera/src/main.rs b/examples/template_tera/src/main.rs index 1925ad4e2..946c78bf7 100644 --- a/examples/template_tera/src/main.rs +++ b/examples/template_tera/src/main.rs @@ -12,7 +12,7 @@ struct State { fn index(req: HttpRequest) -> Result { let s = if let Some(name) = req.query().get("name") { // <- submitted form let mut ctx = tera::Context::new(); - ctx.add("name", name); + ctx.add("name", &name.to_owned()); ctx.add("text", &"Welcome!".to_owned()); req.state().template.render("user.html", &ctx) .map_err(|_| error::ErrorInternalServerError("Template error"))? From 27b0dfd761d501d0bee14c9d04835eb8404dd4a5 Mon Sep 17 00:00:00 2001 From: Alban Minassian Date: Thu, 28 Dec 2017 13:02:46 +0100 Subject: [PATCH 211/279] set sessio name --- src/middleware/session.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/middleware/session.rs b/src/middleware/session.rs index 8ac068888..a03077c26 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -401,6 +401,7 @@ impl SessionBackend for CookieSessionBackend { /// # fn main() { /// let backend: CookieSessionBackend = CookieSessionBackend::build(&[0; 32]) /// .domain("www.rust-lang.org") +/// .name("actix_session") /// .path("/") /// .secure(true) /// .finish(); @@ -420,6 +421,12 @@ impl CookieSessionBackendBuilder { self } + /// Sets the `name` field in the session cookie being built. + pub fn name>(mut self, value: S) -> CookieSessionBackendBuilder { + self.0.name = value.into(); + self + } + /// Sets the `domain` field in the session cookie being built. pub fn domain>(mut self, value: S) -> CookieSessionBackendBuilder { self.0.domain = Some(value.into()); From 02b37570f472c55b1b04c705712c9612a63b938d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Dec 2017 09:11:25 -0800 Subject: [PATCH 212/279] flaky test --- tests/test_server.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_server.rs b/tests/test_server.rs index cfea669ba..0852affbd 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -4,7 +4,7 @@ extern crate tokio_core; extern crate reqwest; extern crate futures; -use std::{net, thread}; +use std::{net, thread, time}; use std::sync::{Arc, mpsc}; use std::sync::atomic::{AtomicUsize, Ordering}; use futures::Future; @@ -34,6 +34,7 @@ fn test_start() { // pause let _ = srv_addr.call_fut(dev::PauseServer).wait(); + thread::sleep(time::Duration::from_millis(100)); assert!(net::TcpStream::connect(addr).is_err()); // resume From d80a0c9f942cd6d9fd3d69081a58bea838990cc5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Dec 2017 11:36:20 -0800 Subject: [PATCH 213/279] add support for unix signals --- Cargo.toml | 3 ++ examples/signals/Cargo.toml | 17 ++++++++++ examples/signals/README.md | 4 +++ examples/signals/src/main.rs | 30 ++++++++++++++++++ src/server.rs | 60 +++++++++++++++++++++++++++++++++--- 5 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 examples/signals/Cargo.toml create mode 100644 examples/signals/README.md create mode 100644 examples/signals/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 2eb523183..ba48f1c7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,9 @@ tls = ["native-tls", "tokio-tls"] # openssl alpn = ["openssl", "openssl/v102", "openssl/v110", "tokio-openssl"] +# signals +signal = ["actix/signal"] + [dependencies] log = "0.3" failure = "0.1" diff --git a/examples/signals/Cargo.toml b/examples/signals/Cargo.toml new file mode 100644 index 000000000..d5a6f9235 --- /dev/null +++ b/examples/signals/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "signals" +version = "0.1.0" +authors = ["Nikolay Kim "] + +[[bin]] +name = "server" +path = "src/main.rs" + +[dependencies] +env_logger = "*" +futures = "0.1" +actix = "^0.3.5" + +#actix-web = { git = "https://github.com/actix/actix-web.git" } + +actix-web = { path="../../", features=["signal"] } diff --git a/examples/signals/README.md b/examples/signals/README.md new file mode 100644 index 000000000..0d2597fed --- /dev/null +++ b/examples/signals/README.md @@ -0,0 +1,4 @@ + +# Signals + +This example shows how to handle unix signals and properly stop http server diff --git a/examples/signals/src/main.rs b/examples/signals/src/main.rs new file mode 100644 index 000000000..500af1a79 --- /dev/null +++ b/examples/signals/src/main.rs @@ -0,0 +1,30 @@ +extern crate actix; +extern crate actix_web; +extern crate futures; +extern crate env_logger; + +use actix_web::*; +use actix::Arbiter; +use actix::actors::signal::{ProcessSignals, Subscribe}; + + +fn main() { + ::std::env::set_var("RUST_LOG", "actix_web=info"); + let _ = env_logger::init(); + let sys = actix::System::new("signals-example"); + + let addr = HttpServer::new(|| { + Application::new() + // enable logger + .middleware(middleware::Logger::default()) + .resource("/", |r| r.h(httpcodes::HTTPOk))}) + .bind("127.0.0.1:8080").unwrap() + .start(); + + // Subscribe to unix signals + let signals = Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); + + println!("Started http server: 127.0.0.1:8080"); + let _ = sys.run(); +} diff --git a/src/server.rs b/src/server.rs index cf7276259..425f7eca9 100644 --- a/src/server.rs +++ b/src/server.rs @@ -33,6 +33,9 @@ use openssl::pkcs12::ParsedPkcs12; #[cfg(feature="alpn")] use tokio_openssl::{SslStream, SslAcceptorExt}; +#[cfg(feature="signal")] +use actix::actors::signal; + use helpers; use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; @@ -108,7 +111,7 @@ pub struct HttpServer workers: Vec>>, sockets: HashMap, accept: Vec<(mio::SetReadiness, sync_mpsc::Sender)>, - spawned: bool, + exit: bool, } unsafe impl Sync for HttpServer where H: 'static {} @@ -152,7 +155,7 @@ impl HttpServer workers: Vec::new(), sockets: HashMap::new(), accept: Vec::new(), - spawned: false, + exit: false, } } @@ -202,6 +205,16 @@ impl HttpServer self } + #[cfg(feature="signal")] + /// Send `SystemExit` message to actix system + /// + /// `SystemExit` message stops currently running system arbiter and all + /// nested arbiters. + pub fn system_exit(mut self) -> Self { + self.exit = true; + self + } + /// Get addresses of bound sockets. pub fn addrs(&self) -> Vec { self.sockets.keys().cloned().collect() @@ -341,7 +354,7 @@ impl HttpServer /// } /// ``` pub fn spawn(mut self) -> SyncAddress { - self.spawned = true; + self.exit = true; let (tx, rx) = sync_mpsc::channel(); thread::spawn(move || { @@ -475,6 +488,41 @@ impl HttpServer } } +#[cfg(feature="signal")] +/// Unix Signals support +/// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and send `SystemExit(0)` +/// message to `System` actor. +impl Handler for HttpServer + where T: AsyncRead + AsyncWrite + 'static, + H: HttpHandler + 'static, + U: 'static, + A: 'static, +{ + fn handle(&mut self, msg: signal::Signal, ctx: &mut Context) + -> Response + { + match msg.0 { + signal::SignalType::Int => { + info!("SIGINT received, exiting"); + self.exit = true; + Handler::::handle(self, StopServer{graceful: false}, ctx); + } + signal::SignalType::Term => { + info!("SIGTERM received, stopping"); + self.exit = true; + Handler::::handle(self, StopServer{graceful: true}, ctx); + } + signal::SignalType::Quit => { + info!("SIGQUIT received, exiting"); + self.exit = true; + Handler::::handle(self, StopServer{graceful: false}, ctx); + } + _ => (), + }; + Self::empty() + } +} + #[derive(Message)] struct IoStream { io: T, @@ -522,7 +570,9 @@ pub struct ResumeServer; /// /// If server starts with `spawn()` method, then spawned thread get terminated. #[derive(Message)] -pub struct StopServer; +pub struct StopServer { + pub graceful: bool +} impl Handler for HttpServer where T: AsyncRead + AsyncWrite + 'static, @@ -571,7 +621,7 @@ impl Handler for HttpServer ctx.stop(); // we need to stop system if server was spawned - if self.spawned { + if self.exit { Arbiter::system().send(msgs::SystemExit(0)) } Self::empty() From 783e19c1bf7676eee819cac2ec8df51f03cc719c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Dec 2017 11:43:45 -0800 Subject: [PATCH 214/279] fix RequestSession impl for HttpRequest --- src/middleware/session.rs | 2 +- src/server.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/middleware/session.rs b/src/middleware/session.rs index a03077c26..fbde31258 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -41,7 +41,7 @@ pub trait RequestSession { fn session(&mut self) -> Session; } -impl RequestSession for HttpRequest { +impl RequestSession for HttpRequest { fn session(&mut self) -> Session { if let Some(s_impl) = self.extensions().get_mut::>() { diff --git a/src/server.rs b/src/server.rs index 425f7eca9..cda09352f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -350,7 +350,8 @@ impl HttpServer /// .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") /// .spawn(); /// - /// let _ = addr.call_fut(dev::StopServer).wait(); // <- Send `StopServer` message to server. + /// let _ = addr.call_fut( + /// dev::StopServer{graceful:true}).wait(); // <- Send `StopServer` message to server. /// } /// ``` pub fn spawn(mut self) -> SyncAddress { From d8b0ce88a5cea4484331a36fabab2afd8311966f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Dec 2017 12:27:46 -0800 Subject: [PATCH 215/279] fix guide example --- guide/src/qs_3_5.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/src/qs_3_5.md b/guide/src/qs_3_5.md index 3a3e04c4f..7ed1ce7da 100644 --- a/guide/src/qs_3_5.md +++ b/guide/src/qs_3_5.md @@ -55,7 +55,7 @@ fn main() { .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") .spawn(); - let _ = addr.call_fut(dev::StopServer).wait(); // <- Send `StopServer` message to server. + let _ = addr.call_fut(dev::StopServer{graceful: true}).wait(); // <- Send `StopServer` message to server. } ``` From 6a2bb9a4731ecd46ddb0c7d90eb1bb2c78e2ff48 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Dec 2017 12:38:37 -0800 Subject: [PATCH 216/279] split worker code to separate module --- src/channel.rs | 3 +- src/h1.rs | 4 +- src/h2.rs | 2 +- src/lib.rs | 1 + src/server.rs | 186 +++---------------------------------------- src/worker.rs | 177 ++++++++++++++++++++++++++++++++++++++++ tests/test_server.rs | 2 +- 7 files changed, 194 insertions(+), 181 deletions(-) create mode 100644 src/worker.rs diff --git a/src/channel.rs b/src/channel.rs index 9940a297d..576c043de 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -11,7 +11,8 @@ use h2; use error::Error; use h1writer::Writer; use httprequest::HttpRequest; -use server::{ServerSettings, WorkerSettings}; +use server::ServerSettings; +use worker::WorkerSettings; /// Low level http request handler #[allow(unused_variables)] diff --git a/src/h1.rs b/src/h1.rs index 3f23029b0..b3cfbf2bd 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -17,7 +17,7 @@ use pipeline::Pipeline; use encoding::PayloadType; use channel::{HttpHandler, HttpHandlerTask}; use h1writer::{Writer, H1Writer}; -use server::WorkerSettings; +use worker::WorkerSettings; use httpcodes::HTTPNotFound; use httprequest::HttpRequest; use error::{ParseError, PayloadError, ResponseError}; @@ -888,7 +888,7 @@ mod tests { use http::{Version, Method}; use super::*; use application::HttpApplication; - use server::WorkerSettings; + use worker::WorkerSettings; struct Buffer { buf: Bytes, diff --git a/src/h2.rs b/src/h2.rs index f0ac8cb77..b3fdc5673 100644 --- a/src/h2.rs +++ b/src/h2.rs @@ -16,7 +16,7 @@ use tokio_core::reactor::Timeout; use pipeline::Pipeline; use h2writer::H2Writer; -use server::WorkerSettings; +use worker::WorkerSettings; use channel::{HttpHandler, HttpHandlerTask}; use error::PayloadError; use encoding::PayloadType; diff --git a/src/lib.rs b/src/lib.rs index f0178178d..b6c6abd58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -103,6 +103,7 @@ mod resource; mod handler; mod pipeline; mod server; +mod worker; mod channel; mod wsframe; mod wsproto; diff --git a/src/server.rs b/src/server.rs index cda09352f..ffac04b9f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,6 +1,5 @@ use std::{io, net, thread}; use std::rc::Rc; -use std::cell::{RefCell, RefMut}; use std::sync::{Arc, mpsc as sync_mpsc}; use std::time::Duration; use std::marker::PhantomData; @@ -11,11 +10,10 @@ use actix::System; use futures::Stream; use futures::sync::mpsc; use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_core::reactor::Handle; use tokio_core::net::TcpStream; use mio; use num_cpus; -use net2::{TcpBuilder, TcpStreamExt}; +use net2::TcpBuilder; #[cfg(feature="tls")] use futures::{future, Future}; @@ -38,6 +36,7 @@ use actix::actors::signal; use helpers; use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; +use worker::{Conn, Worker, WorkerSettings, StreamHandlerType}; /// Various server settings #[derive(Debug, Clone)] @@ -248,13 +247,13 @@ impl HttpServer } fn start_workers(&mut self, settings: &ServerSettings, handler: &StreamHandlerType) - -> Vec>> + -> Vec>> { // start workers let mut workers = Vec::new(); for _ in 0..self.threads { let s = settings.clone(); - let (tx, rx) = mpsc::unbounded::>(); + let (tx, rx) = mpsc::unbounded::>(); let h = handler.clone(); let ka = self.keep_alive; @@ -483,7 +482,7 @@ impl HttpServer // start server HttpServer::create(move |ctx| { ctx.add_stream(stream.map( - move |(t, _)| IoStream{io: t, peer: None, http2: false})); + move |(t, _)| Conn{io: t, peer: None, http2: false})); self }) } @@ -524,20 +523,13 @@ impl Handler for HttpServer } } -#[derive(Message)] -struct IoStream { - io: T, - peer: Option, - http2: bool, -} - -impl StreamHandler, io::Error> for HttpServer +impl StreamHandler, io::Error> for HttpServer where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static, U: 'static, A: 'static {} -impl Handler, io::Error> for HttpServer +impl Handler, io::Error> for HttpServer where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static, U: 'static, @@ -547,8 +539,7 @@ impl Handler, io::Error> for HttpServer debug!("Error handling request: {}", err) } - fn handle(&mut self, msg: IoStream, _: &mut Context) - -> Response> + fn handle(&mut self, msg: Conn, _: &mut Context) -> Response> { Arbiter::handle().spawn( HttpChannel::new(Rc::clone(self.h.as_ref().unwrap()), msg.io, msg.peer, msg.http2)); @@ -629,163 +620,6 @@ impl Handler for HttpServer } } -/// Http worker -/// -/// Worker accepts Socket objects via unbounded channel and start requests processing. -struct Worker { - h: Rc>, - hnd: Handle, - handler: StreamHandlerType, -} - -pub(crate) struct WorkerSettings { - h: RefCell>, - enabled: bool, - keep_alive: u64, - bytes: Rc, - messages: Rc, -} - -impl WorkerSettings { - pub(crate) fn new(h: Vec, keep_alive: Option) -> WorkerSettings { - WorkerSettings { - h: RefCell::new(h), - enabled: if let Some(ka) = keep_alive { ka > 0 } else { false }, - keep_alive: keep_alive.unwrap_or(0), - bytes: Rc::new(helpers::SharedBytesPool::new()), - messages: Rc::new(helpers::SharedMessagePool::new()), - } - } - - pub fn handlers(&self) -> RefMut> { - self.h.borrow_mut() - } - pub fn keep_alive(&self) -> u64 { - self.keep_alive - } - pub fn keep_alive_enabled(&self) -> bool { - self.enabled - } - pub fn get_shared_bytes(&self) -> helpers::SharedBytes { - helpers::SharedBytes::new(self.bytes.get_bytes(), Rc::clone(&self.bytes)) - } - pub fn get_http_message(&self) -> helpers::SharedHttpMessage { - helpers::SharedHttpMessage::new(self.messages.get(), Rc::clone(&self.messages)) - } -} - -impl Worker { - - fn new(h: Vec, handler: StreamHandlerType, keep_alive: Option) -> Worker { - Worker { - h: Rc::new(WorkerSettings::new(h, keep_alive)), - hnd: Arbiter::handle().clone(), - handler: handler, - } - } - - fn update_time(&self, ctx: &mut Context) { - helpers::update_date(); - ctx.run_later(Duration::new(1, 0), |slf, ctx| slf.update_time(ctx)); - } -} - -impl Actor for Worker { - type Context = Context; - - fn started(&mut self, ctx: &mut Self::Context) { - self.update_time(ctx); - } -} - -impl StreamHandler> for Worker - where H: HttpHandler + 'static {} - -impl Handler> for Worker - where H: HttpHandler + 'static, -{ - fn handle(&mut self, msg: IoStream, _: &mut Context) - -> Response> - { - if !self.h.keep_alive_enabled() && - msg.io.set_keepalive(Some(Duration::new(75, 0))).is_err() - { - error!("Can not set socket keep-alive option"); - } - self.handler.handle(Rc::clone(&self.h), &self.hnd, msg); - Self::empty() - } -} - -#[derive(Clone)] -enum StreamHandlerType { - Normal, - #[cfg(feature="tls")] - Tls(TlsAcceptor), - #[cfg(feature="alpn")] - Alpn(SslAcceptor), -} - -impl StreamHandlerType { - - fn handle(&mut self, - h: Rc>, - hnd: &Handle, - msg: IoStream) { - match *self { - StreamHandlerType::Normal => { - let io = TcpStream::from_stream(msg.io, hnd) - .expect("failed to associate TCP stream"); - - hnd.spawn(HttpChannel::new(h, io, msg.peer, msg.http2)); - } - #[cfg(feature="tls")] - StreamHandlerType::Tls(ref acceptor) => { - let IoStream { io, peer, http2 } = msg; - let io = TcpStream::from_stream(io, hnd) - .expect("failed to associate TCP stream"); - - hnd.spawn( - TlsAcceptorExt::accept_async(acceptor, io).then(move |res| { - match res { - Ok(io) => Arbiter::handle().spawn( - HttpChannel::new(h, io, peer, http2)), - Err(err) => - trace!("Error during handling tls connection: {}", err), - }; - future::result(Ok(())) - }) - ); - } - #[cfg(feature="alpn")] - StreamHandlerType::Alpn(ref acceptor) => { - let IoStream { io, peer, .. } = msg; - let io = TcpStream::from_stream(io, hnd) - .expect("failed to associate TCP stream"); - - hnd.spawn( - SslAcceptorExt::accept_async(acceptor, io).then(move |res| { - match res { - Ok(io) => { - let http2 = if let Some(p) = io.get_ref().ssl().selected_alpn_protocol() - { - p.len() == 2 && &p == b"h2" - } else { - false - }; - Arbiter::handle().spawn(HttpChannel::new(h, io, peer, http2)); - }, - Err(err) => - trace!("Error during handling tls connection: {}", err), - }; - future::result(Ok(())) - }) - ); - } - } - } -} - enum Command { Pause, Resume, @@ -793,7 +627,7 @@ enum Command { } fn start_accept_thread(sock: net::TcpListener, addr: net::SocketAddr, backlog: i32, - workers: Vec>>) + workers: Vec>>) -> (mio::SetReadiness, sync_mpsc::Sender) { let (tx, rx) = sync_mpsc::channel(); @@ -844,7 +678,7 @@ fn start_accept_thread(sock: net::TcpListener, addr: net::SocketAddr, backlog: i loop { match server.accept_std() { Ok((sock, addr)) => { - let msg = IoStream{ + let msg = Conn{ io: sock, peer: Some(addr), http2: false}; workers[next].unbounded_send(msg) .expect("worker thread died"); diff --git a/src/worker.rs b/src/worker.rs new file mode 100644 index 000000000..347d02cc6 --- /dev/null +++ b/src/worker.rs @@ -0,0 +1,177 @@ +use std::{net, time}; +use std::rc::Rc; +use std::cell::{RefCell, RefMut}; +use tokio_core::net::TcpStream; +use tokio_core::reactor::Handle; +use net2::TcpStreamExt; + +use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Response, StreamHandler}; + +use helpers; +use channel::{HttpChannel, HttpHandler}; + + +#[derive(Message)] +pub(crate) struct Conn { + pub io: T, + pub peer: Option, + pub http2: bool, +} + +pub(crate) struct WorkerSettings { + h: RefCell>, + enabled: bool, + keep_alive: u64, + bytes: Rc, + messages: Rc, +} + +impl WorkerSettings { + pub(crate) fn new(h: Vec, keep_alive: Option) -> WorkerSettings { + WorkerSettings { + h: RefCell::new(h), + enabled: if let Some(ka) = keep_alive { ka > 0 } else { false }, + keep_alive: keep_alive.unwrap_or(0), + bytes: Rc::new(helpers::SharedBytesPool::new()), + messages: Rc::new(helpers::SharedMessagePool::new()), + } + } + + pub fn handlers(&self) -> RefMut> { + self.h.borrow_mut() + } + pub fn keep_alive(&self) -> u64 { + self.keep_alive + } + pub fn keep_alive_enabled(&self) -> bool { + self.enabled + } + pub fn get_shared_bytes(&self) -> helpers::SharedBytes { + helpers::SharedBytes::new(self.bytes.get_bytes(), Rc::clone(&self.bytes)) + } + pub fn get_http_message(&self) -> helpers::SharedHttpMessage { + helpers::SharedHttpMessage::new(self.messages.get(), Rc::clone(&self.messages)) + } +} + +/// Http worker +/// +/// Worker accepts Socket objects via unbounded channel and start requests processing. +pub(crate) struct Worker { + h: Rc>, + hnd: Handle, + handler: StreamHandlerType, +} + +impl Worker { + + pub(crate) fn new(h: Vec, handler: StreamHandlerType, keep_alive: Option) + -> Worker + { + Worker { + h: Rc::new(WorkerSettings::new(h, keep_alive)), + hnd: Arbiter::handle().clone(), + handler: handler, + } + } + + fn update_time(&self, ctx: &mut Context) { + helpers::update_date(); + ctx.run_later(time::Duration::new(1, 0), |slf, ctx| slf.update_time(ctx)); + } +} + +impl Actor for Worker { + type Context = Context; + + fn started(&mut self, ctx: &mut Self::Context) { + self.update_time(ctx); + } +} + +impl StreamHandler> for Worker + where H: HttpHandler + 'static {} + +impl Handler> for Worker + where H: HttpHandler + 'static, +{ + fn handle(&mut self, msg: Conn, _: &mut Context) + -> Response> + { + if !self.h.keep_alive_enabled() && + msg.io.set_keepalive(Some(time::Duration::new(75, 0))).is_err() + { + error!("Can not set socket keep-alive option"); + } + self.handler.handle(Rc::clone(&self.h), &self.hnd, msg); + Self::empty() + } +} + +#[derive(Clone)] +pub(crate) enum StreamHandlerType { + Normal, + #[cfg(feature="tls")] + Tls(TlsAcceptor), + #[cfg(feature="alpn")] + Alpn(SslAcceptor), +} + +impl StreamHandlerType { + + fn handle(&mut self, + h: Rc>, + hnd: &Handle, msg: Conn) { + match *self { + StreamHandlerType::Normal => { + let io = TcpStream::from_stream(msg.io, hnd) + .expect("failed to associate TCP stream"); + + hnd.spawn(HttpChannel::new(h, io, msg.peer, msg.http2)); + } + #[cfg(feature="tls")] + StreamHandlerType::Tls(ref acceptor) => { + let Conn { io, peer, http2 } = msg; + let io = TcpStream::from_stream(io, hnd) + .expect("failed to associate TCP stream"); + + hnd.spawn( + TlsAcceptorExt::accept_async(acceptor, io).then(move |res| { + match res { + Ok(io) => Arbiter::handle().spawn( + HttpChannel::new(h, io, peer, http2)), + Err(err) => + trace!("Error during handling tls connection: {}", err), + }; + future::result(Ok(())) + }) + ); + } + #[cfg(feature="alpn")] + StreamHandlerType::Alpn(ref acceptor) => { + let Conn { io, peer, .. } = msg; + let io = TcpStream::from_stream(io, hnd) + .expect("failed to associate TCP stream"); + + hnd.spawn( + SslAcceptorExt::accept_async(acceptor, io).then(move |res| { + match res { + Ok(io) => { + let http2 = if let Some(p) = io.get_ref().ssl().selected_alpn_protocol() + { + p.len() == 2 && &p == b"h2" + } else { + false + }; + Arbiter::handle().spawn(HttpChannel::new(h, io, peer, http2)); + }, + Err(err) => + trace!("Error during handling tls connection: {}", err), + }; + future::result(Ok(())) + }) + ); + } + } + } +} diff --git a/tests/test_server.rs b/tests/test_server.rs index 0852affbd..032c750f4 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -24,7 +24,7 @@ fn test_start() { .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))]); let srv = srv.bind("127.0.0.1:0").unwrap(); - let addr = srv.addrs()[0].clone(); + let addr = srv.addrs()[0]; let srv_addr = srv.start(); let _ = tx.send((addr, srv_addr)); sys.run(); From 3f4898a6d18f7d3e7870bebea80e445ffd3253b8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Dec 2017 13:07:29 -0800 Subject: [PATCH 217/279] add StopWorker message --- src/server.rs | 21 ++++++++++++--------- src/worker.rs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/server.rs b/src/server.rs index ffac04b9f..e57855943 100644 --- a/src/server.rs +++ b/src/server.rs @@ -15,28 +15,24 @@ use mio; use num_cpus; use net2::TcpBuilder; -#[cfg(feature="tls")] -use futures::{future, Future}; #[cfg(feature="tls")] use native_tls::TlsAcceptor; #[cfg(feature="tls")] -use tokio_tls::{TlsStream, TlsAcceptorExt}; +use tokio_tls::TlsStream; #[cfg(feature="alpn")] -use futures::{future, Future}; -#[cfg(feature="alpn")] -use openssl::ssl::{SslMethod, SslAcceptor, SslAcceptorBuilder}; +use openssl::ssl::{SslMethod, SslAcceptorBuilder}; #[cfg(feature="alpn")] use openssl::pkcs12::ParsedPkcs12; #[cfg(feature="alpn")] -use tokio_openssl::{SslStream, SslAcceptorExt}; +use tokio_openssl::SslStream; #[cfg(feature="signal")] use actix::actors::signal; use helpers; use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; -use worker::{Conn, Worker, WorkerSettings, StreamHandlerType}; +use worker::{Conn, Worker, WorkerSettings, StreamHandlerType, StopWorker}; /// Various server settings #[derive(Debug, Clone)] @@ -604,14 +600,21 @@ impl Handler for HttpServer U: 'static, A: 'static, { - fn handle(&mut self, _: StopServer, ctx: &mut Context) -> Response + fn handle(&mut self, msg: StopServer, ctx: &mut Context) -> Response { + // stop accept threads for item in &self.accept { let _ = item.1.send(Command::Stop); let _ = item.0.set_readiness(mio::Ready::readable()); } ctx.stop(); + // stop workers + let dur = if msg.graceful { Some(Duration::new(30, 0)) } else { None }; + for worker in &self.workers { + worker.send(StopWorker{graceful: dur}) + } + // we need to stop system if server was spawned if self.exit { Arbiter::system().send(msgs::SystemExit(0)) diff --git a/src/worker.rs b/src/worker.rs index 347d02cc6..3072ccac7 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -5,7 +5,22 @@ use tokio_core::net::TcpStream; use tokio_core::reactor::Handle; use net2::TcpStreamExt; +#[cfg(feature="tls")] +use futures::{future, Future}; +#[cfg(feature="tls")] +use native_tls::TlsAcceptor; +#[cfg(feature="tls")] +use tokio_tls::TlsAcceptorExt; + +#[cfg(feature="alpn")] +use futures::{future, Future}; +#[cfg(feature="alpn")] +use openssl::ssl::SslAcceptor; +#[cfg(feature="alpn")] +use tokio_openssl::SslAcceptorExt; + use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Response, StreamHandler}; +use actix::msgs::StopArbiter; use helpers; use channel::{HttpChannel, HttpHandler}; @@ -18,6 +33,12 @@ pub(crate) struct Conn { pub http2: bool, } +/// Stop worker +#[derive(Message)] +pub(crate) struct StopWorker { + pub graceful: Option, +} + pub(crate) struct WorkerSettings { h: RefCell>, enabled: bool, @@ -108,6 +129,17 @@ impl Handler> for Worker } } +/// `StopWorker` message handler +impl Handler for Worker + where H: HttpHandler + 'static, +{ + fn handle(&mut self, _: StopWorker, _: &mut Context) -> Response + { + Arbiter::arbiter().send(StopArbiter(0)); + Self::empty() + } +} + #[derive(Clone)] pub(crate) enum StreamHandlerType { Normal, From 538fea8027d7ade75ff71d492682c5aa4856752d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Dec 2017 16:25:47 -0800 Subject: [PATCH 218/279] add graceful shutdown system --- examples/signals/Cargo.toml | 5 +-- examples/signals/src/main.rs | 15 +++++++- guide/src/qs_3_5.md | 70 ++++++++++++++++++++++++++++++++++++ src/channel.rs | 29 ++++++++------- src/h1.rs | 4 +++ src/h2.rs | 4 +++ src/server.rs | 52 ++++++++++++++++++++++----- src/worker.rs | 62 +++++++++++++++++++++++++++----- 8 files changed, 208 insertions(+), 33 deletions(-) diff --git a/examples/signals/Cargo.toml b/examples/signals/Cargo.toml index d5a6f9235..869dc66e7 100644 --- a/examples/signals/Cargo.toml +++ b/examples/signals/Cargo.toml @@ -11,7 +11,4 @@ path = "src/main.rs" env_logger = "*" futures = "0.1" actix = "^0.3.5" - -#actix-web = { git = "https://github.com/actix/actix-web.git" } - -actix-web = { path="../../", features=["signal"] } +actix-web = { git = "https://github.com/actix/actix-web.git", features=["signal"] } diff --git a/examples/signals/src/main.rs b/examples/signals/src/main.rs index 500af1a79..77b6b2f74 100644 --- a/examples/signals/src/main.rs +++ b/examples/signals/src/main.rs @@ -3,10 +3,22 @@ extern crate actix_web; extern crate futures; extern crate env_logger; +use actix::*; use actix_web::*; -use actix::Arbiter; use actix::actors::signal::{ProcessSignals, Subscribe}; +struct MyWebSocket; + +impl Actor for MyWebSocket { + type Context = HttpContext; +} + +impl StreamHandler for MyWebSocket {} +impl Handler for MyWebSocket { + fn handle(&mut self, _: ws::Message, _: &mut Self::Context) -> Response { + Self::empty() + } +} fn main() { ::std::env::set_var("RUST_LOG", "actix_web=info"); @@ -17,6 +29,7 @@ fn main() { Application::new() // enable logger .middleware(middleware::Logger::default()) + .resource("/ws/", |r| r.f(|req| ws::start(req, MyWebSocket))) .resource("/", |r| r.h(httpcodes::HTTPOk))}) .bind("127.0.0.1:8080").unwrap() .start(); diff --git a/guide/src/qs_3_5.md b/guide/src/qs_3_5.md index 7ed1ce7da..312668903 100644 --- a/guide/src/qs_3_5.md +++ b/guide/src/qs_3_5.md @@ -164,3 +164,73 @@ fn index(req: HttpRequest) -> HttpResponse { } # fn main() {} ``` + +## Graceful shutdown + +Actix http server support graceful shutdown. After receiving a stop signal, workers +have specific amount of time to finish serving requests. Workers still alive after the +timeout are force dropped. By default shutdown timeout sets to 30 seconds. +You can change this parameter with `HttpServer::shutdown_timeout()` method. + +You can send stop message to server with server address and specify if you what +graceful shutdown or not. `start()` or `spawn()` methods return address of the server. + +```rust +# extern crate futures; +# extern crate actix; +# extern crate actix_web; +# use futures::Future; +use actix_web::*; + +fn main() { + let addr = HttpServer::new( + || Application::new() + .resource("/", |r| r.h(httpcodes::HTTPOk))) + .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") + .shutdown_timeout(60) // <- Set shutdown timeout to 60 seconds + .spawn(); + + let _ = addr.call_fut( + dev::StopServer{graceful:true}).wait(); // <- Send `StopServer` message to server. +} +``` + +It is possible to use unix signals on compatible OSs. "signal" feature needs to be enabled +in *Cargo.toml* for *actix-web* dependency. + +```toml +[dependencies] +actix-web = { git = "https://github.com/actix/actix-web", features=["signal"] } +``` + +Then you can subscribe your server to unix signals. Http server handles three signals: + +* *SIGINT* - Force shutdown workers +* *SIGTERM* - Graceful shutdown workers +* *SIGQUIT* - Force shutdown workers + +```rust,ignore +# extern crate futures; +# extern crate actix; +# extern crate actix_web; +use actix_web::*; +use actix::actors::signal::{ProcessSignals, Subscribe}; + +fn main() { + let sys = actix::System::new("signals"); + + let addr = HttpServer::new(|| { + Application::new() + .resource("/", |r| r.h(httpcodes::HTTPOk))}) + .bind("127.0.0.1:8080").unwrap() + .start(); + + // Subscribe to unix signals + let signals = Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); + + println!("Started http server: 127.0.0.1:8080"); + # actix::Arbiter::system().send(actix::msgs::SystemExit(0)); + let _ = sys.run(); +} +``` diff --git a/src/channel.rs b/src/channel.rs index 576c043de..01c18a527 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -1,7 +1,6 @@ use std::rc::Rc; use std::net::SocketAddr; -use actix::dev::*; use bytes::Bytes; use futures::{Future, Poll, Async}; use tokio_io::{AsyncRead, AsyncWrite}; @@ -71,6 +70,7 @@ impl HttpChannel pub(crate) fn new(h: Rc>, io: T, peer: Option, http2: bool) -> HttpChannel { + h.add_channel(); if http2 { HttpChannel { proto: Some(HttpProtocol::H2( @@ -89,12 +89,6 @@ impl HttpChannel } }*/ -impl Actor for HttpChannel - where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static -{ - type Context = Context; -} - impl Future for HttpChannel where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static { @@ -105,16 +99,27 @@ impl Future for HttpChannel match self.proto { Some(HttpProtocol::H1(ref mut h1)) => { match h1.poll() { - Ok(Async::Ready(h1::Http1Result::Done)) => - return Ok(Async::Ready(())), + Ok(Async::Ready(h1::Http1Result::Done)) => { + h1.settings().remove_channel(); + return Ok(Async::Ready(())) + } Ok(Async::Ready(h1::Http1Result::Switch)) => (), Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(_) => - return Err(()), + Err(_) => { + h1.settings().remove_channel(); + return Err(()) + } } } - Some(HttpProtocol::H2(ref mut h2)) => return h2.poll(), + Some(HttpProtocol::H2(ref mut h2)) => { + let result = h2.poll(); + match result { + Ok(Async::Ready(())) | Err(_) => h2.settings().remove_channel(), + _ => (), + } + return result + } None => unreachable!(), } diff --git a/src/h1.rs b/src/h1.rs index b3cfbf2bd..f49d0a2e7 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -89,6 +89,10 @@ impl Http1 keepalive_timer: None } } + pub fn settings(&self) -> &WorkerSettings { + self.settings.as_ref() + } + pub fn into_inner(self) -> (Rc>, T, Option, Bytes) { (self.settings, self.stream.into_inner(), self.addr, self.read_buf.freeze()) } diff --git a/src/h2.rs b/src/h2.rs index b3fdc5673..be8898038 100644 --- a/src/h2.rs +++ b/src/h2.rs @@ -64,6 +64,10 @@ impl Http2 } } + pub fn settings(&self) -> &WorkerSettings { + self.settings.as_ref() + } + pub fn poll(&mut self) -> Poll<(), ()> { // server if let State::Server(ref mut server) = self.state { diff --git a/src/server.rs b/src/server.rs index e57855943..b5c2e8184 100644 --- a/src/server.rs +++ b/src/server.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use actix::dev::*; use actix::System; -use futures::Stream; +use futures::{Future, Sink, Stream}; use futures::sync::mpsc; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_core::net::TcpStream; @@ -107,6 +107,7 @@ pub struct HttpServer sockets: HashMap, accept: Vec<(mio::SetReadiness, sync_mpsc::Sender)>, exit: bool, + shutdown_timeout: u16, } unsafe impl Sync for HttpServer where H: 'static {} @@ -151,6 +152,7 @@ impl HttpServer sockets: HashMap::new(), accept: Vec::new(), exit: false, + shutdown_timeout: 30, } } @@ -210,6 +212,17 @@ impl HttpServer self } + /// Timeout for graceful workers shutdown. + /// + /// After receiving a stop signal, workers have this much time to finish serving requests. + /// Workers still alive after the timeout are force dropped. + /// + /// By default shutdown timeout sets to 30 seconds. + pub fn shutdown_timeout(mut self, sec: u16) -> Self { + self.shutdown_timeout = sec; + self + } + /// Get addresses of bound sockets. pub fn addrs(&self) -> Vec { self.sockets.keys().cloned().collect() @@ -607,19 +620,42 @@ impl Handler for HttpServer let _ = item.1.send(Command::Stop); let _ = item.0.set_readiness(mio::Ready::readable()); } - ctx.stop(); // stop workers - let dur = if msg.graceful { Some(Duration::new(30, 0)) } else { None }; + let (tx, rx) = mpsc::channel(1); + + let dur = if msg.graceful { + Some(Duration::new(u64::from(self.shutdown_timeout), 0)) + } else { + None + }; for worker in &self.workers { - worker.send(StopWorker{graceful: dur}) + let tx2 = tx.clone(); + let fut = worker.call(self, StopWorker{graceful: dur}); + ActorFuture::then(fut, move |_, slf, _| { + slf.workers.pop(); + if slf.workers.is_empty() { + let _ = tx2.send(()); + + // we need to stop system if server was spawned + if slf.exit { + Arbiter::system().send(msgs::SystemExit(0)) + } + } + fut::ok(()) + }).spawn(ctx); } - // we need to stop system if server was spawned - if self.exit { - Arbiter::system().send(msgs::SystemExit(0)) + if !self.workers.is_empty() { + Self::async_reply( + rx.into_future().map(|_| ()).map_err(|_| ()).actfuture()) + } else { + // we need to stop system if server was spawned + if self.exit { + Arbiter::system().send(msgs::SystemExit(0)) + } + Self::empty() } - Self::empty() } } diff --git a/src/worker.rs b/src/worker.rs index 3072ccac7..29158924f 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -1,25 +1,27 @@ use std::{net, time}; use std::rc::Rc; -use std::cell::{RefCell, RefMut}; +use std::cell::{Cell, RefCell, RefMut}; +use futures::Future; +use futures::unsync::oneshot; use tokio_core::net::TcpStream; use tokio_core::reactor::Handle; use net2::TcpStreamExt; #[cfg(feature="tls")] -use futures::{future, Future}; +use futures::future; #[cfg(feature="tls")] use native_tls::TlsAcceptor; #[cfg(feature="tls")] use tokio_tls::TlsAcceptorExt; #[cfg(feature="alpn")] -use futures::{future, Future}; +use futures::future; #[cfg(feature="alpn")] use openssl::ssl::SslAcceptor; #[cfg(feature="alpn")] use tokio_openssl::SslAcceptorExt; -use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Response, StreamHandler}; +use actix::*; use actix::msgs::StopArbiter; use helpers; @@ -33,8 +35,10 @@ pub(crate) struct Conn { pub http2: bool, } -/// Stop worker +/// Stop worker message. Returns `true` on successful shutdown +/// and `false` if some connections still alive. #[derive(Message)] +#[rtype(bool)] pub(crate) struct StopWorker { pub graceful: Option, } @@ -45,6 +49,7 @@ pub(crate) struct WorkerSettings { keep_alive: u64, bytes: Rc, messages: Rc, + channels: Cell, } impl WorkerSettings { @@ -55,6 +60,7 @@ impl WorkerSettings { keep_alive: keep_alive.unwrap_or(0), bytes: Rc::new(helpers::SharedBytesPool::new()), messages: Rc::new(helpers::SharedMessagePool::new()), + channels: Cell::new(0), } } @@ -73,6 +79,17 @@ impl WorkerSettings { pub fn get_http_message(&self) -> helpers::SharedHttpMessage { helpers::SharedHttpMessage::new(self.messages.get(), Rc::clone(&self.messages)) } + pub fn add_channel(&self) { + self.channels.set(self.channels.get()+1); + } + pub fn remove_channel(&self) { + let num = self.channels.get(); + if num > 0 { + self.channels.set(num-1); + } else { + error!("Number of removed channels is bigger than added channel. Bug in actix-web"); + } + } } /// Http worker @@ -100,6 +117,24 @@ impl Worker { helpers::update_date(); ctx.run_later(time::Duration::new(1, 0), |slf, ctx| slf.update_time(ctx)); } + + fn shutdown_timeout(&self, ctx: &mut Context, + tx: oneshot::Sender, dur: time::Duration) { + // sleep for 1 second and then check again + ctx.run_later(time::Duration::new(1, 0), move |slf, ctx| { + let num = slf.h.channels.get(); + if num == 0 { + let _ = tx.send(true); + Arbiter::arbiter().send(StopArbiter(0)); + } else if let Some(d) = dur.checked_sub(time::Duration::new(1, 0)) { + slf.shutdown_timeout(ctx, tx, d); + } else { + info!("Force shutdown http worker, {} connections", num); + let _ = tx.send(false); + Arbiter::arbiter().send(StopArbiter(0)); + } + }); + } } impl Actor for Worker { @@ -133,10 +168,21 @@ impl Handler> for Worker impl Handler for Worker where H: HttpHandler + 'static, { - fn handle(&mut self, _: StopWorker, _: &mut Context) -> Response + fn handle(&mut self, msg: StopWorker, ctx: &mut Context) -> Response { - Arbiter::arbiter().send(StopArbiter(0)); - Self::empty() + let num = self.h.channels.get(); + if num == 0 { + info!("Shutting down http worker, 0 connections"); + Self::reply(true) + } else if let Some(dur) = msg.graceful { + info!("Graceful http worker shutdown, {} connections", num); + let (tx, rx) = oneshot::channel(); + self.shutdown_timeout(ctx, tx, dur); + Self::async_reply(rx.map_err(|_| ()).actfuture()) + } else { + info!("Force shutdown http worker, {} connections", num); + Self::reply(false) + } } } From 308df19865da6c28a49d7a3802a0184fb10db394 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Dec 2017 16:27:08 -0800 Subject: [PATCH 219/279] update readme --- README.md | 1 + src/lib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index fff88d049..70c5d1cc9 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ fn main() { * [WebSockets](https://actix.github.io/actix-web/actix_web/ws/index.html) * Transparent content compression/decompression (br, gzip, deflate) * Configurable request routing + * Graceful server shutdown * Multipart streams * Middlewares ([Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging), [Session](https://actix.github.io/actix-web/guide/qs_10.html#user-sessions), diff --git a/src/lib.rs b/src/lib.rs index b6c6abd58..1552787d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,7 @@ //! * Configurable request routing //! * Multipart streams //! * Middlewares (`Logger`, `Session`, `DefaultHeaders`) +//! * Graceful server shutdown //! * Built on top of [Actix](https://github.com/actix/actix). #![cfg_attr(actix_nightly, feature( From d87fafb563bf8864347806302b6ca6890d902af8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Dec 2017 01:01:31 -0800 Subject: [PATCH 220/279] fix and refactor middleware runner --- src/application.rs | 50 ++++++++++---- src/lib.rs | 6 +- src/middleware/session.rs | 2 +- src/pipeline.rs | 142 ++++++++++++++++++++++---------------- src/router.rs | 5 +- 5 files changed, 123 insertions(+), 82 deletions(-) diff --git a/src/application.rs b/src/application.rs index 21de726e7..925dbb014 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,4 +1,5 @@ use std::rc::Rc; +use std::cell::RefCell; use std::collections::HashMap; use handler::Reply; @@ -6,7 +7,7 @@ use router::{Router, Pattern}; use resource::Resource; use httprequest::HttpRequest; use channel::{HttpHandler, IntoHttpHandler, HttpHandlerTask}; -use pipeline::Pipeline; +use pipeline::{Pipeline, PipelineHandler}; use middleware::Middleware; use server::ServerSettings; @@ -14,19 +15,20 @@ use server::ServerSettings; pub struct HttpApplication { state: Rc, prefix: String, - default: Resource, router: Router, - resources: Vec>, + inner: Rc>>, middlewares: Rc>>>, } -impl HttpApplication { +pub(crate) struct Inner { + default: Resource, + router: Router, + resources: Vec>, +} - pub(crate) fn prepare_request(&self, req: HttpRequest) -> HttpRequest { - req.with_state(Rc::clone(&self.state), self.router.clone()) - } +impl PipelineHandler for Inner { - pub(crate) fn run(&mut self, mut req: HttpRequest) -> Reply { + fn handle(&mut self, mut req: HttpRequest) -> Reply { if let Some(idx) = self.router.recognize(&mut req) { self.resources[idx].handle(req.clone(), Some(&mut self.default)) } else { @@ -35,14 +37,25 @@ impl HttpApplication { } } +impl HttpApplication { + #[cfg(test)] + pub(crate) fn run(&mut self, req: HttpRequest) -> Reply { + self.inner.borrow_mut().handle(req) + } + #[cfg(test)] + pub(crate) fn prepare_request(&self, req: HttpRequest) -> HttpRequest { + req.with_state(Rc::clone(&self.state), self.router.clone()) + } +} + impl HttpHandler for HttpApplication { fn handle(&mut self, req: HttpRequest) -> Result, HttpRequest> { if req.path().starts_with(&self.prefix) { - let req = self.prepare_request(req); - // TODO: redesign run callback - Ok(Box::new(Pipeline::new(req, Rc::clone(&self.middlewares), - &mut |req: HttpRequest| self.run(req)))) + let inner = Rc::clone(&self.inner); + let req = req.with_state(Rc::clone(&self.state), self.router.clone()); + + Ok(Box::new(Pipeline::new(req, Rc::clone(&self.middlewares), inner))) } else { Err(req) } @@ -267,12 +280,19 @@ impl Application where S: 'static { } let (router, resources) = Router::new(prefix, resources); + + let inner = Rc::new(RefCell::new( + Inner { + default: parts.default, + router: router.clone(), + resources: resources } + )); + HttpApplication { state: Rc::new(parts.state), prefix: prefix.to_owned(), - default: parts.default, - router: router, - resources: resources, + inner: inner, + router: router.clone(), middlewares: Rc::new(parts.middlewares), } } diff --git a/src/lib.rs b/src/lib.rs index 1552787d6..ec1aebe48 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,15 +48,13 @@ extern crate regex; #[macro_use] extern crate bitflags; #[macro_use] +extern crate failure; +#[macro_use] extern crate futures; extern crate tokio_io; extern crate tokio_core; extern crate mio; extern crate net2; - -extern crate failure; -#[macro_use] extern crate failure_derive; - extern crate cookie; extern crate http; extern crate httparse; diff --git a/src/middleware/session.rs b/src/middleware/session.rs index fbde31258..7f610e2d2 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -276,7 +276,7 @@ impl CookieSessionInner { fn new(key: &[u8]) -> CookieSessionInner { CookieSessionInner { key: Key::from_master(key), - name: "actix_session".to_owned(), + name: "actix-session".to_owned(), path: "/".to_owned(), domain: None, secure: true } diff --git a/src/pipeline.rs b/src/pipeline.rs index d4a8edd84..80c1a19c2 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -1,5 +1,6 @@ use std::{io, mem}; use std::rc::Rc; +use std::cell::RefCell; use std::marker::PhantomData; use futures::{Async, Poll, Future, Stream}; @@ -14,21 +15,23 @@ use h1writer::{Writer, WriterState}; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Middleware, Finished, Started, Response}; +use application::Inner; -type Handler = FnMut(HttpRequest) -> Reply; -pub(crate) type PipelineHandler<'a, S> = &'a mut FnMut(HttpRequest) -> Reply; +pub trait PipelineHandler { + fn handle(&mut self, req: HttpRequest) -> Reply; +} -pub struct Pipeline(PipelineInfo, PipelineState); +pub struct Pipeline(PipelineInfo, PipelineState); -enum PipelineState { +enum PipelineState { None, Error, - Starting(StartMiddlewares), - Handler(WaitingResponse), - RunMiddlewares(RunMiddlewares), - Response(ProcessResponse), - Finishing(FinishingMiddlewares), - Completed(Completed), + Starting(StartMiddlewares), + Handler(WaitingResponse), + RunMiddlewares(RunMiddlewares), + Response(ProcessResponse), + Finishing(FinishingMiddlewares), + Completed(Completed), } struct PipelineInfo { @@ -75,11 +78,11 @@ enum PipelineResponse { Response(Box>), } -impl Pipeline { +impl> Pipeline { pub fn new(req: HttpRequest, mws: Rc>>>, - handler: PipelineHandler) -> Pipeline + handler: Rc>) -> Pipeline { let mut info = PipelineInfo { req: req, @@ -94,15 +97,14 @@ impl Pipeline { } } -impl Pipeline<()> { +impl Pipeline<(), Inner<()>> { pub fn error>(err: R) -> Box { - Box::new(Pipeline( - PipelineInfo::new( - HttpRequest::default()), ProcessResponse::init(err.into()))) + Box::new(Pipeline::<(), Inner<()>>( + PipelineInfo::new(HttpRequest::default()), ProcessResponse::init(err.into()))) } } -impl Pipeline { +impl Pipeline { fn is_done(&self) -> bool { match self.1 { @@ -115,7 +117,7 @@ impl Pipeline { } } -impl HttpHandlerTask for Pipeline { +impl> HttpHandlerTask for Pipeline { fn disconnected(&mut self) { if let Some(ref mut context) = self.0.context { @@ -274,20 +276,22 @@ impl HttpHandlerTask for Pipeline { type Fut = Box, Error=Error>>; /// Middlewares start executor -struct StartMiddlewares { - hnd: *mut Handler, +struct StartMiddlewares { + hnd: Rc>, fut: Option, + _s: PhantomData, } -impl StartMiddlewares { +impl> StartMiddlewares { - fn init(info: &mut PipelineInfo, handler: PipelineHandler) -> PipelineState { + fn init(info: &mut PipelineInfo, handler: Rc>) -> PipelineState + { // execute middlewares, we need this stage because middlewares could be non-async // and we can move to next state immidietly let len = info.mws.len(); loop { if info.count == len { - let reply = (&mut *handler)(info.req.clone()); + let reply = handler.borrow_mut().handle(info.req.clone()); return WaitingResponse::init(info, reply) } else { match info.mws[info.count].start(&mut info.req) { @@ -299,8 +303,9 @@ impl StartMiddlewares { match fut.poll() { Ok(Async::NotReady) => return PipelineState::Starting(StartMiddlewares { - hnd: handler as *const _ as *mut _, - fut: Some(fut)}), + hnd: handler, + fut: Some(fut), + _s: PhantomData}), Ok(Async::Ready(resp)) => { if let Some(resp) = resp { return RunMiddlewares::init(info, resp); @@ -317,7 +322,8 @@ impl StartMiddlewares { } } - fn poll(mut self, info: &mut PipelineInfo) -> Result, PipelineState> { + fn poll(mut self, info: &mut PipelineInfo) -> Result, PipelineState> + { let len = info.mws.len(); 'outer: loop { match self.fut.as_mut().unwrap().poll() { @@ -329,7 +335,7 @@ impl StartMiddlewares { return Ok(RunMiddlewares::init(info, resp)); } if info.count == len { - let reply = (unsafe{&mut *self.hnd})(info.req.clone()); + let reply = (*self.hnd.borrow_mut()).handle(info.req.clone()); return Ok(WaitingResponse::init(info, reply)); } else { loop { @@ -357,29 +363,33 @@ impl StartMiddlewares { } // waiting for response -struct WaitingResponse { +struct WaitingResponse { stream: PipelineResponse, _s: PhantomData, + _h: PhantomData, } -impl WaitingResponse { +impl WaitingResponse { #[inline] - fn init(info: &mut PipelineInfo, reply: Reply) -> PipelineState + fn init(info: &mut PipelineInfo, reply: Reply) -> PipelineState { match reply.into() { ReplyItem::Message(resp) => RunMiddlewares::init(info, resp), ReplyItem::Actor(ctx) => PipelineState::Handler( - WaitingResponse { stream: PipelineResponse::Context(ctx), _s: PhantomData }), + WaitingResponse { stream: PipelineResponse::Context(ctx), + _s: PhantomData, _h: PhantomData }), ReplyItem::Future(fut) => PipelineState::Handler( - WaitingResponse { stream: PipelineResponse::Response(fut), _s: PhantomData }), + WaitingResponse { stream: PipelineResponse::Response(fut), + _s: PhantomData, _h: PhantomData }), } } - fn poll(mut self, info: &mut PipelineInfo) -> Result, PipelineState> { + fn poll(mut self, info: &mut PipelineInfo) -> Result, PipelineState> + { let stream = mem::replace(&mut self.stream, PipelineResponse::None); match stream { @@ -430,15 +440,16 @@ impl WaitingResponse { } /// Middlewares response executor -struct RunMiddlewares { +struct RunMiddlewares { curr: usize, fut: Option>>, _s: PhantomData, + _h: PhantomData, } -impl RunMiddlewares { +impl RunMiddlewares { - fn init(info: &mut PipelineInfo, mut resp: HttpResponse) -> PipelineState + fn init(info: &mut PipelineInfo, mut resp: HttpResponse) -> PipelineState { if info.count == 0 { return ProcessResponse::init(resp); @@ -462,20 +473,23 @@ impl RunMiddlewares { }, Response::Future(fut) => { return PipelineState::RunMiddlewares( - RunMiddlewares { curr: curr, fut: Some(fut), _s: PhantomData }) + RunMiddlewares { curr: curr, fut: Some(fut), + _s: PhantomData, _h: PhantomData }) }, }; } } - fn poll(mut self, info: &mut PipelineInfo) -> Result, PipelineState> { + fn poll(mut self, info: &mut PipelineInfo) -> Result, PipelineState> + { let len = info.mws.len(); loop { // poll latest fut let mut resp = match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => - return Ok(PipelineState::RunMiddlewares(self)), + Ok(Async::NotReady) => { + return Err(PipelineState::RunMiddlewares(self)) + } Ok(Async::Ready(resp)) => { self.curr += 1; resp @@ -506,12 +520,13 @@ impl RunMiddlewares { } } -struct ProcessResponse { +struct ProcessResponse { resp: HttpResponse, iostate: IOState, running: RunningState, drain: Option>, _s: PhantomData, + _h: PhantomData, } #[derive(PartialEq)] @@ -543,21 +558,21 @@ enum IOState { Done, } -impl ProcessResponse { +impl ProcessResponse { #[inline] - fn init(resp: HttpResponse) -> PipelineState + fn init(resp: HttpResponse) -> PipelineState { PipelineState::Response( ProcessResponse{ resp: resp, iostate: IOState::Response, running: RunningState::Running, drain: None, - _s: PhantomData}) + _s: PhantomData, _h: PhantomData}) } fn poll_io(mut self, io: &mut Writer, info: &mut PipelineInfo) - -> Result, PipelineState> + -> Result, PipelineState> { if self.drain.is_none() && self.running != RunningState::Paused { // if task is paused, write buffer is probably full @@ -725,25 +740,28 @@ impl ProcessResponse { } /// Middlewares start executor -struct FinishingMiddlewares { +struct FinishingMiddlewares { resp: HttpResponse, fut: Option>>, _s: PhantomData, + _h: PhantomData, } -impl FinishingMiddlewares { +impl FinishingMiddlewares { - fn init(info: &mut PipelineInfo, resp: HttpResponse) -> PipelineState { + fn init(info: &mut PipelineInfo, resp: HttpResponse) -> PipelineState { if info.count == 0 { Completed::init(info) } else { - match (FinishingMiddlewares{resp: resp, fut: None, _s: PhantomData}).poll(info) { + match (FinishingMiddlewares{resp: resp, fut: None, + _s: PhantomData, _h: PhantomData}).poll(info) { Ok(st) | Err(st) => st, } } } - fn poll(mut self, info: &mut PipelineInfo) -> Result, PipelineState> { + fn poll(mut self, info: &mut PipelineInfo) -> Result, PipelineState> + { loop { // poll latest fut let not_ready = if let Some(ref mut fut) = self.fut { @@ -782,24 +800,26 @@ impl FinishingMiddlewares { } } -struct Completed(PhantomData); +struct Completed(PhantomData, PhantomData); -impl Completed { +impl Completed { #[inline] - fn init(info: &mut PipelineInfo) -> PipelineState { + fn init(info: &mut PipelineInfo) -> PipelineState { if info.context.is_none() { PipelineState::None } else { - PipelineState::Completed(Completed(PhantomData)) + PipelineState::Completed(Completed(PhantomData, PhantomData)) } } #[inline] - fn poll(self, info: &mut PipelineInfo) -> Result, PipelineState> { + fn poll(self, info: &mut PipelineInfo) -> Result, PipelineState> { match info.poll_context() { - Ok(Async::NotReady) => Ok(PipelineState::Completed(Completed(PhantomData))), - Ok(Async::Ready(())) => Ok(PipelineState::None), + Ok(Async::NotReady) => + Ok(PipelineState::Completed(Completed(PhantomData, PhantomData))), + Ok(Async::Ready(())) => + Ok(PipelineState::None), Err(_) => Ok(PipelineState::Error), } } @@ -813,11 +833,11 @@ mod tests { use tokio_core::reactor::Core; use futures::future::{lazy, result}; - impl PipelineState { + impl PipelineState { fn is_none(&self) -> Option { if let PipelineState::None = *self { Some(true) } else { None } } - fn completed(self) -> Option> { + fn completed(self) -> Option> { if let PipelineState::Completed(c) = self { Some(c) } else { None } } } @@ -831,14 +851,14 @@ mod tests { fn test_completed() { Core::new().unwrap().run(lazy(|| { let mut info = PipelineInfo::new(HttpRequest::default()); - Completed::init(&mut info).is_none().unwrap(); + Completed::<(), Inner<()>>::init(&mut info).is_none().unwrap(); let req = HttpRequest::default(); let mut ctx = HttpContext::new(req.clone(), MyActor); let addr: Address<_> = ctx.address(); let mut info = PipelineInfo::new(req); info.context = Some(Box::new(ctx)); - let mut state = Completed::init(&mut info).completed().unwrap(); + let mut state = Completed::<(), Inner<()>>::init(&mut info).completed().unwrap(); let st = state.poll(&mut info).ok().unwrap(); let pp = Pipeline(info, st); diff --git a/src/router.rs b/src/router.rs index bbb701623..e5fc091fb 100644 --- a/src/router.rs +++ b/src/router.rs @@ -54,8 +54,11 @@ impl Router { srv: ServerSettings::default() })), resources) } + #[allow(mutable_transmutes)] pub(crate) fn set_server_settings(&mut self, settings: ServerSettings) { - Rc::get_mut(&mut self.0).unwrap().srv = settings; + let inner: &Inner = self.0.as_ref(); + let inner: &mut Inner = unsafe{mem::transmute(inner)}; + inner.srv = settings; } /// Router prefix From 1d195a2cf2668e6f02ef1c2b491db29e81bda3d3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Dec 2017 09:16:50 -0800 Subject: [PATCH 221/279] make Pipeline private --- src/lib.rs | 1 - src/pipeline.rs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ec1aebe48..e453a2013 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -170,7 +170,6 @@ pub mod dev { pub use handler::Handler; pub use json::JsonBody; pub use router::{Router, Pattern}; - pub use pipeline::Pipeline; pub use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; pub use param::{FromParam, Params}; pub use httprequest::UrlEncoded; diff --git a/src/pipeline.rs b/src/pipeline.rs index 80c1a19c2..473f2f9bd 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -17,11 +17,11 @@ use httpresponse::HttpResponse; use middleware::{Middleware, Finished, Started, Response}; use application::Inner; -pub trait PipelineHandler { +pub(crate) trait PipelineHandler { fn handle(&mut self, req: HttpRequest) -> Reply; } -pub struct Pipeline(PipelineInfo, PipelineState); +pub(crate) struct Pipeline(PipelineInfo, PipelineState); enum PipelineState { None, From 3d3e4dae9a0cdd43f1d943a04b48ff96f2eff5ef Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Dec 2017 11:33:04 -0800 Subject: [PATCH 222/279] refactor IntoHttpHandler trait --- src/application.rs | 24 +++++++++++++++--------- src/channel.rs | 7 ++----- src/router.rs | 14 ++++---------- src/server.rs | 15 ++++++--------- src/test.rs | 8 ++++---- 5 files changed, 31 insertions(+), 37 deletions(-) diff --git a/src/application.rs b/src/application.rs index 925dbb014..04c150520 100644 --- a/src/application.rs +++ b/src/application.rs @@ -37,12 +37,11 @@ impl PipelineHandler for Inner { } } +#[cfg(test)] impl HttpApplication { - #[cfg(test)] pub(crate) fn run(&mut self, req: HttpRequest) -> Reply { self.inner.borrow_mut().handle(req) } - #[cfg(test)] pub(crate) fn prepare_request(&self, req: HttpRequest) -> HttpRequest { req.with_state(Rc::clone(&self.state), self.router.clone()) } @@ -60,15 +59,12 @@ impl HttpHandler for HttpApplication { Err(req) } } - - fn server_settings(&mut self, settings: ServerSettings) { - self.router.set_server_settings(settings); - } } struct ApplicationParts { state: S, prefix: String, + settings: ServerSettings, default: Resource, resources: HashMap>>, external: HashMap, @@ -89,6 +85,7 @@ impl Application<()> { parts: Some(ApplicationParts { state: (), prefix: "/".to_owned(), + settings: ServerSettings::default(), default: Resource::default_not_found(), resources: HashMap::new(), external: HashMap::new(), @@ -116,6 +113,7 @@ impl Application where S: 'static { parts: Some(ApplicationParts { state: state, prefix: "/".to_owned(), + settings: ServerSettings::default(), default: Resource::default_not_found(), resources: HashMap::new(), external: HashMap::new(), @@ -279,7 +277,7 @@ impl Application where S: 'static { resources.insert(pattern, None); } - let (router, resources) = Router::new(prefix, resources); + let (router, resources) = Router::new(prefix, parts.settings, resources); let inner = Rc::new(RefCell::new( Inner { @@ -301,7 +299,11 @@ impl Application where S: 'static { impl IntoHttpHandler for Application { type Handler = HttpApplication; - fn into_handler(mut self) -> HttpApplication { + fn into_handler(mut self, settings: ServerSettings) -> HttpApplication { + { + let parts = self.parts.as_mut().expect("Use after finish"); + parts.settings = settings; + } self.finish() } } @@ -309,7 +311,11 @@ impl IntoHttpHandler for Application { impl<'a, S: 'static> IntoHttpHandler for &'a mut Application { type Handler = HttpApplication; - fn into_handler(self) -> HttpApplication { + fn into_handler(self, settings: ServerSettings) -> HttpApplication { + { + let parts = self.parts.as_mut().expect("Use after finish"); + parts.settings = settings; + } self.finish() } } diff --git a/src/channel.rs b/src/channel.rs index 01c18a527..37bbe771b 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -19,9 +19,6 @@ pub trait HttpHandler: 'static { /// Handle request fn handle(&mut self, req: HttpRequest) -> Result, HttpRequest>; - - /// Set server settings - fn server_settings(&mut self, settings: ServerSettings) {} } pub trait HttpHandlerTask { @@ -39,13 +36,13 @@ pub trait IntoHttpHandler { type Handler: HttpHandler; /// Convert into `HttpHandler` object. - fn into_handler(self) -> Self::Handler; + fn into_handler(self, settings: ServerSettings) -> Self::Handler; } impl IntoHttpHandler for T { type Handler = T; - fn into_handler(self) -> Self::Handler { + fn into_handler(self, _: ServerSettings) -> Self::Handler { self } } diff --git a/src/router.rs b/src/router.rs index e5fc091fb..2300dce55 100644 --- a/src/router.rs +++ b/src/router.rs @@ -24,8 +24,9 @@ struct Inner { impl Router { /// Create new router - pub fn new(prefix: &str, map: HashMap>>) - -> (Router, Vec>) + pub fn new(prefix: &str, + settings: ServerSettings, + map: HashMap>>) -> (Router, Vec>) { let prefix = prefix.trim().trim_right_matches('/').to_owned(); let mut named = HashMap::new(); @@ -51,14 +52,7 @@ impl Router { regset: RegexSet::new(&paths).unwrap(), named: named, patterns: patterns, - srv: ServerSettings::default() })), resources) - } - - #[allow(mutable_transmutes)] - pub(crate) fn set_server_settings(&mut self, settings: ServerSettings) { - let inner: &Inner = self.0.as_ref(); - let inner: &mut Inner = unsafe{mem::transmute(inner)}; - inner.srv = settings; + srv: settings })), resources) } /// Router prefix diff --git a/src/server.rs b/src/server.rs index b5c2e8184..7394585ac 100644 --- a/src/server.rs +++ b/src/server.rs @@ -268,11 +268,9 @@ impl HttpServer let ka = self.keep_alive; let factory = Arc::clone(&self.factory); let addr = Arbiter::start(move |ctx: &mut Context<_>| { - let mut apps: Vec<_> = (*factory)() - .into_iter().map(|h| h.into_handler()).collect(); - for app in &mut apps { - app.server_settings(s.clone()); - } + let apps: Vec<_> = (*factory)() + .into_iter() + .map(|h| h.into_handler(s.clone())).collect(); ctx.add_stream(rx); Worker::new(apps, h, ka) }); @@ -482,10 +480,9 @@ impl HttpServer // set server settings let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); let settings = ServerSettings::new(Some(addr), &self.host, secure); - let mut apps: Vec<_> = (*self.factory)().into_iter().map(|h| h.into_handler()).collect(); - for app in &mut apps { - app.server_settings(settings.clone()); - } + let apps: Vec<_> = (*self.factory)() + .into_iter() + .map(|h| h.into_handler(settings.clone())).collect(); self.h = Some(Rc::new(WorkerSettings::new(apps, self.keep_alive))); // start server diff --git a/src/test.rs b/src/test.rs index 11c03f35e..88c2044f5 100644 --- a/src/test.rs +++ b/src/test.rs @@ -16,7 +16,7 @@ use tokio_core::reactor::Core; use net2::TcpBuilder; use error::Error; -use server::HttpServer; +use server::{HttpServer, ServerSettings}; use handler::{Handler, Responder, ReplyItem}; use channel::{HttpHandler, IntoHttpHandler}; use middleware::Middleware; @@ -199,8 +199,8 @@ impl TestApp { impl IntoHttpHandler for TestApp { type Handler = HttpApplication; - fn into_handler(self) -> HttpApplication { - self.app.unwrap().finish() + fn into_handler(mut self, settings: ServerSettings) -> HttpApplication { + self.app.take().unwrap().into_handler(settings) } } @@ -347,7 +347,7 @@ impl TestRequest { let req = HttpRequest::new(method, uri, version, headers, payload); req.as_mut().cookies = cookies; req.as_mut().params = params; - let (router, _) = Router::new::("/", HashMap::new()); + let (router, _) = Router::new::("/", ServerSettings::default(), HashMap::new()); req.with_state(Rc::new(state), router) } From 1baead993a625be2b5a607e2fdf8c9f251b8137e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Dec 2017 11:45:56 -0800 Subject: [PATCH 223/279] call poll_io recursevely aftre drain completion --- src/channel.rs | 3 +-- src/pipeline.rs | 17 ++++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/channel.rs b/src/channel.rs index 37bbe771b..633a05952 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -5,8 +5,7 @@ use bytes::Bytes; use futures::{Future, Poll, Async}; use tokio_io::{AsyncRead, AsyncWrite}; -use h1; -use h2; +use {h1, h2}; use error::Error; use h1writer::Writer; use httprequest::HttpRequest; diff --git a/src/pipeline.rs b/src/pipeline.rs index 473f2f9bd..e8d739428 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -711,8 +711,16 @@ impl ProcessResponse { // flush io but only if we need to if self.running == RunningState::Paused || self.drain.is_some() { match io.poll_completed() { - Ok(Async::Ready(_)) => - self.running.resume(), + Ok(Async::Ready(_)) => { + self.running.resume(); + + // resolve drain futures + if let Some(tx) = self.drain.take() { + let _ = tx.send(()); + } + // restart io processing + return self.poll_io(io, info); + }, Ok(Async::NotReady) => return Err(PipelineState::Response(self)), Err(err) => { @@ -723,11 +731,6 @@ impl ProcessResponse { } } - // drain futures - if let Some(tx) = self.drain.take() { - let _ = tx.send(()); - } - // response is completed match self.iostate { IOState::Done => { From 491d43aa8c4374a895808186a85005466fab5666 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Dec 2017 11:49:36 -0800 Subject: [PATCH 224/279] update tests --- src/httprequest.rs | 7 ++++--- src/router.rs | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index 6d763e2f3..ce0667ba0 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -644,6 +644,7 @@ mod tests { use router::Pattern; use resource::Resource; use test::TestRequest; + use server::ServerSettings; #[test] fn test_debug() { @@ -720,7 +721,7 @@ mod tests { resource.name("index"); let mut map = HashMap::new(); map.insert(Pattern::new("index", "/{key}/"), Some(resource)); - let (router, _) = Router::new("", map); + let (router, _) = Router::new("", ServerSettings::default(), map); assert!(router.recognize(&mut req).is_some()); assert_eq!(req.match_info().get("key"), Some("value")); @@ -822,7 +823,7 @@ mod tests { resource.name("index"); let mut map = HashMap::new(); map.insert(Pattern::new("index", "/user/{name}.{ext}"), Some(resource)); - let (router, _) = Router::new("", map); + let (router, _) = Router::new("", ServerSettings::default(), map); assert!(router.has_route("/user/test.html")); assert!(!router.has_route("/test/unknown")); @@ -849,7 +850,7 @@ mod tests { resource.name("index"); let mut map = HashMap::new(); map.insert(Pattern::new("youtube", "https://youtube.com/watch/{video_id}"), None); - let (router, _) = Router::new::<()>("", map); + let (router, _) = Router::new::<()>("", ServerSettings::default(), map); assert!(!router.has_route("https://youtube.com/watch/unknown")); let req = req.with_state(Rc::new(()), router); diff --git a/src/router.rs b/src/router.rs index 2300dce55..92eb01c3e 100644 --- a/src/router.rs +++ b/src/router.rs @@ -312,7 +312,7 @@ mod tests { routes.insert(Pattern::new("", "/v{val}/{val2}/index.html"), Some(Resource::default())); routes.insert(Pattern::new("", "/v/{tail:.*}"), Some(Resource::default())); routes.insert(Pattern::new("", "{test}/index.html"), Some(Resource::default())); - let (rec, _) = Router::new::<()>("", routes); + let (rec, _) = Router::new::<()>("", ServerSettings::default(), routes); let mut req = HttpRequest::new( Method::GET, Uri::from_str("/name").unwrap(), From 6ea894547db17c3c5e90f764f57226a9aea244b9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Dec 2017 14:04:13 -0800 Subject: [PATCH 225/279] better application handling, fix url_for method for routes with prefix --- guide/src/qs_3.md | 16 ++++++--- guide/src/qs_5.md | 3 +- src/application.rs | 38 ++++++++++++++++++--- src/httprequest.rs | 23 ++++++++++++- src/router.rs | 85 ++++++++++++++++++++++++++++++---------------- 5 files changed, 123 insertions(+), 42 deletions(-) diff --git a/guide/src/qs_3.md b/guide/src/qs_3.md index 38383084b..c970b3efe 100644 --- a/guide/src/qs_3.md +++ b/guide/src/qs_3.md @@ -10,7 +10,11 @@ Also it stores application specific state that is shared across all handlers within same application. Application acts as namespace for all routes, i.e all routes for specific application -has same url path prefix: +has same url path prefix. Application prefix always contains laading "/" slash. +If supplied prefix does not contain leading slash, it get inserted. +Prefix should consists of valud path segments. i.e for application with prefix `/app` +any request with following paths `/app`, `/app/` or `/app/test` would match, +but path `/application` would not match. ```rust,ignore # extern crate actix_web; @@ -21,14 +25,14 @@ has same url path prefix: # } # fn main() { let app = Application::new() - .prefix("/prefix") + .prefix("/app") .resource("/index.html", |r| r.method(Method::GET).f(index)) .finish() # } ``` -In this example application with `/prefix` prefix and `index.html` resource -get created. This resource is available as on `/prefix/index.html` url. +In this example application with `/app` prefix and `index.html` resource +get created. This resource is available as on `/app/index.html` url. For more information check [*URL Matching*](./qs_5.html#using-a-application-prefix-to-compose-applications) section. @@ -56,6 +60,10 @@ fn main() { ``` All `/app1` requests route to first application, `/app2` to second and then all other to third. +Applications get matched based on registration order, if application with more general +prefix is registered before less generic, that would effectively block less generic +application to get matched. For example if *application* with prefix "/" get registered +as first application, it would match all incoming requests. ## State diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index dba0b6370..6a3a452c0 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -445,7 +445,6 @@ fn main() { ## Using a Application Prefix to Compose Applications The `Applicaiton::prefix()`" method allows to set specific application prefix. -If route_prefix is supplied to the include method, it must be a string. This prefix represents a resource prefix that will be prepended to all resource patterns added by the resource configuration. This can be used to help mount a set of routes at a different location than the included callable's author intended while still maintaining the same @@ -471,7 +470,7 @@ fn main() { In the above example, the *show_users* route will have an effective route pattern of */users/show* instead of */show* because the application's prefix argument will be prepended -to the pattern. The route will then only match if the URL path is /users/show, +to the pattern. The route will then only match if the URL path is */users/show*, and when the `HttpRequest.url_for()` function is called with the route name show_users, it will generate a URL with that same path. diff --git a/src/application.rs b/src/application.rs index 04c150520..0fb44bedd 100644 --- a/src/application.rs +++ b/src/application.rs @@ -50,7 +50,13 @@ impl HttpApplication { impl HttpHandler for HttpApplication { fn handle(&mut self, req: HttpRequest) -> Result, HttpRequest> { - if req.path().starts_with(&self.prefix) { + let m = { + let path = req.path(); + path.starts_with(&self.prefix) && ( + path.len() == self.prefix.len() || + path.split_at(self.prefix.len()).1.starts_with('/')) + }; + if m { let inner = Rc::clone(&self.inner); let req = req.with_state(Rc::clone(&self.state), self.router.clone()); @@ -126,11 +132,14 @@ impl Application where S: 'static { /// /// Only requests that matches application's prefix get processed by this application. /// Application prefix always contains laading "/" slash. If supplied prefix - /// does not contain leading slash, it get inserted. + /// does not contain leading slash, it get inserted. Prefix should + /// consists of valud path segments. i.e for application with + /// prefix `/app` any request with following paths `/app`, `/app/` or `/app/test` + /// would match, but path `/application` would not match. /// - /// Inthe following example only requests with "/app/" path prefix - /// get handled. Request with path "/app/test/" will be handled, - /// but request with path "/other/..." will return *NOT FOUND* + /// In the following example only requests with "/app/" path prefix + /// get handled. Request with path "/app/test/" would be handled, + /// but request with path "/application" or "/other/..." would return *NOT FOUND* /// /// ```rust /// # extern crate actix_web; @@ -387,4 +396,23 @@ mod tests { let resp = app.run(req); assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); } + + #[test] + fn test_prefix() { + let mut app = Application::new() + .prefix("/test") + .resource("/blah", |r| r.h(httpcodes::HTTPOk)) + .finish(); + let req = TestRequest::with_uri("/test").finish(); + let resp = app.handle(req); + assert!(resp.is_ok()); + + let req = TestRequest::with_uri("/test/").finish(); + let resp = app.handle(req); + assert!(resp.is_ok()); + + let req = TestRequest::with_uri("/testing").finish(); + let resp = app.handle(req); + assert!(resp.is_err()); + } } diff --git a/src/httprequest.rs b/src/httprequest.rs index ce0667ba0..96c36dbb3 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -823,7 +823,7 @@ mod tests { resource.name("index"); let mut map = HashMap::new(); map.insert(Pattern::new("index", "/user/{name}.{ext}"), Some(resource)); - let (router, _) = Router::new("", ServerSettings::default(), map); + let (router, _) = Router::new("/", ServerSettings::default(), map); assert!(router.has_route("/user/test.html")); assert!(!router.has_route("/test/unknown")); @@ -840,6 +840,27 @@ mod tests { assert_eq!(url.ok().unwrap().as_str(), "http://www.rust-lang.org/user/test.html"); } + #[test] + fn test_url_for_with_prefix() { + let mut headers = HeaderMap::new(); + headers.insert(header::HOST, + header::HeaderValue::from_static("www.rust-lang.org")); + let req = HttpRequest::new( + Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); + + let mut resource = Resource::<()>::default(); + resource.name("index"); + let mut map = HashMap::new(); + map.insert(Pattern::new("index", "/user/{name}.{ext}"), Some(resource)); + let (router, _) = Router::new("/prefix/", ServerSettings::default(), map); + assert!(router.has_route("/user/test.html")); + assert!(!router.has_route("/prefix/user/test.html")); + + let req = req.with_state(Rc::new(()), router); + let url = req.url_for("index", &["test", "html"]); + assert_eq!(url.ok().unwrap().as_str(), "http://www.rust-lang.org/prefix/user/test.html"); + } + #[test] fn test_url_for_external() { let req = HttpRequest::new( diff --git a/src/router.rs b/src/router.rs index 92eb01c3e..eb1027fb3 100644 --- a/src/router.rs +++ b/src/router.rs @@ -16,6 +16,7 @@ pub struct Router(Rc); struct Inner { prefix: String, + prefix_len: usize, regset: RegexSet, named: HashMap, patterns: Vec, @@ -47,8 +48,10 @@ impl Router { } } + let len = prefix.len(); (Router(Rc::new( Inner{ prefix: prefix, + prefix_len: len, regset: RegexSet::new(&paths).unwrap(), named: named, patterns: patterns, @@ -71,7 +74,10 @@ impl Router { pub fn recognize(&self, req: &mut HttpRequest) -> Option { let mut idx = None; { - let path = &req.path()[self.0.prefix.len()..]; + if self.0.prefix_len > req.path().len() { + return None + } + let path = &req.path()[self.0.prefix_len..]; if path.is_empty() { if let Some(i) = self.0.regset.matches("/").into_iter().next() { idx = Some(i); @@ -82,7 +88,7 @@ impl Router { } if let Some(idx) = idx { - let path: &str = unsafe{ mem::transmute(&req.path()[self.0.prefix.len()..]) }; + let path: &str = unsafe{ mem::transmute(&req.path()[self.0.prefix_len..]) }; self.0.patterns[idx].update_match_info(path, req); return Some(idx) } else { @@ -91,13 +97,17 @@ impl Router { } /// Check if application contains matching route. + /// + /// This method does not take `prefix` into account. + /// For example if prefix is `/test` and router contains route `/name`, + /// following path would be recognizable `/test/name` but `has_route()` call + /// would return `false`. pub fn has_route(&self, path: &str) -> bool { - let p = &path[self.0.prefix.len()..]; - if p.is_empty() { + if path.is_empty() { if self.0.regset.matches("/").into_iter().next().is_some() { return true } - } else if self.0.regset.matches(p).into_iter().next().is_some() { + } else if self.0.regset.matches(path).into_iter().next().is_some() { return true } false @@ -205,12 +215,11 @@ impl Pattern { { let mut iter = elements.into_iter(); let mut path = if let Some(prefix) = prefix { - let mut path = String::from(prefix); - path.push('/'); - path + format!("{}/", prefix) } else { String::new() }; + println!("TEST: {:?} {:?}", path, prefix); for el in &self.elements { match *el { PatternElement::Str(ref s) => path.push_str(s), @@ -297,11 +306,9 @@ impl Hash for Pattern { #[cfg(test)] mod tests { - use regex::Regex; use super::*; - use http::{Uri, Version, Method}; - use http::header::HeaderMap; - use std::str::FromStr; + use regex::Regex; + use test::TestRequest; #[test] fn test_recognizer() { @@ -314,45 +321,63 @@ mod tests { routes.insert(Pattern::new("", "{test}/index.html"), Some(Resource::default())); let (rec, _) = Router::new::<()>("", ServerSettings::default(), routes); - let mut req = HttpRequest::new( - Method::GET, Uri::from_str("/name").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); + let mut req = TestRequest::with_uri("/name").finish(); assert!(rec.recognize(&mut req).is_some()); assert!(req.match_info().is_empty()); - let mut req = HttpRequest::new( - Method::GET, Uri::from_str("/name/value").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); + let mut req = TestRequest::with_uri("/name/value").finish(); assert!(rec.recognize(&mut req).is_some()); assert_eq!(req.match_info().get("val").unwrap(), "value"); assert_eq!(&req.match_info()["val"], "value"); - let mut req = HttpRequest::new( - Method::GET, Uri::from_str("/name/value2/index.html").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); + let mut req = TestRequest::with_uri("/name/value2/index.html").finish(); assert!(rec.recognize(&mut req).is_some()); assert_eq!(req.match_info().get("val").unwrap(), "value2"); - let mut req = HttpRequest::new( - Method::GET, Uri::from_str("/vtest/ttt/index.html").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); + let mut req = TestRequest::with_uri("/vtest/ttt/index.html").finish(); assert!(rec.recognize(&mut req).is_some()); assert_eq!(req.match_info().get("val").unwrap(), "test"); assert_eq!(req.match_info().get("val2").unwrap(), "ttt"); - let mut req = HttpRequest::new( - Method::GET, Uri::from_str("/v/blah-blah/index.html").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); + let mut req = TestRequest::with_uri("/v/blah-blah/index.html").finish(); assert!(rec.recognize(&mut req).is_some()); assert_eq!(req.match_info().get("tail").unwrap(), "blah-blah/index.html"); - let mut req = HttpRequest::new( - Method::GET, Uri::from_str("/bbb/index.html").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); + let mut req = TestRequest::with_uri("/bbb/index.html").finish(); assert!(rec.recognize(&mut req).is_some()); assert_eq!(req.match_info().get("test").unwrap(), "bbb"); } + #[test] + fn test_recognizer_with_prefix() { + let mut routes = HashMap::new(); + routes.insert(Pattern::new("", "/name"), Some(Resource::default())); + routes.insert(Pattern::new("", "/name/{val}"), Some(Resource::default())); + let (rec, _) = Router::new::<()>("/test", ServerSettings::default(), routes); + + let mut req = TestRequest::with_uri("/name").finish(); + assert!(rec.recognize(&mut req).is_none()); + + let mut req = TestRequest::with_uri("/test/name").finish(); + assert!(rec.recognize(&mut req).is_some()); + + let mut req = TestRequest::with_uri("/test/name/value").finish(); + assert!(rec.recognize(&mut req).is_some()); + assert_eq!(req.match_info().get("val").unwrap(), "value"); + assert_eq!(&req.match_info()["val"], "value"); + + // same patterns + let mut routes = HashMap::new(); + routes.insert(Pattern::new("", "/name"), Some(Resource::default())); + routes.insert(Pattern::new("", "/name/{val}"), Some(Resource::default())); + let (rec, _) = Router::new::<()>("/test2", ServerSettings::default(), routes); + + let mut req = TestRequest::with_uri("/name").finish(); + assert!(rec.recognize(&mut req).is_none()); + let mut req = TestRequest::with_uri("/test2/name").finish(); + assert!(rec.recognize(&mut req).is_some()); + } + fn assert_parse(pattern: &str, expected_re: &str) -> Regex { let (re_str, _) = Pattern::parse(pattern); assert_eq!(&*re_str, expected_re); From 2d769f805a049e7780c307273322c626c582d6d3 Mon Sep 17 00:00:00 2001 From: Alban Minassian Date: Sat, 30 Dec 2017 12:21:34 +0100 Subject: [PATCH 226/279] add diesel+postgresql link --- examples/diesel/README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/diesel/README.md b/examples/diesel/README.md index 129c4fbe3..2aa30c5db 100644 --- a/examples/diesel/README.md +++ b/examples/diesel/README.md @@ -1,15 +1,20 @@ +# diesel + Diesel's `Getting Started` guide using SQLite for Actix web ## Usage install `diesel_cli` -``` +```bash cargo install diesel_cli --no-default-features --features sqlite ``` +```bash +echo "DATABASE_URL=file:test.db" > .env +diesel migration run +``` -``` -$ echo "DATABASE_URL=file:test.db" > .env -$ diesel migration run -``` +## Postgresql + +You will also find another complete example of diesel+postgresql on [https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Rust/actix](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Rust/actix) \ No newline at end of file From a166fc82f40cb7221195c90b9783ce8fe07cd051 Mon Sep 17 00:00:00 2001 From: ami44 Date: Sat, 30 Dec 2017 15:24:12 +0100 Subject: [PATCH 227/279] add json-rust example --- examples/json/Cargo.toml | 5 +++-- examples/json/README.md | 38 ++++++++++++++++++++++++++++++++++++++ examples/json/src/main.rs | 35 +++++++++++++++++++++++++++++++---- 3 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 examples/json/README.md diff --git a/examples/json/Cargo.toml b/examples/json/Cargo.toml index 468b04900..7eb3155e7 100644 --- a/examples/json/Cargo.toml +++ b/examples/json/Cargo.toml @@ -11,6 +11,7 @@ env_logger = "*" serde = "1.0" serde_json = "1.0" serde_derive = "1.0" +json = "*" -actix = "^0.3.1" -actix-web = { git = "https://github.com/actix/actix-web.git" } +actix = "^0.3.5" +actix-web = { git = "https://github.com/actix/actix-web", features=["signal"] } diff --git a/examples/json/README.md b/examples/json/README.md new file mode 100644 index 000000000..3451ce87e --- /dev/null +++ b/examples/json/README.md @@ -0,0 +1,38 @@ +# json + +Json's `Getting Started` guide using json (serde-json or json-rust) for Actix web + +## Usage + +### server + +```bash +cd actix-web/examples/json +cargo run +# Started http server: 127.0.0.1:8080 +``` + +### client + +With [Postman](https://www.getpostman.com/) or [Rested](moz-extension://60daeb1c-5b1b-4afd-9842-0579ed34dfcb/dist/index.html) + +- POST / (embed serde-json): + + - method : ``POST`` + - url : ``http://127.0.0.1:8080/`` + - header : ``Content-Type`` = ``application/json`` + - body (raw) : ``{"name": "Test user", "number": 100}`` + +- POST /manual (manual serde-json): + + - method : ``POST`` + - url : ``http://127.0.0.1:8080/manual`` + - header : ``Content-Type`` = ``application/json`` + - body (raw) : ``{"name": "Test user", "number": 100}`` + +- POST /mjsonrust (manual json-rust): + + - method : ``POST`` + - url : ``http://127.0.0.1:8080/mjsonrust`` + - header : ``Content-Type`` = ``application/json`` + - body (raw) : ``{"name": "Test user", "number": 100}`` (you can also test ``{notjson}``) diff --git a/examples/json/src/main.rs b/examples/json/src/main.rs index c49bfa152..5b116fc6b 100644 --- a/examples/json/src/main.rs +++ b/examples/json/src/main.rs @@ -5,10 +5,16 @@ extern crate futures; extern crate env_logger; extern crate serde_json; #[macro_use] extern crate serde_derive; +#[macro_use] extern crate json; + use actix_web::*; use bytes::BytesMut; use futures::{Future, Stream}; +use json::JsonValue; + +use actix::Arbiter; +use actix::actors::signal::{ProcessSignals, Subscribe}; #[derive(Debug, Serialize, Deserialize)] struct MyObj { @@ -16,7 +22,7 @@ struct MyObj { number: i32, } -/// This handler uses `HttpRequest::json()` for loading json object. +/// This handler uses `HttpRequest::json()` for loading serde json object. fn index(mut req: HttpRequest) -> Box> { req.json() .from_err() // convert all errors into `Error` @@ -30,7 +36,7 @@ fn index(mut req: HttpRequest) -> Box> { const MAX_SIZE: usize = 262_144; // max payload size is 256k -/// This handler manually load request payload and parse json +/// This handler manually load request payload and parse serde json fn index_manual(mut req: HttpRequest) -> Box> { // readany() returns asynchronous stream of Bytes objects req.payload_mut().readany() @@ -52,27 +58,48 @@ fn index_manual(mut req: HttpRequest) -> Box(&body)?; Ok(httpcodes::HTTPOk.build().json(obj)?) // <- send response }) .responder() } +/// This handler manually load request payload and parse json-rust +fn index_mjsonrust(mut req: HttpRequest) -> Box> { + req.payload_mut().readany().concat2() + .from_err() + .and_then(|body| { + // body is loaded, now we can deserialize json-rust + let result = json::parse(std::str::from_utf8(&body).unwrap()); // return Result + let injson: JsonValue = match result { Ok(v) => v, Err(e) => object!{"err" => e.to_string() } }; + Ok(HttpResponse::build(StatusCode::OK) + .content_type("application/json") + .body(injson.dump()).unwrap()) + + }) + .responder() +} + fn main() { ::std::env::set_var("RUST_LOG", "actix_web=info"); let _ = env_logger::init(); let sys = actix::System::new("json-example"); - HttpServer::new(|| { + let addr = HttpServer::new(|| { Application::new() // enable logger .middleware(middleware::Logger::default()) .resource("/manual", |r| r.method(Method::POST).f(index_manual)) + .resource("/mjsonrust", |r| r.method(Method::POST).f(index_mjsonrust)) .resource("/", |r| r.method(Method::POST).f(index))}) .bind("127.0.0.1:8080").unwrap() + .shutdown_timeout(1) .start(); + let signals = Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); + println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); } From e93af57fa7269a1c52725b8e78235f80141f639f Mon Sep 17 00:00:00 2001 From: ami44 Date: Sat, 30 Dec 2017 16:07:39 +0100 Subject: [PATCH 228/279] add json example link --- README.md | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 70c5d1cc9..51f6ce2a6 100644 --- a/README.md +++ b/README.md @@ -28,18 +28,18 @@ fn main() { ## Features - * Supported *HTTP/1.x* and *HTTP/2.0* protocols - * Streaming and pipelining - * Keep-alive and slow requests handling - * [WebSockets](https://actix.github.io/actix-web/actix_web/ws/index.html) - * Transparent content compression/decompression (br, gzip, deflate) - * Configurable request routing - * Graceful server shutdown - * Multipart streams - * Middlewares ([Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging), - [Session](https://actix.github.io/actix-web/guide/qs_10.html#user-sessions), - [DefaultHeaders](https://actix.github.io/actix-web/guide/qs_10.html#default-headers)) - * Built on top of [Actix](https://github.com/actix/actix). +* Supported *HTTP/1.x* and *HTTP/2.0* protocols +* Streaming and pipelining +* Keep-alive and slow requests handling +* [WebSockets](https://actix.github.io/actix-web/actix_web/ws/index.html) +* Transparent content compression/decompression (br, gzip, deflate) +* Configurable request routing +* Graceful server shutdown +* Multipart streams +* Middlewares ([Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging), + [Session](https://actix.github.io/actix-web/guide/qs_10.html#user-sessions), + [DefaultHeaders](https://actix.github.io/actix-web/guide/qs_10.html#default-headers)) +* Built on top of [Actix](https://github.com/actix/actix). ## Benchmarks @@ -56,17 +56,15 @@ Some basic benchmarks could be found in this [respository](https://github.com/fa * [SSL / HTTP/2.0](https://github.com/actix/actix-web/tree/master/examples/tls/) * [Tcp/Websocket chat](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/) * [SockJS Server](https://github.com/actix/actix-sockjs) +* [Json](https://github.com/actix/actix-web/tree/master/examples/json/) ## License This project is licensed under either of - * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) - * MIT license ([LICENSE-MIT](LICENSE-MIT) or - http://opensource.org/licenses/MIT) +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) at your option. - [![Analytics](https://ga-beacon.appspot.com/UA-110322332-2/actix-web/readme?flat&useReferer)](https://github.com/igrigorik/ga-beacon) From d7d9e8c0e9f68ee83daf80b67efd3f24f3364479 Mon Sep 17 00:00:00 2001 From: ami44 Date: Sat, 30 Dec 2017 16:08:18 +0100 Subject: [PATCH 229/279] update json example --- examples/json/README.md | 12 +++++++++++- examples/json/client.py | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/examples/json/README.md b/examples/json/README.md index 3451ce87e..167c3909f 100644 --- a/examples/json/README.md +++ b/examples/json/README.md @@ -12,7 +12,7 @@ cargo run # Started http server: 127.0.0.1:8080 ``` -### client +### web client With [Postman](https://www.getpostman.com/) or [Rested](moz-extension://60daeb1c-5b1b-4afd-9842-0579ed34dfcb/dist/index.html) @@ -36,3 +36,13 @@ With [Postman](https://www.getpostman.com/) or [Rested](moz-extension://60daeb1c - url : ``http://127.0.0.1:8080/mjsonrust`` - header : ``Content-Type`` = ``application/json`` - body (raw) : ``{"name": "Test user", "number": 100}`` (you can also test ``{notjson}``) + +### python client + +- ``pip install aiohttp`` +- ``python client.py`` + +if ubuntu : + +- ``pip3 install aiohttp`` +- ``python3 client.py`` diff --git a/examples/json/client.py b/examples/json/client.py index 31429443d..e89ffe096 100644 --- a/examples/json/client.py +++ b/examples/json/client.py @@ -11,6 +11,7 @@ async def req(): data=json.dumps({"name": "Test user", "number": 100}), headers={"content-type": "application/json"}) print(str(resp)) + print(await resp.text()) assert 200 == resp.status From a1a77600c662299c5d62fa0aaef988f5ce174942 Mon Sep 17 00:00:00 2001 From: ami44 Date: Sat, 30 Dec 2017 16:09:39 +0100 Subject: [PATCH 230/279] add README example/multipart --- examples/multipart/Cargo.toml | 4 ++-- examples/multipart/README.md | 24 ++++++++++++++++++++++++ examples/multipart/src/main.rs | 7 ++++++- 3 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 examples/multipart/README.md diff --git a/examples/multipart/Cargo.toml b/examples/multipart/Cargo.toml index 03ab65294..049ca76c5 100644 --- a/examples/multipart/Cargo.toml +++ b/examples/multipart/Cargo.toml @@ -10,5 +10,5 @@ path = "src/main.rs" [dependencies] env_logger = "*" futures = "0.1" -actix = "^0.3.4" -actix-web = { git = "https://github.com/actix/actix-web.git" } +actix = "^0.3.5" +actix-web = { git = "https://github.com/actix/actix-web", features=["signal"] } diff --git a/examples/multipart/README.md b/examples/multipart/README.md new file mode 100644 index 000000000..348d28687 --- /dev/null +++ b/examples/multipart/README.md @@ -0,0 +1,24 @@ +# multipart + +Multipart's `Getting Started` guide for Actix web + +## Usage + +### server + +```bash +cd actix-web/examples/multipart +cargo run (or ``cargo watch -x run``) +# Started http server: 127.0.0.1:8080 +``` + +### client + +- ``pip install aiohttp`` +- ``python client.py`` +- you must see in server console multipart fields + +if ubuntu : + +- ``pip3 install aiohttp`` +- ``python3 client.py`` diff --git a/examples/multipart/src/main.rs b/examples/multipart/src/main.rs index 1af329c2f..51bf5799f 100644 --- a/examples/multipart/src/main.rs +++ b/examples/multipart/src/main.rs @@ -9,6 +9,8 @@ use actix_web::*; use futures::{Future, Stream}; use futures::future::{result, Either}; +use actix::Arbiter; +use actix::actors::signal::{ProcessSignals, Subscribe}; fn index(mut req: HttpRequest) -> Box> { @@ -46,13 +48,16 @@ fn main() { let _ = env_logger::init(); let sys = actix::System::new("multipart-example"); - HttpServer::new( + let addr = HttpServer::new( || Application::new() .middleware(middleware::Logger::default()) // <- logger .resource("/multipart", |r| r.method(Method::POST).a(index))) .bind("127.0.0.1:8080").unwrap() .start(); + let signals = Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); + println!("Starting http server: 127.0.0.1:8080"); let _ = sys.run(); } From 12345004ddd2e9f99cdc927e9ca4d006e7b94a64 Mon Sep 17 00:00:00 2001 From: ami44 Date: Sat, 30 Dec 2017 16:10:00 +0100 Subject: [PATCH 231/279] add README examples/signals --- examples/signals/README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/examples/signals/README.md b/examples/signals/README.md index 0d2597fed..4dde40273 100644 --- a/examples/signals/README.md +++ b/examples/signals/README.md @@ -1,4 +1,17 @@ - # Signals This example shows how to handle unix signals and properly stop http server + +## Usage + +```bash +cd actix-web/examples/signal +cargo run (or ``cargo watch -x run``) +# Started http server: 127.0.0.1:8080 +# CTRL+C +# INFO:actix_web::server: SIGINT received, exiting +# INFO:actix_web::worker: Shutting down http worker, 0 connections +# INFO:actix_web::worker: Shutting down http worker, 0 connections +# INFO:actix_web::worker: Shutting down http worker, 0 connections +# INFO:actix_web::worker: Shutting down http worker, 0 connections +``` \ No newline at end of file From 8e580ef7b90c1dc7c4113b8e8897ea53f3aff2a5 Mon Sep 17 00:00:00 2001 From: ami44 Date: Sat, 30 Dec 2017 16:10:29 +0100 Subject: [PATCH 232/279] add README examples/template_tera --- examples/template_tera/Cargo.toml | 4 ++-- examples/template_tera/README.md | 17 +++++++++++++++++ examples/template_tera/src/main.rs | 8 +++++++- 3 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 examples/template_tera/README.md diff --git a/examples/template_tera/Cargo.toml b/examples/template_tera/Cargo.toml index b5ce86cad..36e8d8e55 100644 --- a/examples/template_tera/Cargo.toml +++ b/examples/template_tera/Cargo.toml @@ -5,6 +5,6 @@ authors = ["Nikolay Kim "] [dependencies] env_logger = "0.4" -actix = "^0.3.1" -actix-web = { git = "https://github.com/actix/actix-web.git" } +actix = "^0.3.5" +actix-web = { git = "https://github.com/actix/actix-web", features=["signal"] } tera = "*" diff --git a/examples/template_tera/README.md b/examples/template_tera/README.md new file mode 100644 index 000000000..35829599f --- /dev/null +++ b/examples/template_tera/README.md @@ -0,0 +1,17 @@ +# template_tera + +Minimal example of using the template [tera](https://github.com/Keats/tera) that displays a form. + +## Usage + +### server + +```bash +cd actix-web/examples/template_tera +cargo run (or ``cargo watch -x run``) +# Started http server: 127.0.0.1:8080 +``` + +### web client + +- [http://localhost:8080](http://localhost:8080) diff --git a/examples/template_tera/src/main.rs b/examples/template_tera/src/main.rs index 946c78bf7..3857d489f 100644 --- a/examples/template_tera/src/main.rs +++ b/examples/template_tera/src/main.rs @@ -4,6 +4,8 @@ extern crate env_logger; #[macro_use] extern crate tera; use actix_web::*; +use actix::Arbiter; +use actix::actors::signal::{ProcessSignals, Subscribe}; struct State { template: tera::Tera, // <- store tera template in application state @@ -30,7 +32,7 @@ fn main() { let _ = env_logger::init(); let sys = actix::System::new("tera-example"); - HttpServer::new(|| { + let addr = HttpServer::new(|| { let tera = compile_templates!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")); Application::with_state(State{template: tera}) @@ -40,6 +42,10 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); + // Subscribe to unix signals + let signals = Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); + println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); } From a1dc5a6bd18b5dfbce2ff979b2d703f050c124da Mon Sep 17 00:00:00 2001 From: ami44 Date: Sat, 30 Dec 2017 16:24:50 +0100 Subject: [PATCH 233/279] update examples/tls/README --- examples/tls/Cargo.toml | 4 ++-- examples/tls/README.md | 15 +++++++++++++-- examples/tls/src/main.rs | 8 +++++++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/examples/tls/Cargo.toml b/examples/tls/Cargo.toml index fbb22fb77..eda5e5fc8 100644 --- a/examples/tls/Cargo.toml +++ b/examples/tls/Cargo.toml @@ -9,5 +9,5 @@ path = "src/main.rs" [dependencies] env_logger = "0.4" -actix = "^0.3.1" -actix-web = { git = "https://github.com/actix/actix-web.git", features=["alpn"] } +actix = { version = "^0.3.5" } +actix-web = { git = "https://github.com/actix/actix-web", features=["signal", "alpn"] } diff --git a/examples/tls/README.md b/examples/tls/README.md index bd1f2400d..1bc9ba3b7 100644 --- a/examples/tls/README.md +++ b/examples/tls/README.md @@ -1,5 +1,16 @@ # tls example -To start server use command: `cargo run` +## Usage -Test command: `curl -v https://127.0.0.1:8080/index.html --compress -k` +### server + +```bash +cd actix-web/examples/tls +cargo run (or ``cargo watch -x run``) +# Started http server: 127.0.0.1:8443 +``` + +### web client + +- curl: ``curl -v https://127.0.0.1:8443/index.html --compress -k`` +- browser: [https://127.0.0.1:8443/index.html](https://127.0.0.1:8080/index.html) diff --git a/examples/tls/src/main.rs b/examples/tls/src/main.rs index 8dce633e6..bf0c3eed0 100644 --- a/examples/tls/src/main.rs +++ b/examples/tls/src/main.rs @@ -7,6 +7,8 @@ use std::fs::File; use std::io::Read; use actix_web::*; +use actix::Arbiter; +use actix::actors::signal::{ProcessSignals, Subscribe}; /// somple handle fn index(req: HttpRequest) -> Result { @@ -29,7 +31,7 @@ fn main() { file.read_to_end(&mut pkcs12).unwrap(); let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap().parse("12345").unwrap(); - HttpServer::new( + let addr = HttpServer::new( || Application::new() // enable logger .middleware(middleware::Logger::default()) @@ -45,6 +47,10 @@ fn main() { .bind("127.0.0.1:8443").unwrap() .start_ssl(&pkcs12).unwrap(); + // Subscribe to unix signals + let signals = Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); + println!("Started http server: 127.0.0.1:8443"); let _ = sys.run(); } From df393df5472827713ba33aa2e353e6ffc3a70e9f Mon Sep 17 00:00:00 2001 From: ami44 Date: Sat, 30 Dec 2017 16:50:17 +0100 Subject: [PATCH 234/279] move example/basic.rs to examples/basic --- README.md | 2 +- examples/basic/Cargo.toml | 10 ++++++++++ examples/basic/README.md | 19 +++++++++++++++++++ examples/{basic.rs => basic/src/main.rs} | 10 ++++++++-- 4 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 examples/basic/Cargo.toml create mode 100644 examples/basic/README.md rename examples/{basic.rs => basic/src/main.rs} (90%) diff --git a/README.md b/README.md index 51f6ce2a6..76306ec67 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Some basic benchmarks could be found in this [respository](https://github.com/fa ## Examples -* [Basic](https://github.com/actix/actix-web/tree/master/examples/basic.rs) +* [Basic](https://github.com/actix/actix-web/tree/master/examples/basic/) * [Stateful](https://github.com/actix/actix-web/tree/master/examples/state.rs) * [Mulitpart streams](https://github.com/actix/actix-web/tree/master/examples/multipart/) * [Simple websocket session](https://github.com/actix/actix-web/tree/master/examples/websocket.rs) diff --git a/examples/basic/Cargo.toml b/examples/basic/Cargo.toml new file mode 100644 index 000000000..6bb442e41 --- /dev/null +++ b/examples/basic/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "basic" +version = "0.1.0" +authors = ["Nikolay Kim "] + +[dependencies] +futures = "*" +env_logger = "0.4" +actix = "^0.3.5" +actix-web = { git = "https://github.com/actix/actix-web", features=["signal"] } diff --git a/examples/basic/README.md b/examples/basic/README.md new file mode 100644 index 000000000..45772e915 --- /dev/null +++ b/examples/basic/README.md @@ -0,0 +1,19 @@ +# basic + +## Usage + +### server + +```bash +cd actix-web/examples/basic +cargo run +# Started http server: 127.0.0.1:8080 +``` + +### web client + +- [http://localhost:8080/index.html](http://localhost:8080/index.html) +- [http://localhost:8080/async/bob](http://localhost:8080/async/bob) +- [http://localhost:8080/user/bob/](http://localhost:8080/user/bob/) plain/text download +- [http://localhost:8080/test](http://localhost:8080/test) (return status switch GET or POST or other) +- [http://localhost:8080/static/index.html](http://localhost:8080/static/index.html) \ No newline at end of file diff --git a/examples/basic.rs b/examples/basic/src/main.rs similarity index 90% rename from examples/basic.rs rename to examples/basic/src/main.rs index 7328a5a96..46fe20c6b 100644 --- a/examples/basic.rs +++ b/examples/basic/src/main.rs @@ -8,6 +8,8 @@ extern crate futures; use futures::Stream; use actix_web::*; +use actix::Arbiter; +use actix::actors::signal::{ProcessSignals, Subscribe}; use actix_web::middleware::RequestSession; use futures::future::{FutureResult, result}; @@ -57,7 +59,7 @@ fn main() { let _ = env_logger::init(); let sys = actix::System::new("basic-example"); - HttpServer::new( + let addr = HttpServer::new( || Application::new() // enable logger .middleware(middleware::Logger::default()) @@ -82,7 +84,7 @@ fn main() { })) // static files .resource("/static/{tail:.*}", - |r| r.h(fs::StaticFiles::new("tail", "examples/static/", true))) + |r| r.h(fs::StaticFiles::new("tail", "../static/", true))) // redirect .resource("/", |r| r.method(Method::GET).f(|req| { println!("{:?}", req); @@ -94,6 +96,10 @@ fn main() { .bind("0.0.0.0:8080").unwrap() .start(); + // Subscribe to unix signals + let signals = Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); + println!("Starting http server: 127.0.0.1:8080"); let _ = sys.run(); } From 87188e1505686d5a2ec73155121e2df8a85e3e6c Mon Sep 17 00:00:00 2001 From: ami44 Date: Sat, 30 Dec 2017 16:50:49 +0100 Subject: [PATCH 235/279] minor fix examples/websocket-chat --- examples/websocket-chat/Cargo.toml | 4 ++-- examples/websocket-chat/README.md | 19 ++++++++----------- examples/websocket-chat/src/main.rs | 11 ++++++++--- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/examples/websocket-chat/Cargo.toml b/examples/websocket-chat/Cargo.toml index 2a5c92925..6ca5f0ad3 100644 --- a/examples/websocket-chat/Cargo.toml +++ b/examples/websocket-chat/Cargo.toml @@ -24,5 +24,5 @@ serde = "1.0" serde_json = "1.0" serde_derive = "1.0" -actix = "^0.3.1" -actix-web = { git = "https://github.com/actix/actix-web.git" } +actix = { version = "^0.3.5" } +actix-web = { git = "https://github.com/actix/actix-web", features=["signal"] } diff --git a/examples/websocket-chat/README.md b/examples/websocket-chat/README.md index 2e75343b1..28d734dd3 100644 --- a/examples/websocket-chat/README.md +++ b/examples/websocket-chat/README.md @@ -1,6 +1,6 @@ # Websocket chat example -This is extension of the +This is extension of the [actix chat example](https://github.com/actix/actix/tree/master/examples/chat) Added features: @@ -9,18 +9,16 @@ Added features: * Chat server runs in separate thread * Tcp listener runs in separate thread - ## Server Chat server listens for incoming tcp connections. Server can access several types of message: - * `\list` - list all available rooms - * `\join name` - join room, if room does not exist, create new one - * `\name name` - set session name - * `some message` - just string, send messsage to all peers in same room - * client has to send heartbeat `Ping` messages, if server does not receive a heartbeat - message for 10 seconds connection gets droppped - +* `\list` - list all available rooms +* `\join name` - join room, if room does not exist, create new one +* `\name name` - set session name +* `some message` - just string, send messsage to all peers in same room +* client has to send heartbeat `Ping` messages, if server does not receive a heartbeat message for 10 seconds connection gets droppped + To start server use command: `cargo run --bin server` ## Client @@ -29,7 +27,6 @@ Client connects to server. Reads input from stdin and sends to server. To run client use command: `cargo run --bin client` - ## WebSocket Browser Client -Open url: http://localhost:8080/ +Open url: [http://localhost:8080/](http://localhost:8080/) diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs index 8d0d55d98..39936b358 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -17,6 +17,8 @@ use std::time::Instant; use actix::*; use actix_web::*; +use actix::Arbiter; +use actix::actors::signal::{ProcessSignals, Subscribe}; mod codec; mod server; @@ -175,7 +177,6 @@ impl StreamHandler for WsChatSession } } - fn main() { let _ = env_logger::init(); let sys = actix::System::new("websocket-example"); @@ -192,9 +193,8 @@ fn main() { Ok(()) })); - // Create Http server with websocket support - HttpServer::new( + let addr = HttpServer::new( move || { // Websocket sessions state let state = WsChatSessionState { addr: server.clone() }; @@ -216,5 +216,10 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); + // Subscribe to unix signals + let signals = Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); + + println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); } From 76b03851e65b7ac5ca56b062f3d9695c47a713b4 Mon Sep 17 00:00:00 2001 From: ami44 Date: Sat, 30 Dec 2017 21:05:03 +0100 Subject: [PATCH 236/279] fix examples - disable signal if windows --- examples/basic/src/main.rs | 11 ++++++----- examples/json/src/main.rs | 13 +++++++------ examples/multipart/src/main.rs | 10 ++++++---- examples/signals/src/main.rs | 9 +++++---- examples/template_tera/src/main.rs | 12 +++++++----- examples/tls/src/main.rs | 11 ++++++----- examples/websocket-chat/src/main.rs | 10 +++++----- 7 files changed, 42 insertions(+), 34 deletions(-) diff --git a/examples/basic/src/main.rs b/examples/basic/src/main.rs index 46fe20c6b..9895ac946 100644 --- a/examples/basic/src/main.rs +++ b/examples/basic/src/main.rs @@ -7,9 +7,9 @@ extern crate env_logger; extern crate futures; use futures::Stream; +use actix::*; use actix_web::*; -use actix::Arbiter; -use actix::actors::signal::{ProcessSignals, Subscribe}; +#[cfg(target_os = "linux")] use actix::actors::signal::{ProcessSignals, Subscribe}; use actix_web::middleware::RequestSession; use futures::future::{FutureResult, result}; @@ -96,9 +96,10 @@ fn main() { .bind("0.0.0.0:8080").unwrap() .start(); - // Subscribe to unix signals - let signals = Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); + if cfg!(target_os = "linux") { // Subscribe to unix signals + let signals = Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); + } println!("Starting http server: 127.0.0.1:8080"); let _ = sys.run(); diff --git a/examples/json/src/main.rs b/examples/json/src/main.rs index 5b116fc6b..907eaf51b 100644 --- a/examples/json/src/main.rs +++ b/examples/json/src/main.rs @@ -7,15 +7,14 @@ extern crate serde_json; #[macro_use] extern crate serde_derive; #[macro_use] extern crate json; - +use actix::*; use actix_web::*; +#[cfg(target_os = "linux")] use actix::actors::signal::{ProcessSignals, Subscribe}; + use bytes::BytesMut; use futures::{Future, Stream}; use json::JsonValue; -use actix::Arbiter; -use actix::actors::signal::{ProcessSignals, Subscribe}; - #[derive(Debug, Serialize, Deserialize)] struct MyObj { name: String, @@ -97,8 +96,10 @@ fn main() { .shutdown_timeout(1) .start(); - let signals = Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); + if cfg!(target_os = "linux") { // Subscribe to unix signals + let signals = Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); + } println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); diff --git a/examples/multipart/src/main.rs b/examples/multipart/src/main.rs index 51bf5799f..84259ec1a 100644 --- a/examples/multipart/src/main.rs +++ b/examples/multipart/src/main.rs @@ -6,11 +6,11 @@ extern crate futures; use actix::*; use actix_web::*; +#[cfg(target_os = "linux")] use actix::actors::signal::{ProcessSignals, Subscribe}; + use futures::{Future, Stream}; use futures::future::{result, Either}; -use actix::Arbiter; -use actix::actors::signal::{ProcessSignals, Subscribe}; fn index(mut req: HttpRequest) -> Box> { @@ -55,8 +55,10 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); - let signals = Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); + if cfg!(target_os = "linux") { // Subscribe to unix signals + let signals = Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); + } println!("Starting http server: 127.0.0.1:8080"); let _ = sys.run(); diff --git a/examples/signals/src/main.rs b/examples/signals/src/main.rs index 77b6b2f74..2571fdb4f 100644 --- a/examples/signals/src/main.rs +++ b/examples/signals/src/main.rs @@ -5,7 +5,7 @@ extern crate env_logger; use actix::*; use actix_web::*; -use actix::actors::signal::{ProcessSignals, Subscribe}; +#[cfg(target_os = "linux")] use actix::actors::signal::{ProcessSignals, Subscribe}; struct MyWebSocket; @@ -34,9 +34,10 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); - // Subscribe to unix signals - let signals = Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); + if cfg!(target_os = "linux") { // Subscribe to unix signals + let signals = Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); + } println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); diff --git a/examples/template_tera/src/main.rs b/examples/template_tera/src/main.rs index 3857d489f..c4f962e73 100644 --- a/examples/template_tera/src/main.rs +++ b/examples/template_tera/src/main.rs @@ -3,9 +3,10 @@ extern crate actix_web; extern crate env_logger; #[macro_use] extern crate tera; + +use actix::*; use actix_web::*; -use actix::Arbiter; -use actix::actors::signal::{ProcessSignals, Subscribe}; +#[cfg(target_os = "linux")] use actix::actors::signal::{ProcessSignals, Subscribe}; struct State { template: tera::Tera, // <- store tera template in application state @@ -42,9 +43,10 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); - // Subscribe to unix signals - let signals = Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); + if cfg!(target_os = "linux") { // Subscribe to unix signals + let signals = Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); + } println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); diff --git a/examples/tls/src/main.rs b/examples/tls/src/main.rs index bf0c3eed0..30625fdba 100644 --- a/examples/tls/src/main.rs +++ b/examples/tls/src/main.rs @@ -6,9 +6,9 @@ extern crate env_logger; use std::fs::File; use std::io::Read; +use actix::*; use actix_web::*; -use actix::Arbiter; -use actix::actors::signal::{ProcessSignals, Subscribe}; +#[cfg(target_os = "linux")] use actix::actors::signal::{ProcessSignals, Subscribe}; /// somple handle fn index(req: HttpRequest) -> Result { @@ -47,9 +47,10 @@ fn main() { .bind("127.0.0.1:8443").unwrap() .start_ssl(&pkcs12).unwrap(); - // Subscribe to unix signals - let signals = Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); + if cfg!(target_os = "linux") { // Subscribe to unix signals + let signals = Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); + } println!("Started http server: 127.0.0.1:8443"); let _ = sys.run(); diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs index 39936b358..a0236cbed 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -17,8 +17,7 @@ use std::time::Instant; use actix::*; use actix_web::*; -use actix::Arbiter; -use actix::actors::signal::{ProcessSignals, Subscribe}; +#[cfg(target_os = "linux")] use actix::actors::signal::{ProcessSignals, Subscribe}; mod codec; mod server; @@ -216,9 +215,10 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); - // Subscribe to unix signals - let signals = Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); + if cfg!(target_os = "linux") { // Subscribe to unix signals + let signals = Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); + } println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); From f1f5b23e7788ae85c654fd02278485d38209ccea Mon Sep 17 00:00:00 2001 From: ami44 Date: Sat, 30 Dec 2017 21:08:12 +0100 Subject: [PATCH 237/279] fix readme examples/signal --- examples/signals/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/signals/README.md b/examples/signals/README.md index 4dde40273..368182e7f 100644 --- a/examples/signals/README.md +++ b/examples/signals/README.md @@ -1,6 +1,6 @@ # Signals -This example shows how to handle unix signals and properly stop http server +This example shows how to handle Unix signals and properly stop http server. This example does not work with Windows. ## Usage From c998c75515e410aeada79e24e975d29c0386d28c Mon Sep 17 00:00:00 2001 From: ami44 Date: Sat, 30 Dec 2017 21:08:54 +0100 Subject: [PATCH 238/279] move examples/state.rs to examples/state --- README.md | 2 +- examples/state/Cargo.toml | 10 ++++++++++ examples/state/README.md | 15 +++++++++++++++ examples/{state.rs => state/src/main.rs} | 8 +++++++- 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 examples/state/Cargo.toml create mode 100644 examples/state/README.md rename examples/{state.rs => state/src/main.rs} (88%) diff --git a/README.md b/README.md index 76306ec67..84ec0cd58 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Some basic benchmarks could be found in this [respository](https://github.com/fa ## Examples * [Basic](https://github.com/actix/actix-web/tree/master/examples/basic/) -* [Stateful](https://github.com/actix/actix-web/tree/master/examples/state.rs) +* [Stateful](https://github.com/actix/actix-web/tree/master/examples/state/) * [Mulitpart streams](https://github.com/actix/actix-web/tree/master/examples/multipart/) * [Simple websocket session](https://github.com/actix/actix-web/tree/master/examples/websocket.rs) * [Tera templates](https://github.com/actix/actix-web/tree/master/examples/template_tera/) diff --git a/examples/state/Cargo.toml b/examples/state/Cargo.toml new file mode 100644 index 000000000..c71fc86f8 --- /dev/null +++ b/examples/state/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "state" +version = "0.1.0" +authors = ["Nikolay Kim "] + +[dependencies] +futures = "*" +env_logger = "0.4" +actix = "^0.3.5" +actix-web = { git = "https://github.com/actix/actix-web", features=["signal"] } diff --git a/examples/state/README.md b/examples/state/README.md new file mode 100644 index 000000000..127ed2a0f --- /dev/null +++ b/examples/state/README.md @@ -0,0 +1,15 @@ +# state + +## Usage + +### server + +```bash +cd actix-web/examples/state +cargo run +# Started http server: 127.0.0.1:8080 +``` + +### web client + +- [http://localhost:8080/](http://localhost:8080/) diff --git a/examples/state.rs b/examples/state/src/main.rs similarity index 88% rename from examples/state.rs rename to examples/state/src/main.rs index dfa201f0c..c713e68ec 100644 --- a/examples/state.rs +++ b/examples/state/src/main.rs @@ -9,6 +9,7 @@ extern crate env_logger; use actix::*; use actix_web::*; +#[cfg(target_os = "linux")] use actix::actors::signal::{ProcessSignals, Subscribe}; use std::cell::Cell; struct AppState { @@ -60,7 +61,7 @@ fn main() { let _ = env_logger::init(); let sys = actix::System::new("ws-example"); - HttpServer::new( + let addr = HttpServer::new( || Application::with_state(AppState{counter: Cell::new(0)}) // enable logger .middleware(middleware::Logger::default()) @@ -73,6 +74,11 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); + if cfg!(target_os = "linux") { // Subscribe to unix signals + let signals = Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); + } + println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); } From 73e2773a1042222f16c3836947ae8329f5ccc1e8 Mon Sep 17 00:00:00 2001 From: ami44 Date: Sat, 30 Dec 2017 21:13:23 +0100 Subject: [PATCH 239/279] minor fix guide/ --- guide/src/qs_1.md | 4 +- guide/src/qs_3_5.md | 12 +++--- guide/src/qs_5.md | 102 ++++++++++++++++++++++---------------------- 3 files changed, 59 insertions(+), 59 deletions(-) diff --git a/guide/src/qs_1.md b/guide/src/qs_1.md index cfef105e2..3f629bd1a 100644 --- a/guide/src/qs_1.md +++ b/guide/src/qs_1.md @@ -1,6 +1,6 @@ # Quick start -Before you can start writing a actix web application, you’ll need a version of Rust installed. +Before you can start writing a actix web application, you’ll need a version of Rust installed. We recommend you use rustup to install or configure such a version. ## Install Rust @@ -31,4 +31,4 @@ cd actix-web cargo run --example basic ``` -Check `examples/` directory for more examples. +Check [examples/](https://github.com/actix/actix-web/tree/master/examples) directory for more examples. diff --git a/guide/src/qs_3_5.md b/guide/src/qs_3_5.md index 312668903..6cce25030 100644 --- a/guide/src/qs_3_5.md +++ b/guide/src/qs_3_5.md @@ -1,8 +1,8 @@ # Server -[*HttpServer*](../actix_web/struct.HttpServer.html) type is responsible for -serving http requests. *HttpServer* accept applicaiton factory as a parameter, -Application factory must have `Send` + `Sync` bounderies. More about that in +[*HttpServer*](../actix_web/struct.HttpServer.html) type is responsible for +serving http requests. *HttpServer* accept application factory as a parameter, +Application factory must have `Send` + `Sync` bounderies. More about that in *multi-threading* section. To bind to specific socket address `bind()` must be used. This method could be called multiple times. To start http server one of the *start* methods could be used. `start()` method start simple server, `start_tls()` or `start_ssl()` @@ -54,7 +54,7 @@ fn main() { .resource("/", |r| r.h(httpcodes::HTTPOk))) .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") .spawn(); - + let _ = addr.call_fut(dev::StopServer{graceful: true}).wait(); // <- Send `StopServer` message to server. } ``` @@ -116,7 +116,7 @@ Note on *HTTP/2.0* protocol over tls without prior knowledge, it requires [tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only `openssl` has `alpn ` support. -Please check [example](https://github.com/actix/actix-web/tree/master/examples/tls) +Please check [example](https://github.com/actix/actix-web/tree/master/examples/tls) for full example. ## Keep-Alive @@ -172,7 +172,7 @@ have specific amount of time to finish serving requests. Workers still alive aft timeout are force dropped. By default shutdown timeout sets to 30 seconds. You can change this parameter with `HttpServer::shutdown_timeout()` method. -You can send stop message to server with server address and specify if you what +You can send stop message to server with server address and specify if you what graceful shutdown or not. `start()` or `spawn()` methods return address of the server. ```rust diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index 6a3a452c0..1aa7edeb0 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -5,35 +5,35 @@ language. *Regex* crate and it's [*RegexSet*](https://doc.rust-lang.org/regex/regex/struct.RegexSet.html) is beeing used for pattern matching. If one of the patterns matches the path information associated with a request, a particular handler object is invoked. A handler is a specific object that implements -`Handler` trait, defined in your application, that receives the request and returns +`Handler` trait, defined in your application, that receives the request and returns a response object. More informatin is available in [handler section](../qs_4.html). ## Resource configuration Resource configuraiton is the act of adding a new resource to an application. -A resource has a name, which acts as an identifier to be used for URL generation. -The name also allows developers to add routes to existing resources. +A resource has a name, which acts as an identifier to be used for URL generation. +The name also allows developers to add routes to existing resources. A resource also has a pattern, meant to match against the *PATH* portion of a *URL*, it does not match against *QUERY* portion (the portion following the scheme and port, e.g., */foo/bar* in the *URL* *http://localhost:8080/foo/bar?q=value*). The [Application::resource](../actix_web/struct.Application.html#method.resource) methods add a single resource to application routing table. This method accepts *path pattern* -and resource configuration funnction. +and resource configuration funnction. ```rust # extern crate actix_web; # use actix_web::*; # use actix_web::httpcodes::*; -# +# # fn index(req: HttpRequest) -> HttpResponse { # unimplemented!() # } -# +# fn main() { Application::new() .resource("/prefix", |r| r.f(index)) - .resource("/user/{name}", + .resource("/user/{name}", |r| r.method(Method::GET).f(|req| HTTPOk)) .finish(); } @@ -45,7 +45,7 @@ fn main() { FnOnce(&mut Resource<_>) -> () ``` -*Configration function* can set name and register specific routes. +*Configration function* can set name and register specific routes. If resource does not contain any route or does not have any matching routes it returns *NOT FOUND* http resources. @@ -56,7 +56,7 @@ New route could be crearted with `Resource::route()` method which returns refere to new *Route* instance. By default *route* does not contain any predicates, so matches all requests and default handler is `HTTPNotFound`. -Application routes incoming requests based on route criteria which is defined during +Application routes incoming requests based on route criteria which is defined during resource registration and route registration. Resource matches all routes it contains in the order that the routes were registered via `Resource::route()`. *Route* can contain any number of *predicates* but only one handler. @@ -78,28 +78,28 @@ fn main() { } ``` -In this example `index` get called for *GET* request, +In this example `index` get called for *GET* request, if request contains `Content-Type` header and value of this header is *text/plain* and path equals to `/test`. Resource calls handle of the first matches route. If resource can not match any route "NOT FOUND" response get returned. [*Resource::route()*](../actix_web/struct.Resource.html#method.route) method returns -[*Route*](../actix_web/struct.Route.html) object. Route can be configured with +[*Route*](../actix_web/struct.Route.html) object. Route can be configured with builder-like pattern. Following configuration methods are available: * [*Route::p()*](../actix_web/struct.Route.html#method.p) method registers new predicate, any number of predicates could be registered for each route. - + * [*Route::f()*](../actix_web/struct.Route.html#method.f) method registers handler function for this route. Only one handler could be registered. Usually handler registeration is the last config operation. Handler fanction could be function or closure and has type `Fn(HttpRequest) -> R + 'static` * [*Route::h()*](../actix_web/struct.Route.html#method.h) method registers handler object - that implements `Handler` trait. This is similar to `f()` method, only one handler could - be registered. Handler registeration is the last config operation. + that implements `Handler` trait. This is similar to `f()` method, only one handler could + be registered. Handler registeration is the last config operation. -* [*Route::a()*](../actix_web/struct.Route.html#method.a) method registers asynchandler +* [*Route::a()*](../actix_web/struct.Route.html#method.a) method registers asynchandler function for this route. Only one handler could be registered. Handler registeration is the last config operation. Handler fanction could be function or closure and has type `Fn(HttpRequest) -> Future + 'static` @@ -110,30 +110,30 @@ The main purpose of route configuration is to match (or not match) the request's against a URL path pattern. `path` represents the path portion of the URL that was requested. The way that *actix* does this is very simple. When a request enters the system, -for each resource configuration registration present in the system, actix checks +for each resource configuration registration present in the system, actix checks the request's path against the pattern declared. *Regex* crate and it's [*RegexSet*](https://doc.rust-lang.org/regex/regex/struct.RegexSet.html) is beeing used for pattern matching. If resource could not be found, *default resource* get used as matched resource. When a route configuration is declared, it may contain route predicate arguments. All route -predicates associated with a route declaration must be `true` for the route configuration to +predicates associated with a route declaration must be `true` for the route configuration to be used for a given request during a check. If any predicate in the set of route predicate arguments provided to a route configuration returns `false` during a check, that route is skipped and route matching continues through the ordered set of routes. If any route matches, the route matching process stops and the handler associated with -route get invoked. +route get invoked. If no route matches after all route patterns are exhausted, *NOT FOUND* response get returned. ## Resource pattern syntax The syntax of the pattern matching language used by the actix in the pattern -argument is straightforward. +argument is straightforward. The pattern used in route configuration may start with a slash character. If the pattern -does not start with a slash character, an implicit slash will be prepended +does not start with a slash character, an implicit slash will be prepended to it at matching time. For example, the following patterns are equivalent: ``` @@ -146,13 +146,13 @@ and: /{foo}/bar/baz ``` -A *variable part* (replacement marker) is specified in the form *{identifier}*, -where this means "accept any characters up to the next slash character and use this -as the name in the `HttpRequest.match_info()` object". +A *variable part* (replacement marker) is specified in the form *{identifier}*, +where this means "accept any characters up to the next slash character and use this +as the name in the `HttpRequest.match_info()` object". A replacement marker in a pattern matches the regular expression `[^{}/]+`. -A match_info is the `Params` object representing the dynamic parts extracted from a +A match_info is the `Params` object representing the dynamic parts extracted from a *URL* based on the routing pattern. It is available as *request.match_info*. For example, the following pattern defines one literal segment (foo) and two replacement markers (baz, and bar): @@ -174,8 +174,8 @@ foo/1/2/ -> No match (trailing slash) bar/abc/def -> First segment literal mismatch ``` -The match for a segment replacement marker in a segment will be done only up to -the first non-alphanumeric character in the segment in the pattern. So, for instance, +The match for a segment replacement marker in a segment will be done only up to +the first non-alphanumeric character in the segment in the pattern. So, for instance, if this route pattern was used: ``` @@ -183,8 +183,8 @@ foo/{name}.html ``` The literal path */foo/biz.html* will match the above route pattern, and the match result -will be `Params{'name': 'biz'}`. However, the literal path */foo/biz* will not match, -because it does not contain a literal *.html* at the end of the segment represented +will be `Params{'name': 'biz'}`. However, the literal path */foo/biz* will not match, +because it does not contain a literal *.html* at the end of the segment represented by *{name}.html* (it only contains biz, not biz.html). To capture both segments, two replacement markers can be used: @@ -193,21 +193,21 @@ To capture both segments, two replacement markers can be used: foo/{name}.{ext} ``` -The literal path */foo/biz.html* will match the above route pattern, and the match -result will be *Params{'name': 'biz', 'ext': 'html'}*. This occurs because there is a +The literal path */foo/biz.html* will match the above route pattern, and the match +result will be *Params{'name': 'biz', 'ext': 'html'}*. This occurs because there is a literal part of *.* (period) between the two replacement markers *{name}* and *{ext}*. Replacement markers can optionally specify a regular expression which will be used to decide whether a path segment should match the marker. To specify that a replacement marker should match only a specific set of characters as defined by a regular expression, you must use a slightly extended form of replacement marker syntax. Within braces, the replacement marker -name must be followed by a colon, then directly thereafter, the regular expression. The default +name must be followed by a colon, then directly thereafter, the regular expression. The default regular expression associated with a replacement marker *[^/]+* matches one or more characters which are not a slash. For example, under the hood, the replacement marker *{foo}* can more verbosely be spelled as *{foo:[^/]+}*. You can change this to be an arbitrary regular expression to match an arbitrary sequence of characters, such as *{foo:\d+}* to match only digits. -Segments must contain at least one character in order to match a segment replacement marker. +Segments must contain at least one character in order to match a segment replacement marker. For example, for the URL */abc/*: * */abc/{foo}* will not match. @@ -233,7 +233,7 @@ The matchdict will look like so (the value is URL-decoded): Params{'bar': 'La Pe\xf1a'} ``` -Literal strings in the path segment should represent the decoded value of the +Literal strings in the path segment should represent the decoded value of the path provided to actix. You don't want to use a URL-encoded value in the pattern. For example, rather than this: @@ -262,12 +262,12 @@ foo/abc/def/a/b/c -> Params{'bar':u'abc', 'tail': 'def/a/b/c'} ## Match information -All values representing matched path segments are available in +All values representing matched path segments are available in [`HttpRequest::match_info`](../actix_web/struct.HttpRequest.html#method.match_info). -Specific value can be received with +Specific value can be received with [`Params::get()`](../actix_web/dev/struct.Params.html#method.get) method. -Any matched parameter can be deserialized into specific type if this type +Any matched parameter can be deserialized into specific type if this type implements `FromParam` trait. For example most of standard integer types implements `FromParam` trait. i.e.: @@ -323,20 +323,20 @@ fn main() { } ``` -List of `FromParam` implementation could be found in +List of `FromParam` implementation could be found in [api docs](../actix_web/dev/trait.FromParam.html#foreign-impls) ## Generating resource URLs -Use the [HttpRequest.url_for()](../actix_web/struct.HttpRequest.html#method.url_for) -method to generate URLs based on resource patterns. For example, if you've configured a +Use the [HttpRequest.url_for()](../actix_web/struct.HttpRequest.html#method.url_for) +method to generate URLs based on resource patterns. For example, if you've configured a resource with the name "foo" and the pattern "{a}/{b}/{c}", you might do this. ```rust # extern crate actix_web; # use actix_web::*; # use actix_web::httpcodes::*; -# +# fn index(req: HttpRequest) -> HttpResponse { let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource HTTPOk.into() @@ -354,7 +354,7 @@ fn main() { This would return something like the string *http://example.com/test/1/2/3* (at least if the current protocol and hostname implied http://example.com). -`url_for()` method return [*Url object*](https://docs.rs/url/1.6.0/url/struct.Url.html) so you +`url_for()` method return [*Url object*](https://docs.rs/url/1.6.0/url/struct.Url.html) so you can modify this url (add query parameters, anchor, etc). `url_for()` could be called only for *named* resources otherwise error get returned. @@ -421,7 +421,7 @@ In this example `/resource`, `//resource///` will be redirected to `/resource/` In this example path normalization handler get registered for all method, but you should not rely on this mechanism to redirect *POST* requests. The redirect of the -slash-appending *Not Found* will turn a *POST* request into a GET, losing any +slash-appending *Not Found* will turn a *POST* request into a GET, losing any *POST* data in the original request. It is possible to register path normalization only for *GET* requests only @@ -444,18 +444,18 @@ fn main() { ## Using a Application Prefix to Compose Applications -The `Applicaiton::prefix()`" method allows to set specific application prefix. -This prefix represents a resource prefix that will be prepended to all resource patterns added +The `Application::prefix()`" method allows to set specific application prefix. +This prefix represents a resource prefix that will be prepended to all resource patterns added by the resource configuration. This can be used to help mount a set of routes at a different location than the included callable's author intended while still maintaining the same -resource names. +resource names. For example: ```rust # extern crate actix_web; # use actix_web::*; -# +# fn show_users(req: HttpRequest) -> HttpResponse { unimplemented!() } @@ -468,18 +468,18 @@ fn main() { } ``` -In the above example, the *show_users* route will have an effective route pattern of -*/users/show* instead of */show* because the application's prefix argument will be prepended +In the above example, the *show_users* route will have an effective route pattern of +*/users/show* instead of */show* because the application's prefix argument will be prepended to the pattern. The route will then only match if the URL path is */users/show*, -and when the `HttpRequest.url_for()` function is called with the route name show_users, +and when the `HttpRequest.url_for()` function is called with the route name show_users, it will generate a URL with that same path. ## Custom route predicates You can think of predicate as simple function that accept *request* object reference -and returns *true* or *false*. Formally predicate is any object that implements +and returns *true* or *false*. Formally predicate is any object that implements [`Predicate`](../actix_web/pred/trait.Predicate.html) trait. Actix provides -several predicates, you can check [functions section](../actix_web/pred/index.html#functions) +several predicates, you can check [functions section](../actix_web/pred/index.html#functions) of api docs. Here is simple predicates that check that request contains specific *header*: From 7962d28a6ddd5e16e62fc725d1e429625ee8b5d9 Mon Sep 17 00:00:00 2001 From: ami44 Date: Sat, 30 Dec 2017 21:47:39 +0100 Subject: [PATCH 240/279] move examples/websocket.rs to examples/websocket --- README.md | 2 +- examples/websocket/Cargo.toml | 14 ++++++++++ examples/websocket/README.md | 27 +++++++++++++++++++ .../{websocket.rs => websocket/src/main.rs} | 11 +++++--- examples/{ => websocket}/websocket-client.py | 0 5 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 examples/websocket/Cargo.toml create mode 100644 examples/websocket/README.md rename examples/{websocket.rs => websocket/src/main.rs} (85%) rename examples/{ => websocket}/websocket-client.py (100%) diff --git a/README.md b/README.md index 84ec0cd58..37fb263ef 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Some basic benchmarks could be found in this [respository](https://github.com/fa * [Basic](https://github.com/actix/actix-web/tree/master/examples/basic/) * [Stateful](https://github.com/actix/actix-web/tree/master/examples/state/) * [Mulitpart streams](https://github.com/actix/actix-web/tree/master/examples/multipart/) -* [Simple websocket session](https://github.com/actix/actix-web/tree/master/examples/websocket.rs) +* [Simple websocket session](https://github.com/actix/actix-web/tree/master/examples/websocket/) * [Tera templates](https://github.com/actix/actix-web/tree/master/examples/template_tera/) * [Diesel integration](https://github.com/actix/actix-web/tree/master/examples/diesel/) * [SSL / HTTP/2.0](https://github.com/actix/actix-web/tree/master/examples/tls/) diff --git a/examples/websocket/Cargo.toml b/examples/websocket/Cargo.toml new file mode 100644 index 000000000..3168601a2 --- /dev/null +++ b/examples/websocket/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "websocket" +version = "0.1.0" +authors = ["Nikolay Kim "] + +[[bin]] +name = "server" +path = "src/main.rs" + +[dependencies] +env_logger = "*" +futures = "0.1" +actix = "^0.3.5" +actix-web = { git = "https://github.com/actix/actix-web.git", features=["signal"] } diff --git a/examples/websocket/README.md b/examples/websocket/README.md new file mode 100644 index 000000000..374e939ac --- /dev/null +++ b/examples/websocket/README.md @@ -0,0 +1,27 @@ +# websockect + +Simple echo websocket server. + +## Usage + +### server + +```bash +cd actix-web/examples/websocket +cargo run +# Started http server: 127.0.0.1:8080 +``` + +### web client + +- [http://localhost:8080/ws/index.html](http://localhost:8080/ws/index.html) + +### python client + +- ``pip install aiohttp`` +- ``python websocket-client.py`` + +if ubuntu : + +- ``pip3 install aiohttp`` +- ``python3 websocket-client.py`` diff --git a/examples/websocket.rs b/examples/websocket/src/main.rs similarity index 85% rename from examples/websocket.rs rename to examples/websocket/src/main.rs index 2a80add1b..a517a18a3 100644 --- a/examples/websocket.rs +++ b/examples/websocket/src/main.rs @@ -10,7 +10,7 @@ extern crate env_logger; use actix::*; use actix_web::*; - +#[cfg(target_os = "linux")] use actix::actors::signal::{ProcessSignals, Subscribe}; /// do websocket handshake and start `MyWebSocket` actor fn ws_index(r: HttpRequest) -> Reply { @@ -60,7 +60,7 @@ fn main() { let _ = env_logger::init(); let sys = actix::System::new("ws-example"); - HttpServer::new( + let _addr = HttpServer::new( || Application::new() // enable logger .middleware(middleware::Logger::default()) @@ -68,11 +68,16 @@ fn main() { .resource("/ws/", |r| r.method(Method::GET).f(ws_index)) // static files .resource("/{tail:.*}", - |r| r.h(fs::StaticFiles::new("tail", "examples/static/", true)))) + |r| r.h(fs::StaticFiles::new("tail", "../static/", true)))) // start http server on 127.0.0.1:8080 .bind("127.0.0.1:8080").unwrap() .start(); + if cfg!(target_os = "linux") { // Subscribe to unix signals + let signals = Arbiter::system_registry().get::(); + signals.send(Subscribe(_addr.subscriber())); + } + println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); } diff --git a/examples/websocket-client.py b/examples/websocket/websocket-client.py similarity index 100% rename from examples/websocket-client.py rename to examples/websocket/websocket-client.py From 5741b8b372baab656123806c1f9f427f309801cf Mon Sep 17 00:00:00 2001 From: ami44 Date: Sat, 30 Dec 2017 22:43:10 +0100 Subject: [PATCH 241/279] add examples/websocket --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 242769d4e..b72da2c8d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,6 +46,7 @@ script: cd examples/diesel && cargo check && cd ../.. cd examples/tls && cargo check && cd ../.. cd examples/websocket-chat && cargo check && cd ../.. + cd examples/websocket && cargo check && cd ../.. fi - | if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then From d8548ad83b5e15988f5bc1e2c83f07a46893258f Mon Sep 17 00:00:00 2001 From: ami44 Date: Sun, 31 Dec 2017 08:12:26 +0100 Subject: [PATCH 242/279] update examples/diesel readme --- examples/diesel/Cargo.toml | 4 ++-- examples/diesel/README.md | 31 +++++++++++++++++++++++++++---- examples/diesel/src/main.rs | 10 +++++++++- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/examples/diesel/Cargo.toml b/examples/diesel/Cargo.toml index 40e78e307..e5bf2bb4d 100644 --- a/examples/diesel/Cargo.toml +++ b/examples/diesel/Cargo.toml @@ -5,8 +5,8 @@ authors = ["Nikolay Kim "] [dependencies] env_logger = "0.4" -actix = "^0.3.1" -actix-web = { git = "https://github.com/actix/actix-web.git" } +actix = "^0.3.5" +actix-web = { git = "https://github.com/actix/actix-web", features=["signal"] } futures = "0.1" uuid = { version = "0.5", features = ["serde", "v4"] } diff --git a/examples/diesel/README.md b/examples/diesel/README.md index 2aa30c5db..922ba1e3b 100644 --- a/examples/diesel/README.md +++ b/examples/diesel/README.md @@ -4,17 +4,40 @@ Diesel's `Getting Started` guide using SQLite for Actix web ## Usage -install `diesel_cli` +### init database sqlite ```bash cargo install diesel_cli --no-default-features --features sqlite -``` - -```bash +cd actix-web/examples/diesel echo "DATABASE_URL=file:test.db" > .env diesel migration run ``` +### server + +```bash +# if ubuntu : sudo apt-get install libsqlite3-dev +# if fedora : sudo dnf install libsqlite3x-devel +cd actix-web/examples/diesel +cargo run (or ``cargo watch -x run``) +# Started http server: 127.0.0.1:8080 +``` + +### web client + +[http://127.0.0.1:8080/NAME](http://127.0.0.1:8080/NAME) + +### sqlite client + +```bash +# if ubuntu : sudo apt-get install sqlite3 +# if fedora : sudo dnf install sqlite3x +sqlite3 test.db +sqlite> .tables +sqlite> select * from users; +``` + + ## Postgresql You will also find another complete example of diesel+postgresql on [https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Rust/actix](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Rust/actix) \ No newline at end of file diff --git a/examples/diesel/src/main.rs b/examples/diesel/src/main.rs index 350a9ee5a..7303690e6 100644 --- a/examples/diesel/src/main.rs +++ b/examples/diesel/src/main.rs @@ -16,8 +16,11 @@ extern crate actix; extern crate actix_web; extern crate env_logger; +use actix::*; use actix_web::*; use actix::prelude::*; +#[cfg(target_os = "linux")] use actix::actors::signal::{ProcessSignals, Subscribe}; + use diesel::prelude::*; use futures::future::Future; @@ -59,7 +62,7 @@ fn main() { }); // Start http server - HttpServer::new(move || { + let _addr = HttpServer::new(move || { Application::with_state(State{db: addr.clone()}) // enable logger .middleware(middleware::Logger::default()) @@ -67,6 +70,11 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); + if cfg!(target_os = "linux") { // Subscribe to unix signals + let signals = Arbiter::system_registry().get::(); + signals.send(Subscribe(_addr.subscriber())); + } + println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); } From 967d3244d73e5afacb219e36ba67e12b8b7bb56b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 31 Dec 2017 13:22:11 -0800 Subject: [PATCH 243/279] fix http/2 support --- examples/tls/src/main.rs | 7 +++-- src/h1writer.rs | 1 - src/h2writer.rs | 56 ++++++++++++++++++++-------------------- src/helpers.rs | 12 ++++++--- src/pipeline.rs | 4 +-- 5 files changed, 43 insertions(+), 37 deletions(-) diff --git a/examples/tls/src/main.rs b/examples/tls/src/main.rs index 30625fdba..3d969d3a4 100644 --- a/examples/tls/src/main.rs +++ b/examples/tls/src/main.rs @@ -8,7 +8,8 @@ use std::io::Read; use actix::*; use actix_web::*; -#[cfg(target_os = "linux")] use actix::actors::signal::{ProcessSignals, Subscribe}; +#[cfg(target_os = "linux")] +use actix::actors::signal::{ProcessSignals, Subscribe}; /// somple handle fn index(req: HttpRequest) -> Result { @@ -47,7 +48,9 @@ fn main() { .bind("127.0.0.1:8443").unwrap() .start_ssl(&pkcs12).unwrap(); - if cfg!(target_os = "linux") { // Subscribe to unix signals + // Subscribe to unix signals + #[cfg(target_os = "linux")] + { let signals = Arbiter::system_registry().get::(); signals.send(Subscribe(addr.subscriber())); } diff --git a/src/h1writer.rs b/src/h1writer.rs index 2deca9d14..011e56ace 100644 --- a/src/h1writer.rs +++ b/src/h1writer.rs @@ -192,7 +192,6 @@ impl Writer for H1Writer { if let Body::Binary(bytes) = body { self.encoder.write(bytes.as_ref())?; - return Ok(WriterState::Done) } else { msg.replace_body(body); } diff --git a/src/h2writer.rs b/src/h2writer.rs index d3091c6f3..51027f7f4 100644 --- a/src/h2writer.rs +++ b/src/h2writer.rs @@ -92,7 +92,7 @@ impl H2Writer { let cap = cmp::min(buffer.len(), CHUNK_SIZE); stream.reserve_capacity(cap); } else { - return Ok(WriterState::Done) + return Ok(WriterState::Pause) } } Err(_) => { @@ -130,9 +130,23 @@ impl Writer for H2Writer { // using helpers::date is quite a lot faster if !msg.headers().contains_key(DATE) { let mut bytes = BytesMut::with_capacity(29); - helpers::date(&mut bytes); - msg.headers_mut().insert( - DATE, HeaderValue::try_from(bytes.freeze()).unwrap()); + helpers::date_value(&mut bytes); + msg.headers_mut().insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap()); + } + + let body = msg.replace_body(Body::Empty); + match body { + Body::Binary(ref bytes) => { + let mut val = BytesMut::new(); + helpers::convert_usize(bytes.len(), &mut val); + let l = val.len(); + msg.headers_mut().insert( + CONTENT_LENGTH, HeaderValue::try_from(val.split_to(l-2).freeze()).unwrap()); + } + Body::Empty => { + msg.headers_mut().insert(CONTENT_LENGTH, HeaderValue::from_static("0")); + }, + _ => (), } let mut resp = Response::new(()); @@ -142,19 +156,6 @@ impl Writer for H2Writer { resp.headers_mut().insert(key, value.clone()); } - match *msg.body() { - Body::Binary(ref bytes) => { - let mut val = BytesMut::new(); - helpers::convert_usize(bytes.len(), &mut val); - resp.headers_mut().insert( - CONTENT_LENGTH, HeaderValue::try_from(val.freeze()).unwrap()); - } - Body::Empty => { - resp.headers_mut().insert(CONTENT_LENGTH, HeaderValue::from_static("0")); - }, - _ => (), - } - match self.respond.send_response(resp, self.flags.contains(Flags::EOF)) { Ok(stream) => self.stream = Some(stream), @@ -162,20 +163,19 @@ impl Writer for H2Writer { return Err(io::Error::new(io::ErrorKind::Other, "err")), } - // trace!("Response: {:?}", msg); + trace!("Response: {:?}", msg); - if msg.body().is_binary() { - if let Body::Binary(bytes) = msg.replace_body(Body::Empty) { - self.flags.insert(Flags::EOF); - self.encoder.write(bytes.as_ref())?; - if let Some(ref mut stream) = self.stream { - stream.reserve_capacity(cmp::min(self.encoder.len(), CHUNK_SIZE)); - } - return Ok(WriterState::Done) + if let Body::Binary(bytes) = body { + self.flags.insert(Flags::EOF); + self.encoder.write(bytes.as_ref())?; + if let Some(ref mut stream) = self.stream { + stream.reserve_capacity(cmp::min(self.encoder.len(), CHUNK_SIZE)); } + Ok(WriterState::Pause) + } else { + msg.replace_body(body); + Ok(WriterState::Done) } - - Ok(WriterState::Done) } fn write(&mut self, payload: &[u8]) -> Result { diff --git a/src/helpers.rs b/src/helpers.rs index 8211b03d1..ba6fd49d2 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -11,9 +11,9 @@ use http::Version; use httprequest::HttpMessage; // "Sun, 06 Nov 1994 08:49:37 GMT".len() -pub const DATE_VALUE_LENGTH: usize = 29; +pub(crate) const DATE_VALUE_LENGTH: usize = 29; -pub fn date(dst: &mut BytesMut) { +pub(crate) fn date(dst: &mut BytesMut) { CACHED.with(|cache| { let mut buf: [u8; 39] = unsafe { mem::uninitialized() }; buf[..6].copy_from_slice(b"date: "); @@ -23,7 +23,13 @@ pub fn date(dst: &mut BytesMut) { }) } -pub fn update_date() { +pub(crate) fn date_value(dst: &mut BytesMut) { + CACHED.with(|cache| { + dst.extend_from_slice(cache.borrow().buffer()); + }) +} + +pub(crate) fn update_date() { CACHED.with(|cache| { cache.borrow_mut().update(); }); diff --git a/src/pipeline.rs b/src/pipeline.rs index e8d739428..f37065f01 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -576,12 +576,10 @@ impl ProcessResponse { { if self.drain.is_none() && self.running != RunningState::Paused { // if task is paused, write buffer is probably full - loop { let result = match mem::replace(&mut self.iostate, IOState::Done) { IOState::Response => { - let result = match io.start(info.req_mut().get_inner(), - &mut self.resp) { + let result = match io.start(info.req_mut().get_inner(), &mut self.resp) { Ok(res) => res, Err(err) => { info.error = Some(err.into()); From cc38b30f7be041b5406a86cd169395f9a27ca15b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 31 Dec 2017 17:26:32 -0800 Subject: [PATCH 244/279] refactor http actor usage --- examples/websocket.rs | 4 +- guide/src/qs_10.md | 2 +- guide/src/qs_14.md | 2 +- guide/src/qs_4.md | 6 +-- guide/src/qs_7.md | 2 +- guide/src/qs_8.md | 8 +-- src/application.rs | 2 +- src/body.rs | 32 +++++------- src/context.rs | 105 +++++++++++++++++++++++++++------------ src/encoding.rs | 29 ++++++----- src/error.rs | 8 --- src/handler.rs | 32 +----------- src/httpcodes.rs | 9 ++-- src/httprequest.rs | 12 +++-- src/httpresponse.rs | 13 +++++ src/json.rs | 2 +- src/middleware/logger.rs | 6 +-- src/payload.rs | 42 ++++++---------- src/pipeline.rs | 97 ++++++++---------------------------- src/router.rs | 5 +- src/test.rs | 7 ++- src/ws.rs | 26 +++++----- 22 files changed, 197 insertions(+), 254 deletions(-) diff --git a/examples/websocket.rs b/examples/websocket.rs index 2a80add1b..3ddd04f58 100644 --- a/examples/websocket.rs +++ b/examples/websocket.rs @@ -13,8 +13,8 @@ use actix_web::*; /// do websocket handshake and start `MyWebSocket` actor -fn ws_index(r: HttpRequest) -> Reply { - ws::start(r, MyWebSocket).into() +fn ws_index(r: HttpRequest) -> Result { + ws::start(r, MyWebSocket) } /// websocket connection is long running connection, it easier diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index 89bf8c6d3..8974f241d 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -72,7 +72,7 @@ Create `Logger` middleware with the specified `format`. Default `Logger` could be created with `default` method, it uses the default format: ```ignore - %a %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i" %T + %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T ``` ```rust # extern crate actix_web; diff --git a/guide/src/qs_14.md b/guide/src/qs_14.md index e9e489be7..26fa4ecfb 100644 --- a/guide/src/qs_14.md +++ b/guide/src/qs_14.md @@ -110,7 +110,7 @@ fn index(req: HttpRequest) -> Box> .and_then(|res| { match res { Ok(user) => Ok(httpcodes::HTTPOk.build().json(user)?), - Err(_) => Ok(httpcodes::HTTPInternalServerError.response()) + Err(_) => Ok(httpcodes::HTTPInternalServerError.into()) } }) .responder() diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index 1a82d51bd..42afb9219 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -65,7 +65,7 @@ impl Handler for MyHandler { /// Handle request fn handle(&mut self, req: HttpRequest) -> Self::Result { self.0 += 1; - httpcodes::HTTPOk.response() + httpcodes::HTTPOk.into() } } # fn main() {} @@ -91,7 +91,7 @@ impl Handler for MyHandler { fn handle(&mut self, req: HttpRequest) -> Self::Result { let num = self.0.load(Ordering::Relaxed) + 1; self.0.store(num, Ordering::Relaxed); - httpcodes::HTTPOk.response() + httpcodes::HTTPOk.into() } } @@ -104,7 +104,7 @@ fn main() { move || { let cloned = inc.clone(); Application::new() - .resource("/", move |r| r.h(MyHandler(cloned))) + .resource("/", move |r| r.h(MyHandler(cloned))) }) .bind("127.0.0.1:8088").unwrap() .start(); diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 5d3447514..7cce5932b 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -246,7 +246,7 @@ fn index(mut req: HttpRequest) -> Box> { .from_err() .and_then(|params| { // <- url encoded parameters println!("==== BODY ==== {:?}", params); - ok(httpcodes::HTTPOk.response()) + ok(httpcodes::HTTPOk.into()) }) .responder() } diff --git a/guide/src/qs_8.md b/guide/src/qs_8.md index 7661b9ad4..53713a205 100644 --- a/guide/src/qs_8.md +++ b/guide/src/qs_8.md @@ -20,10 +20,10 @@ use actix_web::test::TestRequest; fn index(req: HttpRequest) -> HttpResponse { if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { if let Ok(s) = hdr.to_str() { - return httpcodes::HTTPOk.response() + return httpcodes::HTTPOk.into() } } - httpcodes::HTTPBadRequest.response() + httpcodes::HTTPBadRequest.into() } fn main() { @@ -60,7 +60,7 @@ use actix_web::*; use actix_web::test::TestServer; fn index(req: HttpRequest) -> HttpResponse { - httpcodes::HTTPOk.response() + httpcodes::HTTPOk.into() } fn main() { @@ -80,7 +80,7 @@ use actix_web::*; use actix_web::test::TestServer; fn index(req: HttpRequest) -> HttpResponse { - httpcodes::HTTPOk.response() + httpcodes::HTTPOk.into() } /// This function get called by http server. diff --git a/src/application.rs b/src/application.rs index 0fb44bedd..02ae078ba 100644 --- a/src/application.rs +++ b/src/application.rs @@ -128,7 +128,7 @@ impl Application where S: 'static { } } - /// Set application prefix. + /// Set application prefix /// /// Only requests that matches application's prefix get processed by this application. /// Application prefix always contains laading "/" slash. If supplied prefix diff --git a/src/body.rs b/src/body.rs index b9e6676e7..53af6e40e 100644 --- a/src/body.rs +++ b/src/body.rs @@ -5,6 +5,7 @@ use bytes::{Bytes, BytesMut}; use futures::Stream; use error::Error; +use context::ActorHttpContext; /// Type represent streaming body pub type BodyStream = Box>; @@ -18,12 +19,8 @@ pub enum Body { /// Unspecified streaming response. Developer is responsible for setting /// right `Content-Length` or `Transfer-Encoding` headers. Streaming(BodyStream), - /// Upgrade connection. - Upgrade(BodyStream), - /// Special body type for actor streaming response. - StreamingContext, - /// Special body type for actor upgrade response. - UpgradeContext, + /// Special body type for actor response. + Actor(Box), } /// Represents various types of binary body. @@ -51,8 +48,7 @@ impl Body { #[inline] pub fn is_streaming(&self) -> bool { match *self { - Body::Streaming(_) | Body::StreamingContext - | Body::Upgrade(_) | Body::UpgradeContext => true, + Body::Streaming(_) | Body::Actor(_) => true, _ => false } } @@ -83,15 +79,7 @@ impl PartialEq for Body { Body::Binary(ref b2) => b == b2, _ => false, }, - Body::StreamingContext => match *other { - Body::StreamingContext => true, - _ => false, - }, - Body::UpgradeContext => match *other { - Body::UpgradeContext => true, - _ => false, - }, - Body::Streaming(_) | Body::Upgrade(_) => false, + Body::Streaming(_) | Body::Actor(_) => false, } } } @@ -102,9 +90,7 @@ impl fmt::Debug for Body { Body::Empty => write!(f, "Body::Empty"), Body::Binary(ref b) => write!(f, "Body::Binary({:?})", b), Body::Streaming(_) => write!(f, "Body::Streaming(_)"), - Body::Upgrade(_) => write!(f, "Body::Upgrade(_)"), - Body::StreamingContext => write!(f, "Body::StreamingContext"), - Body::UpgradeContext => write!(f, "Body::UpgradeContext"), + Body::Actor(_) => write!(f, "Body::Actor(_)"), } } } @@ -115,6 +101,12 @@ impl From for Body where T: Into{ } } +impl From> for Body { + fn from(ctx: Box) -> Body { + Body::Actor(ctx) + } +} + impl Binary { #[inline] pub fn is_empty(&self) -> bool { diff --git a/src/context.rs b/src/context.rs index f0e80de1a..1cdb6b9b4 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,6 +1,7 @@ use std; +use std::marker::PhantomData; use std::collections::VecDeque; -use futures::{Async, Poll}; +use futures::{Async, Future, Poll}; use futures::sync::oneshot::Sender; use futures::unsync::oneshot; @@ -11,19 +12,18 @@ use actix::dev::{AsyncContextApi, ActorAddressCell, ActorItemsCell, ActorWaitCel Envelope, ToEnvelope, RemoteEnvelope}; use body::{Body, Binary}; -use error::Error; +use error::{Error, Result}; use httprequest::HttpRequest; use httpresponse::HttpResponse; -pub(crate) trait IoContext: 'static { +pub trait ActorHttpContext: 'static { fn disconnected(&mut self); fn poll(&mut self) -> Poll, Error>; } #[derive(Debug)] -pub(crate) enum Frame { - Message(HttpResponse), +pub enum Frame { Payload(Option), Drain(oneshot::Sender<()>), } @@ -31,7 +31,7 @@ pub(crate) enum Frame { /// Http actor execution context pub struct HttpContext where A: Actor>, { - act: A, + act: Option, state: ActorState, modified: bool, items: ActorItemsCell, @@ -39,7 +39,6 @@ pub struct HttpContext where A: Actor>, stream: VecDeque, wait: ActorWaitCell, request: HttpRequest, - streaming: bool, disconnected: bool, } @@ -101,12 +100,15 @@ impl AsyncContextApi for HttpContext where A: Actor } } -impl HttpContext where A: Actor { +impl HttpContext where A: Actor { - pub fn new(req: HttpRequest, actor: A) -> HttpContext - { + pub fn new(req: HttpRequest, actor: A) -> HttpContext { + HttpContext::from_request(req).actor(actor) + } + + pub fn from_request(req: HttpRequest) -> HttpContext { HttpContext { - act: actor, + act: None, state: ActorState::Started, modified: false, items: ActorItemsCell::default(), @@ -114,10 +116,24 @@ impl HttpContext where A: Actor { wait: ActorWaitCell::default(), stream: VecDeque::new(), request: req, - streaming: false, disconnected: false, } } + + pub fn actor(mut self, actor: A) -> HttpContext { + self.act = Some(actor); + self + } + + pub fn with_actor(mut self, actor: A, mut resp: HttpResponse) -> Result { + if self.act.is_some() { + panic!("Actor is set already"); + } + self.act = Some(actor); + + resp.replace_body(Body::Actor(Box::new(self))); + Ok(resp) + } } impl HttpContext where A: Actor { @@ -132,24 +148,12 @@ impl HttpContext where A: Actor { &mut self.request } - /// Send response to peer - pub fn start>(&mut self, response: R) { - let resp = response.into(); - match *resp.body() { - Body::StreamingContext | Body::UpgradeContext => self.streaming = true, - _ => (), - } - self.stream.push_back(Frame::Message(resp)) - } - /// Write payload pub fn write>(&mut self, data: B) { - if self.streaming { - if !self.disconnected { - self.stream.push_back(Frame::Payload(Some(data.into()))) - } + if !self.disconnected { + self.stream.push_back(Frame::Payload(Some(data.into()))); } else { - warn!("Trying to write response body for non-streaming response"); + warn!("Trying to write to disconnected response"); } } @@ -159,11 +163,11 @@ impl HttpContext where A: Actor { } /// Returns drain future - pub fn drain(&mut self) -> oneshot::Receiver<()> { + pub fn drain(&mut self) -> Drain { let (tx, rx) = oneshot::channel(); self.modified = true; self.stream.push_back(Frame::Drain(tx)); - rx + Drain::new(rx) } /// Check if connection still open @@ -193,7 +197,7 @@ impl HttpContext where A: Actor { } } -impl IoContext for HttpContext where A: Actor, S: 'static { +impl ActorHttpContext for HttpContext where A: Actor, S: 'static { fn disconnected(&mut self) { self.items.stop(); @@ -204,8 +208,11 @@ impl IoContext for HttpContext where A: Actor, S: 'sta } fn poll(&mut self) -> Poll, Error> { + if self.act.is_none() { + return Ok(Async::Ready(None)) + } let act: &mut A = unsafe { - std::mem::transmute(&mut self.act as &mut A) + std::mem::transmute(self.act.as_mut().unwrap() as &mut A) }; let ctx: &mut HttpContext = unsafe { std::mem::transmute(self as &mut HttpContext) @@ -303,3 +310,39 @@ impl ToEnvelope for HttpContext RemoteEnvelope::new(msg, tx).into() } } + +impl From> for Body + where A: Actor>, + S: 'static +{ + fn from(ctx: HttpContext) -> Body { + Body::Actor(Box::new(ctx)) + } +} + +pub struct Drain { + fut: oneshot::Receiver<()>, + _a: PhantomData, +} + +impl Drain { + fn new(fut: oneshot::Receiver<()>) -> Self { + Drain { + fut: fut, + _a: PhantomData + } + } +} + +impl ActorFuture for Drain { + type Item = (); + type Error = (); + type Actor = A; + + fn poll(&mut self, + _: &mut A, + _: &mut ::Context) -> Poll + { + self.fut.poll().map_err(|_| ()) + } +} diff --git a/src/encoding.rs b/src/encoding.rs index 3254f6404..e30ba9f40 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -416,8 +416,20 @@ impl PayloadEncoder { resp.headers_mut().remove(CONTENT_LENGTH); TransferEncoding::eof(buf) } - Body::Streaming(_) | Body::StreamingContext => { - if resp.chunked() { + Body::Streaming(_) | Body::Actor(_) => { + if resp.upgrade() { + if version == Version::HTTP_2 { + error!("Connection upgrade is forbidden for HTTP/2"); + } else { + resp.headers_mut().insert( + CONNECTION, HeaderValue::from_static("upgrade")); + } + if encoding != ContentEncoding::Identity { + encoding = ContentEncoding::Identity; + resp.headers_mut().remove(CONTENT_ENCODING); + } + TransferEncoding::eof(buf) + } else if resp.chunked() { resp.headers_mut().remove(CONTENT_LENGTH); if version != Version::HTTP_11 { error!("Chunked transfer encoding is forbidden for {:?}", version); @@ -446,19 +458,6 @@ impl PayloadEncoder { TransferEncoding::eof(buf) } } - Body::Upgrade(_) | Body::UpgradeContext => { - if version == Version::HTTP_2 { - error!("Connection upgrade is forbidden for HTTP/2"); - } else { - resp.headers_mut().insert( - CONNECTION, HeaderValue::from_static("upgrade")); - } - if encoding != ContentEncoding::Identity { - encoding = ContentEncoding::Identity; - resp.headers_mut().remove(CONTENT_ENCODING); - } - TransferEncoding::eof(buf) - } }; resp.replace_body(body); diff --git a/src/error.rs b/src/error.rs index ef4b65c56..31ae69fbb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -114,14 +114,6 @@ impl ResponseError for header::InvalidHeaderValue {} /// `InternalServerError` for `futures::Canceled` impl ResponseError for Canceled {} -/// Internal error -#[doc(hidden)] -#[derive(Fail, Debug)] -#[fail(display="Unexpected task frame")] -pub struct UnexpectedTaskFrame; - -impl ResponseError for UnexpectedTaskFrame {} - /// A set of errors that can occur during parsing HTTP streams #[derive(Fail, Debug)] pub enum ParseError { diff --git a/src/handler.rs b/src/handler.rs index deab468ea..106fecc8e 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,13 +1,11 @@ use std::marker::PhantomData; -use actix::Actor; -use futures::future::{Future, ok, err}; use regex::Regex; +use futures::future::{Future, ok, err}; use http::{header, StatusCode, Error as HttpError}; use body::Body; use error::Error; -use context::{HttpContext, IoContext}; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -69,20 +67,11 @@ pub struct Reply(ReplyItem); pub(crate) enum ReplyItem { Message(HttpResponse), - Actor(Box), Future(Box>), } impl Reply { - /// Create actor response - #[inline] - pub fn actor(ctx: HttpContext) -> Reply - where A: Actor>, S: 'static - { - Reply(ReplyItem::Actor(Box::new(ctx))) - } - /// Create async response #[inline] pub fn async(fut: F) -> Reply @@ -163,25 +152,6 @@ impl> From> for Reply { } } -impl>, S: 'static> Responder for HttpContext -{ - type Item = Reply; - type Error = Error; - - #[inline] - fn respond_to(self, _: HttpRequest) -> Result { - Ok(Reply(ReplyItem::Actor(Box::new(self)))) - } -} - -impl>, S: 'static> From> for Reply { - - #[inline] - fn from(ctx: HttpContext) -> Reply { - Reply(ReplyItem::Actor(Box::new(ctx))) - } -} - impl Responder for Box> where I: Responder + 'static, E: Into + 'static diff --git a/src/httpcodes.rs b/src/httpcodes.rs index abe226cbe..e27c56237 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -54,9 +54,6 @@ impl StaticResponse { pub fn build(&self) -> HttpResponseBuilder { HttpResponse::build(self.0) } - pub fn response(&self) -> HttpResponse { - HttpResponse::new(self.0, Body::Empty) - } pub fn with_reason(self, reason: &'static str) -> HttpResponse { let mut resp = HttpResponse::new(self.0, Body::Empty); resp.set_reason(reason); @@ -92,7 +89,7 @@ impl Responder for StaticResponse { impl From for HttpResponse { fn from(st: StaticResponse) -> Self { - st.response() + HttpResponse::new(st.0, Body::Empty) } } @@ -153,7 +150,7 @@ mod tests { #[test] fn test_response() { - let resp = HTTPOk.response(); + let resp: HttpResponse = HTTPOk.into(); assert_eq!(resp.status(), StatusCode::OK); } @@ -165,7 +162,7 @@ mod tests { #[test] fn test_with_reason() { - let resp = HTTPOk.response(); + let resp: HttpResponse = HTTPOk.into(); assert_eq!(resp.reason(), "OK"); let resp = HTTPBadRequest.with_reason("test"); diff --git a/src/httprequest.rs b/src/httprequest.rs index 96c36dbb3..dd8de37dc 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -135,6 +135,12 @@ impl HttpRequest<()> { impl HttpRequest { + #[inline] + /// Construct new http request with state. + pub fn change_state(self, state: Rc) -> HttpRequest { + HttpRequest(self.0, Some(state), self.2.clone()) + } + #[inline] /// Construct new http request without state. pub(crate) fn clone_without_state(&self) -> HttpRequest { @@ -447,7 +453,7 @@ impl HttpRequest { /// } /// }) /// .finish() // <- Stream::finish() combinator from actix - /// .map(|_| httpcodes::HTTPOk.response()) + /// .map(|_| httpcodes::HTTPOk.into()) /// .responder() /// } /// # fn main() {} @@ -477,7 +483,7 @@ impl HttpRequest { /// .from_err() /// .and_then(|params| { // <- url encoded parameters /// println!("==== BODY ==== {:?}", params); - /// ok(httpcodes::HTTPOk.response()) + /// ok(httpcodes::HTTPOk.into()) /// }) /// .responder() /// } @@ -512,7 +518,7 @@ impl HttpRequest { /// .from_err() /// .and_then(|val: MyObj| { // <- deserialized value /// println!("==== BODY ==== {:?}", val); - /// Ok(httpcodes::HTTPOk.response()) + /// Ok(httpcodes::HTTPOk.into()) /// }).responder() /// } /// # fn main() {} diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 69bb71a72..5d5e85fcb 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -138,6 +138,7 @@ impl HttpResponse { } /// Connection upgrade status + #[inline] pub fn upgrade(&self) -> bool { self.get_ref().connection_type == Some(ConnectionType::Upgrade) } @@ -155,11 +156,13 @@ impl HttpResponse { } /// is chunked encoding enabled + #[inline] pub fn chunked(&self) -> bool { self.get_ref().chunked } /// Content encoding + #[inline] pub fn content_encoding(&self) -> &ContentEncoding { &self.get_ref().encoding } @@ -171,6 +174,7 @@ impl HttpResponse { } /// Get body os this response + #[inline] pub fn body(&self) -> &Body { &self.get_ref().body } @@ -443,6 +447,15 @@ impl HttpResponseBuilder { pub fn finish(&mut self) -> Result { self.body(Body::Empty) } + + /// This method construct new `HttpResponseBuilder` + pub fn take(&mut self) -> HttpResponseBuilder { + HttpResponseBuilder { + response: self.response.take(), + err: self.err.take(), + cookies: self.cookies.take(), + } + } } #[inline] diff --git a/src/json.rs b/src/json.rs index a33fa46d6..263f6028f 100644 --- a/src/json.rs +++ b/src/json.rs @@ -71,7 +71,7 @@ impl Responder for Json { /// .from_err() /// .and_then(|val: MyObj| { // <- deserialized value /// println!("==== BODY ==== {:?}", val); -/// Ok(httpcodes::HTTPOk.response()) +/// Ok(httpcodes::HTTPOk.into()) /// }).responder() /// } /// # fn main() {} diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index e498ad4c9..ac1d6cc47 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -19,7 +19,7 @@ use middleware::{Middleware, Started, Finished}; /// Default `Logger` could be created with `default` method, it uses the default format: /// /// ```ignore -/// %a %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i" %T +/// %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T /// ``` /// ```rust /// # extern crate actix_web; @@ -75,7 +75,7 @@ impl Default for Logger { /// Create `Logger` middleware with format: /// /// ```ignore - /// %a %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i" %T + /// %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T /// ``` fn default() -> Logger { Logger { format: Format::default() } @@ -121,7 +121,7 @@ struct Format(Vec); impl Default for Format { /// Return the default formatting style for the `Logger`: fn default() -> Format { - Format::new(r#"%a %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i" %T"#) + Format::new(r#"%a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T"#) } } diff --git a/src/payload.rs b/src/payload.rs index 7c921070c..df2e4f7fb 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -9,20 +9,14 @@ use futures::{Future, Async, Poll, Stream}; use futures::task::{Task, current as current_task}; use body::BodyStream; -use actix::ResponseType; use error::PayloadError; pub(crate) const DEFAULT_BUFFER_SIZE: usize = 65_536; // max buffer size 64k /// Just Bytes object -#[derive(PartialEq)] +#[derive(PartialEq, Message)] pub struct PayloadItem(pub Bytes); -impl ResponseType for PayloadItem { - type Item = (); - type Error = (); -} - impl Deref for PayloadItem { type Target = Bytes; @@ -91,27 +85,27 @@ impl Payload { } /// Get first available chunk of data. - pub fn readany(&mut self) -> ReadAny { + pub fn readany(&self) -> ReadAny { ReadAny(Rc::clone(&self.inner)) } /// Get exact number of bytes - pub fn readexactly(&mut self, size: usize) -> ReadExactly { + pub fn readexactly(&self, size: usize) -> ReadExactly { ReadExactly(Rc::clone(&self.inner), size) } /// Read until `\n` - pub fn readline(&mut self) -> ReadLine { + pub fn readline(&self) -> ReadLine { ReadLine(Rc::clone(&self.inner)) } /// Read until match line - pub fn readuntil(&mut self, line: &[u8]) -> ReadUntil { + pub fn readuntil(&self, line: &[u8]) -> ReadUntil { ReadUntil(Rc::clone(&self.inner), line.to_vec()) } #[doc(hidden)] - pub fn readall(&mut self) -> Option { + pub fn readall(&self) -> Option { self.inner.borrow_mut().readall() } @@ -132,20 +126,16 @@ impl Payload { /// Convert payload into compatible `HttpResponse` body stream pub fn stream(self) -> BodyStream { - Box::new(self.map_err(|e| e.into())) + Box::new(self.map(|i| i.0).map_err(|e| e.into())) } } impl Stream for Payload { - type Item = Bytes; + type Item = PayloadItem; type Error = PayloadError; - fn poll(&mut self) -> Poll, PayloadError> { - match self.inner.borrow_mut().readany()? { - Async::Ready(Some(item)) => Ok(Async::Ready(Some(item.0))), - Async::Ready(None) => Ok(Async::Ready(None)), - Async::NotReady => Ok(Async::NotReady), - } + fn poll(&mut self) -> Poll, PayloadError> { + self.inner.borrow_mut().readany() } } @@ -474,7 +464,7 @@ mod tests { #[test] fn test_basic() { Core::new().unwrap().run(lazy(|| { - let (_, mut payload) = Payload::new(false); + let (_, payload) = Payload::new(false); assert!(!payload.eof()); assert!(payload.is_empty()); @@ -489,7 +479,7 @@ mod tests { #[test] fn test_eof() { Core::new().unwrap().run(lazy(|| { - let (mut sender, mut payload) = Payload::new(false); + let (mut sender, payload) = Payload::new(false); assert_eq!(Async::NotReady, payload.readany().poll().ok().unwrap()); assert!(!payload.eof()); @@ -514,7 +504,7 @@ mod tests { #[test] fn test_err() { Core::new().unwrap().run(lazy(|| { - let (mut sender, mut payload) = Payload::new(false); + let (mut sender, payload) = Payload::new(false); assert_eq!(Async::NotReady, payload.readany().poll().ok().unwrap()); @@ -528,7 +518,7 @@ mod tests { #[test] fn test_readany() { Core::new().unwrap().run(lazy(|| { - let (mut sender, mut payload) = Payload::new(false); + let (mut sender, payload) = Payload::new(false); sender.feed_data(Bytes::from("line1")); @@ -552,7 +542,7 @@ mod tests { #[test] fn test_readexactly() { Core::new().unwrap().run(lazy(|| { - let (mut sender, mut payload) = Payload::new(false); + let (mut sender, payload) = Payload::new(false); assert_eq!(Async::NotReady, payload.readexactly(2).poll().ok().unwrap()); @@ -579,7 +569,7 @@ mod tests { #[test] fn test_readuntil() { Core::new().unwrap().run(lazy(|| { - let (mut sender, mut payload) = Payload::new(false); + let (mut sender, payload) = Payload::new(false); assert_eq!(Async::NotReady, payload.readuntil(b"ne").poll().ok().unwrap()); diff --git a/src/pipeline.rs b/src/pipeline.rs index f37065f01..f5f4799c4 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -8,8 +8,8 @@ use futures::unsync::oneshot; use channel::HttpHandlerTask; use body::{Body, BodyStream}; -use context::{Frame, IoContext}; -use error::{Error, UnexpectedTaskFrame}; +use context::{Frame, ActorHttpContext}; +use error::Error; use handler::{Reply, ReplyItem}; use h1writer::{Writer, WriterState}; use httprequest::HttpRequest; @@ -38,7 +38,7 @@ struct PipelineInfo { req: HttpRequest, count: usize, mws: Rc>>>, - context: Option>, + context: Option>, error: Option, } @@ -72,12 +72,6 @@ impl PipelineInfo { } } -enum PipelineResponse { - None, - Context(Box), - Response(Box>), -} - impl> Pipeline { pub fn new(req: HttpRequest, @@ -364,7 +358,7 @@ impl> StartMiddlewares { // waiting for response struct WaitingResponse { - stream: PipelineResponse, + fut: Box>, _s: PhantomData, _h: PhantomData, } @@ -377,65 +371,22 @@ impl WaitingResponse { match reply.into() { ReplyItem::Message(resp) => RunMiddlewares::init(info, resp), - ReplyItem::Actor(ctx) => - PipelineState::Handler( - WaitingResponse { stream: PipelineResponse::Context(ctx), - _s: PhantomData, _h: PhantomData }), ReplyItem::Future(fut) => PipelineState::Handler( - WaitingResponse { stream: PipelineResponse::Response(fut), - _s: PhantomData, _h: PhantomData }), + WaitingResponse { fut: fut, _s: PhantomData, _h: PhantomData }), } } fn poll(mut self, info: &mut PipelineInfo) -> Result, PipelineState> { - let stream = mem::replace(&mut self.stream, PipelineResponse::None); - - match stream { - PipelineResponse::Context(mut context) => { - loop { - match context.poll() { - Ok(Async::Ready(Some(frame))) => { - match frame { - Frame::Message(resp) => { - info.context = Some(context); - return Ok(RunMiddlewares::init(info, resp)) - } - Frame::Payload(_) | Frame::Drain(_) => (), - } - }, - Ok(Async::Ready(None)) => { - error!("Unexpected eof"); - let err: Error = UnexpectedTaskFrame.into(); - return Ok(ProcessResponse::init(err.into())) - }, - Ok(Async::NotReady) => { - self.stream = PipelineResponse::Context(context); - return Err(PipelineState::Handler(self)) - }, - Err(err) => - return Ok(ProcessResponse::init(err.into())) - } - } - }, - PipelineResponse::Response(mut fut) => { - match fut.poll() { - Ok(Async::NotReady) => { - self.stream = PipelineResponse::Response(fut); - Err(PipelineState::Handler(self)) - } - Ok(Async::Ready(response)) => - Ok(RunMiddlewares::init(info, response)), - Err(err) => - Ok(ProcessResponse::init(err.into())), - } - } - PipelineResponse::None => { - unreachable!("Broken internal state") - } + match self.fut.poll() { + Ok(Async::NotReady) => + Err(PipelineState::Handler(self)), + Ok(Async::Ready(response)) => + Ok(RunMiddlewares::init(info, response)), + Err(err) => + Ok(ProcessResponse::init(err.into())), } - } } @@ -554,7 +505,7 @@ impl RunningState { enum IOState { Response, Payload(BodyStream), - Context, + Actor(Box), Done, } @@ -588,10 +539,10 @@ impl ProcessResponse { }; match self.resp.replace_body(Body::Empty) { - Body::Streaming(stream) | Body::Upgrade(stream) => + Body::Streaming(stream) => self.iostate = IOState::Payload(stream), - Body::StreamingContext | Body::UpgradeContext => - self.iostate = IOState::Context, + Body::Actor(ctx) => + self.iostate = IOState::Actor(ctx), _ => (), } @@ -640,17 +591,12 @@ impl ProcessResponse { } } }, - IOState::Context => { - match info.context.as_mut().unwrap().poll() { + IOState::Actor(mut ctx) => { + match ctx.poll() { Ok(Async::Ready(Some(frame))) => { match frame { - Frame::Message(msg) => { - error!("Unexpected message frame {:?}", msg); - info.error = Some(UnexpectedTaskFrame.into()); - return Ok( - FinishingMiddlewares::init(info, self.resp)) - }, Frame::Payload(None) => { + info.context = Some(ctx); self.iostate = IOState::Done; if let Err(err) = io.write_eof() { info.error = Some(err.into()); @@ -660,7 +606,7 @@ impl ProcessResponse { break }, Frame::Payload(Some(chunk)) => { - self.iostate = IOState::Context; + self.iostate = IOState::Actor(ctx); match io.write(chunk.as_ref()) { Err(err) => { info.error = Some(err.into()); @@ -678,11 +624,10 @@ impl ProcessResponse { }, Ok(Async::Ready(None)) => { self.iostate = IOState::Done; - info.context.take(); break } Ok(Async::NotReady) => { - self.iostate = IOState::Context; + self.iostate = IOState::Actor(ctx); break } Err(err) => { diff --git a/src/router.rs b/src/router.rs index eb1027fb3..ebd763bfd 100644 --- a/src/router.rs +++ b/src/router.rs @@ -190,7 +190,7 @@ impl Pattern { } /// Extract pattern parameters from the text - pub(crate) fn update_match_info(&self, text: &str, req: &mut HttpRequest) { + pub fn update_match_info(&self, text: &str, req: &mut HttpRequest) { if !self.names.is_empty() { if let Some(captures) = self.re.captures(text) { let mut idx = 0; @@ -208,8 +208,7 @@ impl Pattern { } /// Build pattern path. - pub fn path(&self, prefix: Option<&str>, elements: U) - -> Result + pub fn path(&self, prefix: Option<&str>, elements: U) -> Result where U: IntoIterator, I: AsRef, { diff --git a/src/test.rs b/src/test.rs index 88c2044f5..a0d8a8de5 100644 --- a/src/test.rs +++ b/src/test.rs @@ -41,7 +41,7 @@ use httpresponse::HttpResponse; /// # extern crate reqwest; /// # /// # fn my_handler(req: HttpRequest) -> HttpResponse { -/// # httpcodes::HTTPOk.response() +/// # httpcodes::HTTPOk.into() /// # } /// # /// # fn main() { @@ -228,9 +228,9 @@ impl Iterator for TestApp { /// /// fn index(req: HttpRequest) -> HttpResponse { /// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { -/// httpcodes::HTTPOk.response() +/// httpcodes::HTTPOk.into() /// } else { -/// httpcodes::HTTPBadRequest.response() +/// httpcodes::HTTPBadRequest.into() /// } /// } /// @@ -365,7 +365,6 @@ impl TestRequest { Ok(resp) => { match resp.into().into() { ReplyItem::Message(resp) => Ok(resp), - ReplyItem::Actor(_) => panic!("Actor handler is not supported."), ReplyItem::Future(_) => panic!("Async handler is not supported."), } }, diff --git a/src/ws.rs b/src/ws.rs index 5832e5c29..69e74aadb 100644 --- a/src/ws.rs +++ b/src/ws.rs @@ -12,7 +12,7 @@ //! use actix_web::*; //! //! // do websocket handshake and start actor -//! fn ws_index(req: HttpRequest) -> Result { +//! fn ws_index(req: HttpRequest) -> Result { //! ws::start(req, Ws) //! } //! @@ -52,13 +52,11 @@ use futures::{Async, Poll, Stream}; use actix::{Actor, AsyncContext, ResponseType, StreamHandler}; -use body::Body; -use context::HttpContext; -use handler::Reply; use payload::ReadAny; use error::{Error, WsHandshakeError}; +use context::HttpContext; use httprequest::HttpRequest; -use httpresponse::{ConnectionType, HttpResponse}; +use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder}; use wsframe; use wsproto::*; @@ -88,17 +86,17 @@ impl ResponseType for Message { } /// Do websocket handshake and start actor -pub fn start(mut req: HttpRequest, actor: A) -> Result +pub fn start(mut req: HttpRequest, actor: A) -> Result where A: Actor> + StreamHandler, S: 'static { - let resp = handshake(&req)?; - + let mut resp = handshake(&req)?; let stream = WsStream::new(req.payload_mut().readany()); + let mut ctx = HttpContext::new(req, actor); - ctx.start(resp); ctx.add_stream(stream); - Ok(ctx.into()) + + Ok(resp.body(ctx)?) } /// Prepare `WebSocket` handshake response. @@ -109,7 +107,7 @@ pub fn start(mut req: HttpRequest, actor: A) -> Result // /// `protocols` is a sequence of known protocols. On successful handshake, // /// the returned response headers contain the first protocol in this list // /// which the server also knows. -pub fn handshake(req: &HttpRequest) -> Result { +pub fn handshake(req: &HttpRequest) -> Result { // WebSocket accepts only GET if *req.method() != Method::GET { return Err(WsHandshakeError::GetMethodRequired) @@ -163,8 +161,7 @@ pub fn handshake(req: &HttpRequest) -> Result Date: Sun, 31 Dec 2017 20:08:35 -0800 Subject: [PATCH 245/279] fix examples --- examples/diesel/src/main.rs | 2 +- examples/multipart/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/diesel/src/main.rs b/examples/diesel/src/main.rs index 350a9ee5a..5c0e3724c 100644 --- a/examples/diesel/src/main.rs +++ b/examples/diesel/src/main.rs @@ -42,7 +42,7 @@ fn index(req: HttpRequest) -> Box> .and_then(|res| { match res { Ok(user) => Ok(httpcodes::HTTPOk.build().json(user)?), - Err(_) => Ok(httpcodes::HTTPInternalServerError.response()) + Err(_) => Ok(httpcodes::HTTPInternalServerError.into()) } }) .responder() diff --git a/examples/multipart/src/main.rs b/examples/multipart/src/main.rs index 84259ec1a..78c613622 100644 --- a/examples/multipart/src/main.rs +++ b/examples/multipart/src/main.rs @@ -39,7 +39,7 @@ fn index(mut req: HttpRequest) -> Box> } }) .finish() // <- Stream::finish() combinator from actix - .map(|_| httpcodes::HTTPOk.response()) + .map(|_| httpcodes::HTTPOk.into()) .responder() } From d2f54b7d197b577232f9bc37f7cd28bd7dc77dcb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 31 Dec 2017 21:55:25 -0800 Subject: [PATCH 246/279] use workspace --- Cargo.toml | 2 ++ examples/basic/Cargo.toml | 1 + examples/basic/src/main.rs | 7 +++++-- examples/diesel/Cargo.toml | 1 + examples/json/Cargo.toml | 1 + examples/json/src/main.rs | 7 +++++-- examples/multipart/Cargo.toml | 1 + examples/multipart/src/main.rs | 7 +++++-- examples/signals/Cargo.toml | 1 + examples/signals/src/main.rs | 7 +++++-- examples/state/Cargo.toml | 1 + examples/state/src/main.rs | 14 +++++++++----- examples/template_tera/Cargo.toml | 1 + examples/template_tera/src/main.rs | 7 +++++-- examples/tls/Cargo.toml | 1 + examples/tls/src/main.rs | 4 ++-- examples/websocket-chat/Cargo.toml | 1 + examples/websocket-chat/src/main.rs | 9 ++++++--- 18 files changed, 53 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ba48f1c7c..21775a96b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,3 +99,5 @@ version_check = "0.1" lto = true opt-level = 3 # debug = true + +[workspace] diff --git a/examples/basic/Cargo.toml b/examples/basic/Cargo.toml index 6bb442e41..d4fc56662 100644 --- a/examples/basic/Cargo.toml +++ b/examples/basic/Cargo.toml @@ -2,6 +2,7 @@ name = "basic" version = "0.1.0" authors = ["Nikolay Kim "] +workspace = "../.." [dependencies] futures = "*" diff --git a/examples/basic/src/main.rs b/examples/basic/src/main.rs index 9895ac946..f3f519807 100644 --- a/examples/basic/src/main.rs +++ b/examples/basic/src/main.rs @@ -9,9 +9,10 @@ use futures::Stream; use actix::*; use actix_web::*; -#[cfg(target_os = "linux")] use actix::actors::signal::{ProcessSignals, Subscribe}; use actix_web::middleware::RequestSession; use futures::future::{FutureResult, result}; +#[cfg(unix)] +use actix::actors::signal::{ProcessSignals, Subscribe}; /// simple handler fn index(mut req: HttpRequest) -> Result { @@ -96,7 +97,9 @@ fn main() { .bind("0.0.0.0:8080").unwrap() .start(); - if cfg!(target_os = "linux") { // Subscribe to unix signals + // Subscribe to unix signals + #[cfg(unix)] + { let signals = Arbiter::system_registry().get::(); signals.send(Subscribe(addr.subscriber())); } diff --git a/examples/diesel/Cargo.toml b/examples/diesel/Cargo.toml index 40e78e307..6e7a23935 100644 --- a/examples/diesel/Cargo.toml +++ b/examples/diesel/Cargo.toml @@ -2,6 +2,7 @@ name = "diesel-example" version = "0.1.0" authors = ["Nikolay Kim "] +workspace = "../.." [dependencies] env_logger = "0.4" diff --git a/examples/json/Cargo.toml b/examples/json/Cargo.toml index 7eb3155e7..e4d7ed8fc 100644 --- a/examples/json/Cargo.toml +++ b/examples/json/Cargo.toml @@ -2,6 +2,7 @@ name = "json-example" version = "0.1.0" authors = ["Nikolay Kim "] +workspace = "../.." [dependencies] bytes = "0.4" diff --git a/examples/json/src/main.rs b/examples/json/src/main.rs index 907eaf51b..296d4a4bc 100644 --- a/examples/json/src/main.rs +++ b/examples/json/src/main.rs @@ -9,7 +9,8 @@ extern crate serde_json; use actix::*; use actix_web::*; -#[cfg(target_os = "linux")] use actix::actors::signal::{ProcessSignals, Subscribe}; +#[cfg(unix)] +use actix::actors::signal::{ProcessSignals, Subscribe}; use bytes::BytesMut; use futures::{Future, Stream}; @@ -96,7 +97,9 @@ fn main() { .shutdown_timeout(1) .start(); - if cfg!(target_os = "linux") { // Subscribe to unix signals + // Subscribe to unix signals + #[cfg(unix)] + { let signals = Arbiter::system_registry().get::(); signals.send(Subscribe(addr.subscriber())); } diff --git a/examples/multipart/Cargo.toml b/examples/multipart/Cargo.toml index 049ca76c5..7a92f465c 100644 --- a/examples/multipart/Cargo.toml +++ b/examples/multipart/Cargo.toml @@ -2,6 +2,7 @@ name = "multipart-example" version = "0.1.0" authors = ["Nikolay Kim "] +workspace = "../.." [[bin]] name = "multipart" diff --git a/examples/multipart/src/main.rs b/examples/multipart/src/main.rs index 78c613622..407365cd6 100644 --- a/examples/multipart/src/main.rs +++ b/examples/multipart/src/main.rs @@ -6,7 +6,8 @@ extern crate futures; use actix::*; use actix_web::*; -#[cfg(target_os = "linux")] use actix::actors::signal::{ProcessSignals, Subscribe}; +#[cfg(unix)] +use actix::actors::signal::{ProcessSignals, Subscribe}; use futures::{Future, Stream}; use futures::future::{result, Either}; @@ -55,7 +56,9 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); - if cfg!(target_os = "linux") { // Subscribe to unix signals + // Subscribe to unix signals + #[cfg(unix)] + { let signals = Arbiter::system_registry().get::(); signals.send(Subscribe(addr.subscriber())); } diff --git a/examples/signals/Cargo.toml b/examples/signals/Cargo.toml index 869dc66e7..9352ef5e7 100644 --- a/examples/signals/Cargo.toml +++ b/examples/signals/Cargo.toml @@ -2,6 +2,7 @@ name = "signals" version = "0.1.0" authors = ["Nikolay Kim "] +workspace = "../.." [[bin]] name = "server" diff --git a/examples/signals/src/main.rs b/examples/signals/src/main.rs index 2571fdb4f..7f939081a 100644 --- a/examples/signals/src/main.rs +++ b/examples/signals/src/main.rs @@ -5,7 +5,8 @@ extern crate env_logger; use actix::*; use actix_web::*; -#[cfg(target_os = "linux")] use actix::actors::signal::{ProcessSignals, Subscribe}; +#[cfg(unix)] +use actix::actors::signal::{ProcessSignals, Subscribe}; struct MyWebSocket; @@ -34,7 +35,9 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); - if cfg!(target_os = "linux") { // Subscribe to unix signals + // Subscribe to unix signals + #[cfg(unix)] + { let signals = Arbiter::system_registry().get::(); signals.send(Subscribe(addr.subscriber())); } diff --git a/examples/state/Cargo.toml b/examples/state/Cargo.toml index c71fc86f8..7e4c7d3dd 100644 --- a/examples/state/Cargo.toml +++ b/examples/state/Cargo.toml @@ -2,6 +2,7 @@ name = "state" version = "0.1.0" authors = ["Nikolay Kim "] +workspace = "../.." [dependencies] futures = "*" diff --git a/examples/state/src/main.rs b/examples/state/src/main.rs index c713e68ec..68e989bf3 100644 --- a/examples/state/src/main.rs +++ b/examples/state/src/main.rs @@ -7,11 +7,14 @@ extern crate actix; extern crate actix_web; extern crate env_logger; -use actix::*; -use actix_web::*; -#[cfg(target_os = "linux")] use actix::actors::signal::{ProcessSignals, Subscribe}; use std::cell::Cell; +use actix::*; +use actix_web::*; +#[cfg(unix)] +use actix::actors::signal::{ProcessSignals, Subscribe}; + +/// Application state struct AppState { counter: Cell, } @@ -55,7 +58,6 @@ impl Handler for MyWebSocket { } } - fn main() { ::std::env::set_var("RUST_LOG", "actix_web=info"); let _ = env_logger::init(); @@ -74,7 +76,9 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); - if cfg!(target_os = "linux") { // Subscribe to unix signals + // Subscribe to unix signals + #[cfg(unix)] + { let signals = Arbiter::system_registry().get::(); signals.send(Subscribe(addr.subscriber())); } diff --git a/examples/template_tera/Cargo.toml b/examples/template_tera/Cargo.toml index 36e8d8e55..791934d09 100644 --- a/examples/template_tera/Cargo.toml +++ b/examples/template_tera/Cargo.toml @@ -2,6 +2,7 @@ name = "template-tera" version = "0.1.0" authors = ["Nikolay Kim "] +workspace = "../.." [dependencies] env_logger = "0.4" diff --git a/examples/template_tera/src/main.rs b/examples/template_tera/src/main.rs index c4f962e73..1b5552234 100644 --- a/examples/template_tera/src/main.rs +++ b/examples/template_tera/src/main.rs @@ -6,7 +6,9 @@ extern crate tera; use actix::*; use actix_web::*; -#[cfg(target_os = "linux")] use actix::actors::signal::{ProcessSignals, Subscribe}; +#[cfg(unix)] +use actix::actors::signal::{ProcessSignals, Subscribe}; + struct State { template: tera::Tera, // <- store tera template in application state @@ -43,7 +45,8 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); - if cfg!(target_os = "linux") { // Subscribe to unix signals + #[cfg(unix)] + { // Subscribe to unix signals let signals = Arbiter::system_registry().get::(); signals.send(Subscribe(addr.subscriber())); } diff --git a/examples/tls/Cargo.toml b/examples/tls/Cargo.toml index eda5e5fc8..a659bc36b 100644 --- a/examples/tls/Cargo.toml +++ b/examples/tls/Cargo.toml @@ -2,6 +2,7 @@ name = "tls-example" version = "0.1.0" authors = ["Nikolay Kim "] +workspace = "../.." [[bin]] name = "server" diff --git a/examples/tls/src/main.rs b/examples/tls/src/main.rs index 3d969d3a4..a754e0738 100644 --- a/examples/tls/src/main.rs +++ b/examples/tls/src/main.rs @@ -8,7 +8,7 @@ use std::io::Read; use actix::*; use actix_web::*; -#[cfg(target_os = "linux")] +#[cfg(unix)] use actix::actors::signal::{ProcessSignals, Subscribe}; /// somple handle @@ -49,7 +49,7 @@ fn main() { .start_ssl(&pkcs12).unwrap(); // Subscribe to unix signals - #[cfg(target_os = "linux")] + #[cfg(unix)] { let signals = Arbiter::system_registry().get::(); signals.send(Subscribe(addr.subscriber())); diff --git a/examples/websocket-chat/Cargo.toml b/examples/websocket-chat/Cargo.toml index 6ca5f0ad3..517cf8163 100644 --- a/examples/websocket-chat/Cargo.toml +++ b/examples/websocket-chat/Cargo.toml @@ -2,6 +2,7 @@ name = "websocket-example" version = "0.1.0" authors = ["Nikolay Kim "] +workspace = "../.." [[bin]] name = "server" diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs index a0236cbed..a4d3ce333 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -17,7 +17,8 @@ use std::time::Instant; use actix::*; use actix_web::*; -#[cfg(target_os = "linux")] use actix::actors::signal::{ProcessSignals, Subscribe}; +#[cfg(unix)] +use actix::actors::signal::{ProcessSignals, Subscribe}; mod codec; mod server; @@ -30,7 +31,7 @@ struct WsChatSessionState { } /// Entry point for our route -fn chat_route(req: HttpRequest) -> Result { +fn chat_route(req: HttpRequest) -> Result { ws::start( req, WsChatSession { @@ -215,7 +216,9 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); - if cfg!(target_os = "linux") { // Subscribe to unix signals + // Subscribe to unix signals + #[cfg(unix)] + { let signals = Arbiter::system_registry().get::(); signals.send(Subscribe(addr.subscriber())); } From f3a90a28299521a445540861668cd2804e7c752e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 31 Dec 2017 22:22:56 -0800 Subject: [PATCH 247/279] add example to workspace --- Cargo.toml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 21775a96b..dd7dc5e64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,3 +101,15 @@ opt-level = 3 # debug = true [workspace] +members = [ + "./", + "examples/basic", + "examples/diesel", + "examples/json", + "examples/multipart", + "examples/signals", + "examples/state", + "examples/template_tera", + "examples/tls", + "examples/websocket-chat", +] From 284b59722a429154bc18105ee77b888970dfe8e4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Jan 2018 09:31:42 -0800 Subject: [PATCH 248/279] update websocket example --- Cargo.toml | 1 + examples/websocket/src/main.rs | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dd7dc5e64..316ba9ef3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -111,5 +111,6 @@ members = [ "examples/state", "examples/template_tera", "examples/tls", + "examples/websocket", "examples/websocket-chat", ] diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index 022ada344..dfd0d52c7 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -10,10 +10,11 @@ extern crate env_logger; use actix::*; use actix_web::*; -#[cfg(target_os = "linux")] use actix::actors::signal::{ProcessSignals, Subscribe}; +#[cfg(unix)] +use actix::actors::signal::{ProcessSignals, Subscribe}; /// do websocket handshake and start `MyWebSocket` actor -fn ws_index(r: HttpRequest) -> Result { +fn ws_index(r: HttpRequest) -> Result { ws::start(r, MyWebSocket) } @@ -73,7 +74,9 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); - if cfg!(target_os = "linux") { // Subscribe to unix signals + // Subscribe to unix signals + #[cfg(unix)] + { let signals = Arbiter::system_registry().get::(); signals.send(Subscribe(_addr.subscriber())); } From 9040f588afeb785a5d2d20cdc01fd1b2d2f53af1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Jan 2018 13:09:02 -0800 Subject: [PATCH 249/279] allow to handle entire sub path --- src/application.rs | 98 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 86 insertions(+), 12 deletions(-) diff --git a/src/application.rs b/src/application.rs index 02ae078ba..7ca1449c0 100644 --- a/src/application.rs +++ b/src/application.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; use handler::Reply; use router::{Router, Pattern}; use resource::Resource; +use handler::{Handler, RouteHandler, WrapHandler}; use httprequest::HttpRequest; use channel::{HttpHandler, IntoHttpHandler, HttpHandlerTask}; use pipeline::{Pipeline, PipelineHandler}; @@ -24,6 +25,7 @@ pub(crate) struct Inner { default: Resource, router: Router, resources: Vec>, + handlers: Vec<(String, Box>)>, } impl PipelineHandler for Inner { @@ -32,6 +34,17 @@ impl PipelineHandler for Inner { if let Some(idx) = self.router.recognize(&mut req) { self.resources[idx].handle(req.clone(), Some(&mut self.default)) } else { + for &mut (ref prefix, ref mut handler) in &mut self.handlers { + let m = { + let path = req.path(); + path.starts_with(prefix) && ( + path.len() == prefix.len() || + path.split_at(prefix.len()).1.starts_with('/')) + }; + if m { + return handler.handle(req) + } + } self.default.handle(req, None) } } @@ -73,6 +86,7 @@ struct ApplicationParts { settings: ServerSettings, default: Resource, resources: HashMap>>, + handlers: Vec<(String, Box>)>, external: HashMap, middlewares: Vec>>, } @@ -94,6 +108,7 @@ impl Application<()> { settings: ServerSettings::default(), default: Resource::default_not_found(), resources: HashMap::new(), + handlers: Vec::new(), external: HashMap::new(), middlewares: Vec::new(), }) @@ -122,6 +137,7 @@ impl Application where S: 'static { settings: ServerSettings::default(), default: Resource::default_not_found(), resources: HashMap::new(), + handlers: Vec::new(), external: HashMap::new(), middlewares: Vec::new(), }) @@ -133,7 +149,7 @@ impl Application where S: 'static { /// Only requests that matches application's prefix get processed by this application. /// Application prefix always contains laading "/" slash. If supplied prefix /// does not contain leading slash, it get inserted. Prefix should - /// consists of valud path segments. i.e for application with + /// consists valid path segments. i.e for application with /// prefix `/app` any request with following paths `/app`, `/app/` or `/app/test` /// would match, but path `/application` would not match. /// @@ -194,8 +210,7 @@ impl Application where S: 'static { /// .resource("/test", |r| { /// r.method(Method::GET).f(|_| httpcodes::HTTPOk); /// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed); - /// }) - /// .finish(); + /// }); /// } /// ``` pub fn resource(mut self, path: &str, f: F) -> Application @@ -267,6 +282,36 @@ impl Application where S: 'static { self } + /// Configure handler for specific path prefix. + /// + /// Path prefix consists valid path segments. i.e for prefix `/app` + /// any request with following paths `/app`, `/app/` or `/app/test` + /// would match, but path `/application` would not match. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::*; + /// + /// fn main() { + /// let app = Application::new() + /// .handler("/app", |req: HttpRequest| { + /// match *req.method() { + /// Method::GET => httpcodes::HTTPOk, + /// Method::POST => httpcodes::HTTPMethodNotAllowed, + /// _ => httpcodes::HTTPNotFound, + /// }}); + /// } + /// ``` + pub fn handler>(mut self, path: &str, handler: H) -> Application + { + { + let path = path.trim().trim_right_matches('/').to_owned(); + let parts = self.parts.as_mut().expect("Use after finish"); + parts.handlers.push((path, Box::new(WrapHandler::new(handler)))); + } + self + } + /// Register a middleware pub fn middleware(mut self, mw: T) -> Application where T: Middleware + 'static @@ -292,7 +337,9 @@ impl Application where S: 'static { Inner { default: parts.default, router: router.clone(), - resources: resources } + resources: resources, + handlers: parts.handlers, + } )); HttpApplication { @@ -345,8 +392,7 @@ impl Iterator for Application { #[cfg(test)] mod tests { - use std::str::FromStr; - use http::{Method, Version, Uri, HeaderMap, StatusCode}; + use http::StatusCode; use super::*; use test::TestRequest; use httprequest::HttpRequest; @@ -362,18 +408,14 @@ mod tests { let resp = app.run(req); assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); - let req = HttpRequest::new( - Method::GET, Uri::from_str("/blah").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); + let req = TestRequest::with_uri("/blah").finish(); let resp = app.run(req); assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); let mut app = Application::new() .default_resource(|r| r.h(httpcodes::HTTPMethodNotAllowed)) .finish(); - let req = HttpRequest::new( - Method::GET, Uri::from_str("/blah").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); + let req = TestRequest::with_uri("/blah").finish(); let resp = app.run(req); assert_eq!(resp.as_response().unwrap().status(), StatusCode::METHOD_NOT_ALLOWED); } @@ -411,8 +453,40 @@ mod tests { let resp = app.handle(req); assert!(resp.is_ok()); + let req = TestRequest::with_uri("/test/blah").finish(); + let resp = app.handle(req); + assert!(resp.is_ok()); + let req = TestRequest::with_uri("/testing").finish(); let resp = app.handle(req); assert!(resp.is_err()); } + + #[test] + fn test_handler() { + let mut app = Application::new() + .handler("/test", httpcodes::HTTPOk) + .finish(); + + let req = TestRequest::with_uri("/test").finish(); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + + let req = TestRequest::with_uri("/test/").finish(); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + + let req = TestRequest::with_uri("/test/app").finish(); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + + let req = TestRequest::with_uri("/testapp").finish(); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri("/blah").finish(); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + + } } From b49eadf7e55eee2b804046e88e2c864b7957047e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Jan 2018 13:39:32 -0800 Subject: [PATCH 250/279] fix content length serialization #33 --- src/helpers.rs | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/helpers.rs b/src/helpers.rs index ba6fd49d2..1b4bd0e11 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -309,16 +309,10 @@ pub(crate) fn write_content_length(mut n: usize, bytes: &mut BytesMut) { let d1 = (n % 100) << 1; n /= 100; unsafe {ptr::copy_nonoverlapping( - DEC_DIGITS_LUT.as_ptr().offset(d1 as isize), buf.as_mut_ptr().offset(18), 2)}; + DEC_DIGITS_LUT.as_ptr().offset(d1 as isize), buf.as_mut_ptr().offset(19), 2)}; - // decode last 1 or 2 chars - if n < 10 { - buf[20] = (n as u8) + b'0'; - } else { - let d1 = n << 1; - unsafe {ptr::copy_nonoverlapping( - DEC_DIGITS_LUT.as_ptr().offset(d1 as isize), buf.as_mut_ptr().offset(17), 2)}; - } + // decode last 1 + buf[18] = (n as u8) + b'0'; bytes.extend_from_slice(&buf); } else { @@ -394,4 +388,29 @@ mod tests { date(&mut buf2); assert_eq!(buf1, buf2); } + + #[test] + fn test_write_content_length() { + let mut bytes = BytesMut::new(); + write_content_length(0, &mut bytes); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 0\r\n"[..]); + write_content_length(9, &mut bytes); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 9\r\n"[..]); + write_content_length(10, &mut bytes); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 10\r\n"[..]); + write_content_length(99, &mut bytes); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 99\r\n"[..]); + write_content_length(100, &mut bytes); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 100\r\n"[..]); + write_content_length(101, &mut bytes); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 101\r\n"[..]); + write_content_length(998, &mut bytes); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 998\r\n"[..]); + write_content_length(1000, &mut bytes); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1000\r\n"[..]); + write_content_length(1001, &mut bytes); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1001\r\n"[..]); + write_content_length(5909, &mut bytes); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 5909\r\n"[..]); + } } From fb2c78d9fc058401a1d8d743876bdc83bcfed9a7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Jan 2018 13:42:30 -0800 Subject: [PATCH 251/279] add hello-world example --- .travis.yml | 2 ++ Cargo.toml | 3 ++- examples/{basic => basics}/Cargo.toml | 2 +- examples/{basic => basics}/README.md | 0 examples/{basic => basics}/src/main.rs | 0 examples/hello-world/Cargo.toml | 10 +++++++++ examples/hello-world/src/main.rs | 28 ++++++++++++++++++++++++++ 7 files changed, 43 insertions(+), 2 deletions(-) rename examples/{basic => basics}/Cargo.toml (93%) rename examples/{basic => basics}/README.md (100%) rename examples/{basic => basics}/src/main.rs (100%) create mode 100644 examples/hello-world/Cargo.toml create mode 100644 examples/hello-world/src/main.rs diff --git a/.travis.yml b/.travis.yml index b72da2c8d..3c55bbde6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,6 +37,8 @@ script: - | if [[ "$TRAVIS_RUST_VERSION" == "1.20.0" ]]; then + cd examples/basics && cargo check && cd ../.. + cd examples/hello-world && cargo check && cd ../.. cd examples/multipart && cargo check && cd ../.. cd examples/json && cargo check && cd ../.. cd examples/template_tera && cargo check && cd ../.. diff --git a/Cargo.toml b/Cargo.toml index 316ba9ef3..7a106be6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,9 +103,10 @@ opt-level = 3 [workspace] members = [ "./", - "examples/basic", + "examples/basics", "examples/diesel", "examples/json", + "examples/hello-world", "examples/multipart", "examples/signals", "examples/state", diff --git a/examples/basic/Cargo.toml b/examples/basics/Cargo.toml similarity index 93% rename from examples/basic/Cargo.toml rename to examples/basics/Cargo.toml index d4fc56662..b5eefd0f3 100644 --- a/examples/basic/Cargo.toml +++ b/examples/basics/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "basic" +name = "basics" version = "0.1.0" authors = ["Nikolay Kim "] workspace = "../.." diff --git a/examples/basic/README.md b/examples/basics/README.md similarity index 100% rename from examples/basic/README.md rename to examples/basics/README.md diff --git a/examples/basic/src/main.rs b/examples/basics/src/main.rs similarity index 100% rename from examples/basic/src/main.rs rename to examples/basics/src/main.rs diff --git a/examples/hello-world/Cargo.toml b/examples/hello-world/Cargo.toml new file mode 100644 index 000000000..63bb6f6a4 --- /dev/null +++ b/examples/hello-world/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "hello-world" +version = "0.1.0" +authors = ["Nikolay Kim "] +workspace = "../.." + +[dependencies] +env_logger = "0.4" +actix = "^0.3.5" +actix-web = { path = "../../" } diff --git a/examples/hello-world/src/main.rs b/examples/hello-world/src/main.rs new file mode 100644 index 000000000..5f1484bd2 --- /dev/null +++ b/examples/hello-world/src/main.rs @@ -0,0 +1,28 @@ +extern crate actix; +extern crate actix_web; +extern crate env_logger; + +use actix_web::*; + + +fn index(_req: HttpRequest) -> &'static str { + "Hello world!" +} + +fn main() { + ::std::env::set_var("RUST_LOG", "actix_web=info"); + let _ = env_logger::init(); + let sys = actix::System::new("ws-example"); + + let _addr = HttpServer::new( + || Application::new() + // enable logger + .middleware(middleware::Logger::default()) + .resource("/index.html", |r| r.f(|_| "Hello world!")) + .resource("/", |r| r.f(index))) + .bind("127.0.0.1:8080").unwrap() + .start(); + + println!("Started http server: 127.0.0.1:8080"); + let _ = sys.run(); +} From 77ba1de305cb1f3257060219efcee819c1682c6f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Jan 2018 14:53:51 -0800 Subject: [PATCH 252/279] flush encoder --- src/encoding.rs | 4 ++++ src/h1writer.rs | 9 +++------ src/h2writer.rs | 3 +++ src/pipeline.rs | 1 + 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/encoding.rs b/src/encoding.rs index e30ba9f40..aec45929b 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -569,6 +569,7 @@ impl ContentEncoder { match encoder.finish() { Ok(mut writer) => { writer.encode_eof(); + *self = ContentEncoder::Identity(writer); Ok(()) }, Err(err) => Err(err), @@ -578,6 +579,7 @@ impl ContentEncoder { match encoder.finish() { Ok(mut writer) => { writer.encode_eof(); + *self = ContentEncoder::Identity(writer); Ok(()) }, Err(err) => Err(err), @@ -587,6 +589,7 @@ impl ContentEncoder { match encoder.finish() { Ok(mut writer) => { writer.encode_eof(); + *self = ContentEncoder::Identity(writer); Ok(()) }, Err(err) => Err(err), @@ -594,6 +597,7 @@ impl ContentEncoder { }, ContentEncoder::Identity(mut writer) => { writer.encode_eof(); + *self = ContentEncoder::Identity(writer); Ok(()) } } diff --git a/src/h1writer.rs b/src/h1writer.rs index 011e56ace..5352e7435 100644 --- a/src/h1writer.rs +++ b/src/h1writer.rs @@ -95,7 +95,6 @@ impl H1Writer { match self.stream.write(buffer.as_ref()) { Ok(n) => { buffer.split_to(n); - self.written += n as u64; }, Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { if buffer.len() > MAX_WRITE_BUFFER_SIZE { @@ -115,11 +114,7 @@ impl Writer for H1Writer { #[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))] fn written(&self) -> u64 { - if self.written > self.headers_size as u64 { - self.written - self.headers_size as u64 - } else { - 0 - } + self.written } fn start(&mut self, req: &mut HttpMessage, msg: &mut HttpResponse) @@ -191,6 +186,7 @@ impl Writer for H1Writer { } if let Body::Binary(bytes) = body { + self.written = bytes.len() as u64; self.encoder.write(bytes.as_ref())?; } else { msg.replace_body(body); @@ -199,6 +195,7 @@ impl Writer for H1Writer { } fn write(&mut self, payload: &[u8]) -> Result { + self.written += payload.len() as u64; if !self.flags.contains(Flags::DISCONNECTED) { if self.flags.contains(Flags::STARTED) { // TODO: add warning, write after EOF diff --git a/src/h2writer.rs b/src/h2writer.rs index 51027f7f4..0b21421f5 100644 --- a/src/h2writer.rs +++ b/src/h2writer.rs @@ -167,6 +167,7 @@ impl Writer for H2Writer { if let Body::Binary(bytes) = body { self.flags.insert(Flags::EOF); + self.written = bytes.len() as u64; self.encoder.write(bytes.as_ref())?; if let Some(ref mut stream) = self.stream { stream.reserve_capacity(cmp::min(self.encoder.len(), CHUNK_SIZE)); @@ -179,6 +180,8 @@ impl Writer for H2Writer { } fn write(&mut self, payload: &[u8]) -> Result { + self.written = payload.len() as u64; + if !self.flags.contains(Flags::DISCONNECTED) { if self.flags.contains(Flags::STARTED) { // TODO: add warning, write after EOF diff --git a/src/pipeline.rs b/src/pipeline.rs index f5f4799c4..e8fbfbe8a 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -677,6 +677,7 @@ impl ProcessResponse { // response is completed match self.iostate { IOState::Done => { + io.write_eof(); self.resp.set_response_size(io.written()); Ok(FinishingMiddlewares::init(info, self.resp)) } From f0fdcc99365cc2c0f0d6cc7e679a8058b938abb2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Jan 2018 15:23:31 -0800 Subject: [PATCH 253/279] handle application prefix for handlers; use handler for StaticFiles --- guide/src/qs_12.md | 10 ++++------ src/application.rs | 50 ++++++++++++++++++++++++++++++++++++++++++---- src/fs.rs | 14 ++++++------- src/pipeline.rs | 9 ++++++++- 4 files changed, 64 insertions(+), 19 deletions(-) diff --git a/guide/src/qs_12.md b/guide/src/qs_12.md index 5900e4172..286709353 100644 --- a/guide/src/qs_12.md +++ b/guide/src/qs_12.md @@ -25,9 +25,8 @@ fn main() { ## Directory To serve files from specific directory and sub-directories `StaticFiles` could be used. -`StaticFiles` could be registered with `Application::resource` method. -`StaticFiles` requires tail named path expression for resource registration. -And this name has to be used in `StaticFile` constructor. +`StaticFiles` must be registered with `Application::handler()` method otherwise +it won't be able to server sub-paths. ```rust # extern crate actix_web; @@ -35,12 +34,11 @@ use actix_web::*; fn main() { Application::new() - .resource("/static/{tail:.*}", |r| r.h(fs::StaticFiles::new("tail", ".", true))) + .handler("/static", fs::StaticFiles::new(".", true)) .finish(); } ``` -First parameter is a name of path pattern. Second parameter is a base directory. -Third parameter is *show_index*, if it is set to *true* +First parameter is a base directory. Second parameter is *show_index*, if it is set to *true* directory listing would be returned for directories, if it is set to *false* then *404 Not Found* would be returned instead of directory listing. diff --git a/src/application.rs b/src/application.rs index 7ca1449c0..7eb178997 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,3 +1,4 @@ +use std::mem; use std::rc::Rc; use std::cell::RefCell; use std::collections::HashMap; @@ -22,6 +23,7 @@ pub struct HttpApplication { } pub(crate) struct Inner { + prefix: usize, default: Resource, router: Router, resources: Vec>, @@ -36,12 +38,18 @@ impl PipelineHandler for Inner { } else { for &mut (ref prefix, ref mut handler) in &mut self.handlers { let m = { - let path = req.path(); - path.starts_with(prefix) && ( - path.len() == prefix.len() || - path.split_at(prefix.len()).1.starts_with('/')) + let path = &req.path()[self.prefix..]; + path.starts_with(prefix) && (path.len() == prefix.len() || + path.split_at(prefix.len()).1.starts_with('/')) }; if m { + let path: &'static str = unsafe{ + mem::transmute(&req.path()[self.prefix+prefix.len()..])}; + if path.is_empty() { + req.match_info_mut().add("tail", ""); + } else { + req.match_info_mut().add("tail", path.trim_left_matches('/')); + } return handler.handle(req) } } @@ -335,6 +343,7 @@ impl Application where S: 'static { let inner = Rc::new(RefCell::new( Inner { + prefix: prefix.len(), default: parts.default, router: router.clone(), resources: resources, @@ -487,6 +496,39 @@ mod tests { let req = TestRequest::with_uri("/blah").finish(); let resp = app.run(req); assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + } + + #[test] + fn test_handler_prefix() { + let mut app = Application::new() + .prefix("/app") + .handler("/test", httpcodes::HTTPOk) + .finish(); + + let req = TestRequest::with_uri("/test").finish(); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri("/app/test").finish(); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/test/").finish(); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/test/app").finish(); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/testapp").finish(); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri("/app/blah").finish(); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); } + } diff --git a/src/fs.rs b/src/fs.rs index c7dc68dc7..467e90192 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -191,8 +191,8 @@ impl Responder for FilesystemElement { /// Static files handling /// -/// Can be registered with `Application::resource()`. Resource path has to contain -/// tail named pattern and this name has to be used in `StaticFile` constructor. +/// `StaticFile` handler must be registered with `Application::handler()` method, +/// because `StaticFile` handler requires access sub-path information. /// /// ```rust /// # extern crate actix_web; @@ -200,12 +200,11 @@ impl Responder for FilesystemElement { /// /// fn main() { /// let app = Application::new() -/// .resource("/static/{tail:.*}", |r| r.h(fs::StaticFiles::new("tail", ".", true))) +/// .handler("/static", fs::StaticFiles::new(".", true)) /// .finish(); /// } /// ``` pub struct StaticFiles { - name: String, directory: PathBuf, accessible: bool, show_index: bool, @@ -219,7 +218,7 @@ impl StaticFiles { /// `dir` - base directory /// /// `index` - show index for directory - pub fn new>(name: &str, dir: D, index: bool) -> StaticFiles { + pub fn new>(dir: D, index: bool) -> StaticFiles { let dir = dir.into(); let (dir, access) = match dir.canonicalize() { @@ -238,7 +237,6 @@ impl StaticFiles { }; StaticFiles { - name: name.to_owned(), directory: dir, accessible: access, show_index: index, @@ -256,7 +254,7 @@ impl Handler for StaticFiles { if !self.accessible { Err(io::Error::new(io::ErrorKind::NotFound, "not found")) } else { - let path = if let Some(path) = req.match_info().get(&self.name) { + let path = if let Some(path) = req.match_info().get("tail") { path } else { return Err(io::Error::new(io::ErrorKind::NotFound, "not found")) @@ -300,7 +298,7 @@ mod tests { #[test] fn test_static_files() { - let mut st = StaticFiles::new("tail", ".", true); + let mut st = StaticFiles::new(".", true); st.accessible = false; assert!(st.handle(HttpRequest::default()).is_err()); diff --git a/src/pipeline.rs b/src/pipeline.rs index e8fbfbe8a..3ea1a3209 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -677,7 +677,14 @@ impl ProcessResponse { // response is completed match self.iostate { IOState::Done => { - io.write_eof(); + match io.write_eof() { + Ok(_) => (), + Err(err) => { + debug!("Error sending data: {}", err); + info.error = Some(err.into()); + return Ok(FinishingMiddlewares::init(info, self.resp)) + } + } self.resp.set_response_size(io.written()); Ok(FinishingMiddlewares::init(info, self.resp)) } From 3768a2885d58415c057bac70a389533fd4b79b80 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Jan 2018 15:52:11 -0800 Subject: [PATCH 254/279] fix examples --- examples/basics/src/main.rs | 3 +-- examples/websocket-chat/src/main.rs | 3 +-- examples/websocket/src/main.rs | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/basics/src/main.rs b/examples/basics/src/main.rs index f3f519807..9855fe777 100644 --- a/examples/basics/src/main.rs +++ b/examples/basics/src/main.rs @@ -84,8 +84,7 @@ fn main() { } })) // static files - .resource("/static/{tail:.*}", - |r| r.h(fs::StaticFiles::new("tail", "../static/", true))) + .handler("/static/", fs::StaticFiles::new("tail", "../static/", true)) // redirect .resource("/", |r| r.method(Method::GET).f(|req| { println!("{:?}", req); diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs index a4d3ce333..baa1e1475 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -210,8 +210,7 @@ fn main() { // websocket .resource("/ws/", |r| r.route().f(chat_route)) // static resources - .resource("/static/{tail:.*}", - |r| r.h(fs::StaticFiles::new("tail", "static/", true))) + .handler("/static/", fs::StaticFiles::new("static/", true)) }) .bind("127.0.0.1:8080").unwrap() .start(); diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index dfd0d52c7..fe55e0d83 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -68,8 +68,7 @@ fn main() { // websocket route .resource("/ws/", |r| r.method(Method::GET).f(ws_index)) // static files - .resource("/{tail:.*}", - |r| r.h(fs::StaticFiles::new("tail", "../static/", true)))) + .handler("/", fs::StaticFiles::new("../static/", true))) // start http server on 127.0.0.1:8080 .bind("127.0.0.1:8080").unwrap() .start(); From 3a59344ffbbf7ad6231edc0927b72c8ed1d590f6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Jan 2018 19:37:33 -0800 Subject: [PATCH 255/279] update h2 lib --- Cargo.toml | 3 +++ examples/basics/src/main.rs | 2 +- src/h2.rs | 8 ++++---- src/h2writer.rs | 6 +++--- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7a106be6e..5ba819c4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,3 +115,6 @@ members = [ "examples/websocket", "examples/websocket-chat", ] + +[patch.crates-io] +ring = { git = "https://github.com/SergioBenitez/ring", branch = "v0.12" } diff --git a/examples/basics/src/main.rs b/examples/basics/src/main.rs index 9855fe777..82d0639a6 100644 --- a/examples/basics/src/main.rs +++ b/examples/basics/src/main.rs @@ -84,7 +84,7 @@ fn main() { } })) // static files - .handler("/static/", fs::StaticFiles::new("tail", "../static/", true)) + .handler("/static/", fs::StaticFiles::new("../static/", true)) // redirect .resource("/", |r| r.method(Method::GET).f(|req| { println!("{:?}", req); diff --git a/src/h2.rs b/src/h2.rs index be8898038..89998afc9 100644 --- a/src/h2.rs +++ b/src/h2.rs @@ -8,7 +8,7 @@ use std::collections::VecDeque; use actix::Arbiter; use http::request::Parts; use http2::{Reason, RecvStream}; -use http2::server::{Server, Handshake, Respond}; +use http2::server::{self, Connection, Handshake, SendResponse}; use bytes::{Buf, Bytes}; use futures::{Async, Poll, Future, Stream}; use tokio_io::{AsyncRead, AsyncWrite}; @@ -44,7 +44,7 @@ pub(crate) struct Http2 enum State { Handshake(Handshake), - Server(Server), + Server(Connection), Empty, } @@ -59,7 +59,7 @@ impl Http2 addr: addr, tasks: VecDeque::new(), state: State::Handshake( - Server::handshake(IoWrapper{unread: Some(buf), inner: io})), + server::handshake(IoWrapper{unread: Some(buf), inner: io})), keepalive_timer: None, } } @@ -242,7 +242,7 @@ struct Entry { impl Entry { fn new(parts: Parts, recv: RecvStream, - resp: Respond, + resp: SendResponse, addr: Option, settings: &Rc>) -> Entry where H: HttpHandler + 'static diff --git a/src/h2writer.rs b/src/h2writer.rs index 0b21421f5..9af11010f 100644 --- a/src/h2writer.rs +++ b/src/h2writer.rs @@ -2,7 +2,7 @@ use std::{io, cmp}; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll}; use http2::{Reason, SendStream}; -use http2::server::Respond; +use http2::server::SendResponse; use http::{Version, HttpTryFrom, Response}; use http::header::{HeaderValue, CONNECTION, TRANSFER_ENCODING, DATE, CONTENT_LENGTH}; @@ -26,7 +26,7 @@ bitflags! { } pub(crate) struct H2Writer { - respond: Respond, + respond: SendResponse, stream: Option>, encoder: PayloadEncoder, flags: Flags, @@ -36,7 +36,7 @@ pub(crate) struct H2Writer { impl H2Writer { - pub fn new(respond: Respond, buf: SharedBytes) -> H2Writer { + pub fn new(respond: SendResponse, buf: SharedBytes) -> H2Writer { H2Writer { respond: respond, stream: None, From 7af3b3f9560d1e2933ae240cebb3a3323a3753ae Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Jan 2018 19:43:59 -0800 Subject: [PATCH 256/279] update example --- .travis.yml | 4 ++-- examples/diesel/src/main.rs | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3c55bbde6..ce14e6369 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ rust: - 1.20.0 - stable - beta - - nightly-2017-12-21 + - nightly sudo: required dist: trusty @@ -58,7 +58,7 @@ script: # Upload docs after_success: - | - if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "nightly-2017-12-21" ]]; then + if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; then cargo doc --features alpn --no-deps && echo "" > target/doc/index.html && cargo install mdbook && diff --git a/examples/diesel/src/main.rs b/examples/diesel/src/main.rs index a5b25b21d..56d7c2dc2 100644 --- a/examples/diesel/src/main.rs +++ b/examples/diesel/src/main.rs @@ -19,7 +19,8 @@ extern crate env_logger; use actix::*; use actix_web::*; use actix::prelude::*; -#[cfg(target_os = "linux")] use actix::actors::signal::{ProcessSignals, Subscribe}; +#[cfg(unix)] +use actix::actors::signal::{ProcessSignals, Subscribe}; use diesel::prelude::*; use futures::future::Future; @@ -70,10 +71,10 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); - if cfg!(target_os = "linux") { // Subscribe to unix signals - let signals = Arbiter::system_registry().get::(); - signals.send(Subscribe(_addr.subscriber())); - } + // Subscribe to unix signals + #[cfg(unix)] + { let signals = Arbiter::system_registry().get::(); + signals.send(Subscribe(_addr.subscriber())); } println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); From 9e6d090fd04793d44352bbab45773ffc1f7eb8c9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Jan 2018 19:57:25 -0800 Subject: [PATCH 257/279] update readme example --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 37fb263ef..f861e9601 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ Actix web is a small, fast, down-to-earth, open source rust web framework. ```rust,ignore +extern crate actix; +extern crate actix_web; use actix_web::*; fn index(req: HttpRequest) -> String { @@ -10,11 +12,14 @@ fn index(req: HttpRequest) -> String { } fn main() { + let sys = actix::System::new("readme"); HttpServer::new( || Application::new() .resource("/{name}", |r| r.f(index))) - .bind("127.0.0.1:8080")? + .bind("127.0.0.1:8080").unwrap() .start(); + + sys.run(); } ``` From 70ea43b3c01e2355d7d34bb12b90fef4fdd4d5e1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Jan 2018 23:43:17 -0800 Subject: [PATCH 258/279] fix drain support for actor; make pattern more reusable --- src/application.rs | 4 ++-- src/handler.rs | 18 ++++++++++++++ src/httpcodes.rs | 6 +++++ src/httprequest.rs | 12 +++++----- src/pipeline.rs | 14 ++++++++--- src/router.rs | 58 +++++++++++++++++++++++++++++++--------------- 6 files changed, 82 insertions(+), 30 deletions(-) diff --git a/src/application.rs b/src/application.rs index 7eb178997..8c284e71e 100644 --- a/src/application.rs +++ b/src/application.rs @@ -231,7 +231,7 @@ impl Application where S: 'static { let mut resource = Resource::default(); f(&mut resource); - let pattern = Pattern::new(resource.get_name(), path); + let pattern = Pattern::new(resource.get_name(), path, "^/"); if parts.resources.contains_key(&pattern) { panic!("Resource {:?} is registered.", path); } @@ -285,7 +285,7 @@ impl Application where S: 'static { panic!("External resource {:?} is registered.", name.as_ref()); } parts.external.insert( - String::from(name.as_ref()), Pattern::new(name.as_ref(), url.as_ref())); + String::from(name.as_ref()), Pattern::new(name.as_ref(), url.as_ref(), "^/")); } self } diff --git a/src/handler.rs b/src/handler.rs index 106fecc8e..8f6861e5f 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -144,6 +144,7 @@ impl> Responder for Result } impl> From> for Reply { + #[inline] fn from(res: Result) -> Self { match res { Ok(val) => val, @@ -152,6 +153,23 @@ impl> From> for Reply { } } +impl> From> for Reply { + #[inline] + fn from(res: Result) -> Self { + match res { + Ok(val) => Reply(ReplyItem::Message(val)), + Err(err) => Reply(ReplyItem::Message(err.into().into())), + } + } +} + +impl From>> for Reply { + #[inline] + fn from(fut: Box>) -> Reply { + Reply(ReplyItem::Future(fut)) + } +} + impl Responder for Box> where I: Responder + 'static, E: Into + 'static diff --git a/src/httpcodes.rs b/src/httpcodes.rs index e27c56237..c39167dd7 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -93,6 +93,12 @@ impl From for HttpResponse { } } +impl From for Reply { + fn from(st: StaticResponse) -> Self { + HttpResponse::new(st.0, Body::Empty).into() + } +} + macro_rules! STATIC_RESP { ($name:ident, $status:expr) => { #[allow(non_snake_case)] diff --git a/src/httprequest.rs b/src/httprequest.rs index dd8de37dc..c39c533db 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -137,8 +137,8 @@ impl HttpRequest { #[inline] /// Construct new http request with state. - pub fn change_state(self, state: Rc) -> HttpRequest { - HttpRequest(self.0, Some(state), self.2.clone()) + pub fn change_state(&self, state: Rc) -> HttpRequest { + HttpRequest(self.0.clone(), Some(state), self.2.clone()) } #[inline] @@ -726,7 +726,7 @@ mod tests { let mut resource = Resource::<()>::default(); resource.name("index"); let mut map = HashMap::new(); - map.insert(Pattern::new("index", "/{key}/"), Some(resource)); + map.insert(Pattern::new("index", "/{key}/", "^/"), Some(resource)); let (router, _) = Router::new("", ServerSettings::default(), map); assert!(router.recognize(&mut req).is_some()); @@ -828,7 +828,7 @@ mod tests { let mut resource = Resource::<()>::default(); resource.name("index"); let mut map = HashMap::new(); - map.insert(Pattern::new("index", "/user/{name}.{ext}"), Some(resource)); + map.insert(Pattern::new("index", "/user/{name}.{ext}", "^/"), Some(resource)); let (router, _) = Router::new("/", ServerSettings::default(), map); assert!(router.has_route("/user/test.html")); assert!(!router.has_route("/test/unknown")); @@ -857,7 +857,7 @@ mod tests { let mut resource = Resource::<()>::default(); resource.name("index"); let mut map = HashMap::new(); - map.insert(Pattern::new("index", "/user/{name}.{ext}"), Some(resource)); + map.insert(Pattern::new("index", "/user/{name}.{ext}", "^/"), Some(resource)); let (router, _) = Router::new("/prefix/", ServerSettings::default(), map); assert!(router.has_route("/user/test.html")); assert!(!router.has_route("/prefix/user/test.html")); @@ -876,7 +876,7 @@ mod tests { let mut resource = Resource::<()>::default(); resource.name("index"); let mut map = HashMap::new(); - map.insert(Pattern::new("youtube", "https://youtube.com/watch/{video_id}"), None); + map.insert(Pattern::new("youtube", "https://youtube.com/watch/{video_id}", "^/"), None); let (router, _) = Router::new::<()>("", ServerSettings::default(), map); assert!(!router.has_route("https://youtube.com/watch/unknown")); diff --git a/src/pipeline.rs b/src/pipeline.rs index 3ea1a3209..7e2681c57 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -40,6 +40,7 @@ struct PipelineInfo { mws: Rc>>>, context: Option>, error: Option, + disconnected: Option, } impl PipelineInfo { @@ -50,6 +51,7 @@ impl PipelineInfo { mws: Rc::new(Vec::new()), error: None, context: None, + disconnected: None, } } @@ -84,6 +86,7 @@ impl> Pipeline { mws: mws, error: None, context: None, + disconnected: None, }; let state = StartMiddlewares::init(&mut info, handler); @@ -114,9 +117,7 @@ impl Pipeline { impl> HttpHandlerTask for Pipeline { fn disconnected(&mut self) { - if let Some(ref mut context) = self.0.context { - context.disconnected(); - } + self.0.disconnected = Some(true); } fn poll_io(&mut self, io: &mut Writer) -> Poll { @@ -592,10 +593,14 @@ impl ProcessResponse { } }, IOState::Actor(mut ctx) => { + if info.disconnected.take().is_some() { + ctx.disconnected(); + } match ctx.poll() { Ok(Async::Ready(Some(frame))) => { match frame { Frame::Payload(None) => { + println!("ACTOR PAYLOAD EOF"); info.context = Some(ctx); self.iostate = IOState::Done; if let Err(err) = io.write_eof() { @@ -606,6 +611,7 @@ impl ProcessResponse { break }, Frame::Payload(Some(chunk)) => { + println!("ACTOR PAYLOAD"); self.iostate = IOState::Actor(ctx); match io.write(chunk.as_ref()) { Err(err) => { @@ -617,7 +623,9 @@ impl ProcessResponse { } }, Frame::Drain(fut) => { + println!("ACTOR DRAIN"); self.drain = Some(fut); + self.iostate = IOState::Actor(ctx); break } } diff --git a/src/router.rs b/src/router.rs index ebd763bfd..ca6783413 100644 --- a/src/router.rs +++ b/src/router.rs @@ -88,8 +88,7 @@ impl Router { } if let Some(idx) = idx { - let path: &str = unsafe{ mem::transmute(&req.path()[self.0.prefix_len..]) }; - self.0.patterns[idx].update_match_info(path, req); + self.0.patterns[idx].update_match_info(req, self.0.prefix_len); return Some(idx) } else { None @@ -159,8 +158,8 @@ impl Pattern { /// Parse path pattern and create new `Pattern` instance. /// /// Panics if path pattern is wrong. - pub fn new(name: &str, path: &str) -> Self { - let (pattern, elements) = Pattern::parse(path); + pub fn new(name: &str, path: &str, starts: &str) -> Self { + let (pattern, elements) = Pattern::parse(path, starts); let re = match Regex::new(&pattern) { Ok(re) => re, @@ -190,8 +189,9 @@ impl Pattern { } /// Extract pattern parameters from the text - pub fn update_match_info(&self, text: &str, req: &mut HttpRequest) { + pub fn update_match_info(&self, req: &mut HttpRequest, prefix: usize) { if !self.names.is_empty() { + let text: &str = unsafe{ mem::transmute(&req.path()[prefix..]) }; if let Some(captures) = self.re.captures(text) { let mut idx = 0; for capture in captures.iter() { @@ -207,6 +207,25 @@ impl Pattern { } } + /// Extract pattern parameters from the text + pub fn get_match_info<'a>(&self, text: &'a str) -> HashMap<&str, &'a str> { + let mut info = HashMap::new(); + if !self.names.is_empty() { + if let Some(captures) = self.re.captures(text) { + let mut idx = 0; + for capture in captures.iter() { + if let Some(ref m) = capture { + if idx != 0 { + info.insert(self.names[idx-1].as_str(), m.as_str()); + } + idx += 1; + } + } + }; + } + info + } + /// Build pattern path. pub fn path(&self, prefix: Option<&str>, elements: U) -> Result where U: IntoIterator, @@ -218,7 +237,6 @@ impl Pattern { } else { String::new() }; - println!("TEST: {:?} {:?}", path, prefix); for el in &self.elements { match *el { PatternElement::Str(ref s) => path.push_str(s), @@ -234,10 +252,10 @@ impl Pattern { Ok(path) } - fn parse(pattern: &str) -> (String, Vec) { + fn parse(pattern: &str, starts: &str) -> (String, Vec) { const DEFAULT_PATTERN: &str = "[^/]+"; - let mut re = String::from("^/"); + let mut re = String::from(starts); let mut el = String::new(); let mut in_param = false; let mut in_param_pattern = false; @@ -312,12 +330,14 @@ mod tests { #[test] fn test_recognizer() { let mut routes = HashMap::new(); - routes.insert(Pattern::new("", "/name"), Some(Resource::default())); - routes.insert(Pattern::new("", "/name/{val}"), Some(Resource::default())); - routes.insert(Pattern::new("", "/name/{val}/index.html"), Some(Resource::default())); - routes.insert(Pattern::new("", "/v{val}/{val2}/index.html"), Some(Resource::default())); - routes.insert(Pattern::new("", "/v/{tail:.*}"), Some(Resource::default())); - routes.insert(Pattern::new("", "{test}/index.html"), Some(Resource::default())); + routes.insert(Pattern::new("", "/name", "^/"), Some(Resource::default())); + routes.insert(Pattern::new("", "/name/{val}", "^/"), Some(Resource::default())); + routes.insert(Pattern::new("", "/name/{val}/index.html", "^/"), + Some(Resource::default())); + routes.insert(Pattern::new("", "/v{val}/{val2}/index.html", "^/"), + Some(Resource::default())); + routes.insert(Pattern::new("", "/v/{tail:.*}", "^/"), Some(Resource::default())); + routes.insert(Pattern::new("", "{test}/index.html", "^/"), Some(Resource::default())); let (rec, _) = Router::new::<()>("", ServerSettings::default(), routes); let mut req = TestRequest::with_uri("/name").finish(); @@ -350,8 +370,8 @@ mod tests { #[test] fn test_recognizer_with_prefix() { let mut routes = HashMap::new(); - routes.insert(Pattern::new("", "/name"), Some(Resource::default())); - routes.insert(Pattern::new("", "/name/{val}"), Some(Resource::default())); + routes.insert(Pattern::new("", "/name", "^/"), Some(Resource::default())); + routes.insert(Pattern::new("", "/name/{val}", "^/"), Some(Resource::default())); let (rec, _) = Router::new::<()>("/test", ServerSettings::default(), routes); let mut req = TestRequest::with_uri("/name").finish(); @@ -367,8 +387,8 @@ mod tests { // same patterns let mut routes = HashMap::new(); - routes.insert(Pattern::new("", "/name"), Some(Resource::default())); - routes.insert(Pattern::new("", "/name/{val}"), Some(Resource::default())); + routes.insert(Pattern::new("", "/name", "^/"), Some(Resource::default())); + routes.insert(Pattern::new("", "/name/{val}", "^/"), Some(Resource::default())); let (rec, _) = Router::new::<()>("/test2", ServerSettings::default(), routes); let mut req = TestRequest::with_uri("/name").finish(); @@ -378,7 +398,7 @@ mod tests { } fn assert_parse(pattern: &str, expected_re: &str) -> Regex { - let (re_str, _) = Pattern::parse(pattern); + let (re_str, _) = Pattern::parse(pattern, "^/"); assert_eq!(&*re_str, expected_re); Regex::new(&re_str).unwrap() } From 88031b7fdee56ed64a480edc33b1a0d01f8f1a17 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Jan 2018 09:00:22 -0800 Subject: [PATCH 259/279] remove debug prints --- src/pipeline.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pipeline.rs b/src/pipeline.rs index 7e2681c57..ad26266fa 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -600,7 +600,6 @@ impl ProcessResponse { Ok(Async::Ready(Some(frame))) => { match frame { Frame::Payload(None) => { - println!("ACTOR PAYLOAD EOF"); info.context = Some(ctx); self.iostate = IOState::Done; if let Err(err) = io.write_eof() { @@ -611,7 +610,6 @@ impl ProcessResponse { break }, Frame::Payload(Some(chunk)) => { - println!("ACTOR PAYLOAD"); self.iostate = IOState::Actor(ctx); match io.write(chunk.as_ref()) { Err(err) => { @@ -623,7 +621,6 @@ impl ProcessResponse { } }, Frame::Drain(fut) => { - println!("ACTOR DRAIN"); self.drain = Some(fut); self.iostate = IOState::Actor(ctx); break From ae084d1146acedd146cc80d80c29092df96ff7d1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Jan 2018 09:23:58 -0800 Subject: [PATCH 260/279] added helper future for reading request body --- src/error.rs | 6 +++ src/httprequest.rs | 109 +++++++++++++++++++++++++++++++++++++++++++-- src/json.rs | 4 +- src/lib.rs | 2 +- 4 files changed, 115 insertions(+), 6 deletions(-) diff --git a/src/error.rs b/src/error.rs index 31ae69fbb..29a94d4c0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -196,6 +196,12 @@ pub enum PayloadError { /// Content encoding stream corruption #[fail(display="Can not decode content-encoding.")] EncodingCorrupted, + /// A payload reached size limit. + #[fail(display="A payload reached size limit.")] + Overflow, + /// A payload length is unknown. + #[fail(display="A payload length is unknown.")] + UnknownLength, /// Parse error #[fail(display="{}", _0)] ParseError(#[cause] IoError), diff --git a/src/httprequest.rs b/src/httprequest.rs index c39c533db..2c56534d6 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -3,7 +3,7 @@ use std::{str, fmt, mem}; use std::rc::Rc; use std::net::SocketAddr; use std::collections::HashMap; -use bytes::BytesMut; +use bytes::{Bytes, BytesMut}; use cookie::Cookie; use futures::{Async, Future, Stream, Poll}; use http_range::HttpRange; @@ -14,11 +14,12 @@ use http::{header, Uri, Method, Version, HeaderMap, Extensions}; use info::ConnectionInfo; use param::Params; use router::Router; -use payload::Payload; +use payload::{Payload, ReadAny}; use json::JsonBody; use multipart::Multipart; use helpers::SharedHttpMessage; -use error::{ParseError, UrlGenerationError, CookieParseError, HttpRangeError, UrlencodedError}; +use error::{ParseError, UrlGenerationError, + CookieParseError, HttpRangeError, PayloadError, UrlencodedError}; pub struct HttpMessage { @@ -424,6 +425,36 @@ impl HttpRequest { msg.payload.as_mut().unwrap() } + /// Load request body. + /// + /// By default only 256Kb payload reads to a memory, then `BAD REQUEST` + /// http response get returns to a peer. Use `RequestBody::limit()` + /// method to change upper limit. + /// + /// ```rust + /// # extern crate bytes; + /// # extern crate actix_web; + /// # extern crate futures; + /// # #[macro_use] extern crate serde_derive; + /// use actix_web::*; + /// use bytes::Bytes; + /// use futures::future::Future; + /// + /// fn index(mut req: HttpRequest) -> Box> { + /// req.body() // <- get Body future + /// .limit(1024) // <- change max size of the body to a 1kb + /// .from_err() + /// .and_then(|bytes: Bytes| { // <- complete body + /// println!("==== BODY ==== {:?}", bytes); + /// Ok(httpcodes::HTTPOk.into()) + /// }).responder() + /// } + /// # fn main() {} + /// ``` + pub fn body(&mut self) -> RequestBody { + RequestBody::from_request(self) + } + /// Return stream to http payload processes as multipart. /// /// Content-type: multipart/form-data; @@ -642,6 +673,78 @@ impl Future for UrlEncoded { } } +/// Future that resolves to a complete request body. +pub struct RequestBody { + pl: ReadAny, + body: BytesMut, + limit: usize, + error: Option, +} + +impl RequestBody { + + /// Create `RequestBody` for request. + pub fn from_request(req: &mut HttpRequest) -> RequestBody { + let mut body = RequestBody { + pl: req.payload().readany(), + body: BytesMut::new(), + limit: 262_144, + error: None + }; + + if let Some(len) = req.headers().get(header::CONTENT_LENGTH) { + if let Ok(s) = len.to_str() { + if let Ok(len) = s.parse::() { + if len > 262_144 { + body.error = Some(PayloadError::Overflow); + } + } else { + body.error = Some(PayloadError::UnknownLength); + } + } else { + body.error = Some(PayloadError::UnknownLength); + } + } + + body + } + + /// Change max size of payload. By default max size is 256Kb + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } +} + +impl Future for RequestBody { + type Item = Bytes; + type Error = PayloadError; + + fn poll(&mut self) -> Poll { + if let Some(err) = self.error.take() { + return Err(err) + } + + loop { + return match self.pl.poll() { + Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(None)) => { + Ok(Async::Ready(self.body.take().freeze())) + }, + Ok(Async::Ready(Some(chunk))) => { + if (self.body.len() + chunk.len()) > self.limit { + Err(PayloadError::Overflow) + } else { + self.body.extend_from_slice(&chunk); + continue + } + }, + Err(err) => Err(err), + } + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/json.rs b/src/json.rs index 263f6028f..ce9f72eeb 100644 --- a/src/json.rs +++ b/src/json.rs @@ -116,7 +116,7 @@ impl Future for JsonBody { type Error = JsonPayloadError; fn poll(&mut self) -> Poll { - if let Some(mut req) = self.req.take() { + if let Some(req) = self.req.take() { if let Some(len) = req.headers().get(CONTENT_LENGTH) { if let Ok(s) = len.to_str() { if let Ok(len) = s.parse::() { @@ -134,7 +134,7 @@ impl Future for JsonBody { } let limit = self.limit; - let fut = req.payload_mut().readany() + let fut = req.payload().readany() .from_err() .fold(BytesMut::new(), move |mut body, chunk| { if (body.len() + chunk.len()) > limit { diff --git a/src/lib.rs b/src/lib.rs index e453a2013..5238a684e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -172,7 +172,7 @@ pub mod dev { pub use router::{Router, Pattern}; pub use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; pub use param::{FromParam, Params}; - pub use httprequest::UrlEncoded; + pub use httprequest::{UrlEncoded, RequestBody}; pub use httpresponse::HttpResponseBuilder; pub use server::{ServerSettings, PauseServer, ResumeServer, StopServer}; From 8348c830e2e6b8164b67dd8edf5617313ef54e61 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Jan 2018 10:57:57 -0800 Subject: [PATCH 261/279] no need for mut ref --- src/httprequest.rs | 120 +++++++++++++++++++++++---------------------- src/json.rs | 2 +- src/test.rs | 10 ++++ 3 files changed, 72 insertions(+), 60 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index 2c56534d6..2498a9298 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -132,6 +132,12 @@ impl HttpRequest<()> { pub fn with_state(self, state: Rc, router: Router) -> HttpRequest { HttpRequest(self.0, Some(state), Some(router)) } + + #[cfg(test)] + /// Construct new http request with state. + pub(crate) fn with_state_no_router(self, state: Rc) -> HttpRequest { + HttpRequest(self.0, Some(state), None) + } } impl HttpRequest { @@ -451,7 +457,7 @@ impl HttpRequest { /// } /// # fn main() {} /// ``` - pub fn body(&mut self) -> RequestBody { + pub fn body(&self) -> RequestBody { RequestBody::from_request(self) } @@ -520,7 +526,7 @@ impl HttpRequest { /// } /// # fn main() {} /// ``` - pub fn urlencoded(&mut self) -> UrlEncoded { + pub fn urlencoded(&self) -> UrlEncoded { UrlEncoded::from_request(self) } @@ -554,7 +560,7 @@ impl HttpRequest { /// } /// # fn main() {} /// ``` - pub fn json(&mut self) -> JsonBody { + pub fn json(&self) -> JsonBody { JsonBody::from_request(self) } } @@ -604,9 +610,9 @@ pub struct UrlEncoded { } impl UrlEncoded { - pub fn from_request(req: &mut HttpRequest) -> UrlEncoded { + pub fn from_request(req: &HttpRequest) -> UrlEncoded { let mut encoded = UrlEncoded { - pl: req.payload_mut().clone(), + pl: req.payload().clone(), body: BytesMut::new(), error: None }; @@ -684,7 +690,7 @@ pub struct RequestBody { impl RequestBody { /// Create `RequestBody` for request. - pub fn from_request(req: &mut HttpRequest) -> RequestBody { + pub fn from_request(req: &HttpRequest) -> RequestBody { let mut body = RequestBody { pl: req.payload().readany(), body: BytesMut::new(), @@ -793,20 +799,14 @@ mod tests { #[test] fn test_no_request_range_header() { - let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); + let req = HttpRequest::default(); let ranges = req.range(100).unwrap(); assert!(ranges.is_empty()); } #[test] fn test_request_range_header() { - let mut headers = HeaderMap::new(); - headers.insert(header::RANGE, - header::HeaderValue::from_static("bytes=0-4")); - - let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, None); + let req = TestRequest::with_header(header::RANGE, "bytes=0-4").finish(); let ranges = req.range(100).unwrap(); assert_eq!(ranges.len(), 1); assert_eq!(ranges[0].start, 0); @@ -815,8 +815,7 @@ mod tests { #[test] fn test_request_query() { - let req = HttpRequest::new(Method::GET, Uri::from_str("/?id=test").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); + let req = TestRequest::with_uri("/?id=test").finish(); assert_eq!(req.query_string(), "id=test"); let query = req.query(); assert_eq!(&query["id"], "test"); @@ -881,52 +880,61 @@ mod tests { #[test] fn test_urlencoded_error() { - let mut headers = HeaderMap::new(); - headers.insert(header::TRANSFER_ENCODING, - header::HeaderValue::from_static("chunked")); - let mut req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); - + let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish(); assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Chunked); - let mut headers = HeaderMap::new(); - headers.insert(header::CONTENT_TYPE, - header::HeaderValue::from_static("application/x-www-form-urlencoded")); - headers.insert(header::CONTENT_LENGTH, - header::HeaderValue::from_static("xxxx")); - let mut req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); - + let req = TestRequest::with_header( + header::CONTENT_TYPE, "application/x-www-form-urlencoded") + .header(header::CONTENT_LENGTH, "xxxx") + .finish(); assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::UnknownLength); - let mut headers = HeaderMap::new(); - headers.insert(header::CONTENT_TYPE, - header::HeaderValue::from_static("application/x-www-form-urlencoded")); - headers.insert(header::CONTENT_LENGTH, - header::HeaderValue::from_static("1000000")); - let mut req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); - + let req = TestRequest::with_header( + header::CONTENT_TYPE, "application/x-www-form-urlencoded") + .header(header::CONTENT_LENGTH, "1000000") + .finish(); assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Overflow); - let mut headers = HeaderMap::new(); - headers.insert(header::CONTENT_TYPE, - header::HeaderValue::from_static("text/plain")); - headers.insert(header::CONTENT_LENGTH, - header::HeaderValue::from_static("10")); - let mut req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); - + let req = TestRequest::with_header( + header::CONTENT_TYPE, "text/plain") + .header(header::CONTENT_LENGTH, "10") + .finish(); assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::ContentType); } + #[test] + fn test_request_body() { + let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); + match req.body().poll().err().unwrap() { + PayloadError::UnknownLength => (), + _ => panic!("error"), + } + + let req = TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish(); + match req.body().poll().err().unwrap() { + PayloadError::Overflow => (), + _ => panic!("error"), + } + + let mut req = HttpRequest::default(); + req.payload_mut().unread_data(Bytes::from_static(b"test")); + match req.body().poll().ok().unwrap() { + Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")), + _ => panic!("error"), + } + + let mut req = HttpRequest::default(); + req.payload_mut().unread_data(Bytes::from_static(b"11111111111111")); + match req.body().limit(5).poll().err().unwrap() { + PayloadError::Overflow => (), + _ => panic!("error"), + } + } + #[test] fn test_url_for() { - let mut headers = HeaderMap::new(); - headers.insert(header::HOST, - header::HeaderValue::from_static("www.rust-lang.org")); - let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); + let req = TestRequest::with_header(header::HOST, "www.rust-lang.org") + .finish_no_router(); let mut resource = Resource::<()>::default(); resource.name("index"); @@ -951,11 +959,7 @@ mod tests { #[test] fn test_url_for_with_prefix() { - let mut headers = HeaderMap::new(); - headers.insert(header::HOST, - header::HeaderValue::from_static("www.rust-lang.org")); - let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); + let req = TestRequest::with_header(header::HOST, "www.rust-lang.org").finish(); let mut resource = Resource::<()>::default(); resource.name("index"); @@ -972,9 +976,7 @@ mod tests { #[test] fn test_url_for_external() { - let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); + let req = HttpRequest::default(); let mut resource = Resource::<()>::default(); resource.name("index"); diff --git a/src/json.rs b/src/json.rs index ce9f72eeb..8bcda5c90 100644 --- a/src/json.rs +++ b/src/json.rs @@ -86,7 +86,7 @@ pub struct JsonBody{ impl JsonBody { /// Create `JsonBody` for request. - pub fn from_request(req: &mut HttpRequest) -> Self { + pub fn from_request(req: &HttpRequest) -> Self { JsonBody{ limit: 262_144, req: Some(req.clone()), diff --git a/src/test.rs b/src/test.rs index a0d8a8de5..f92ed8e62 100644 --- a/src/test.rs +++ b/src/test.rs @@ -351,6 +351,16 @@ impl TestRequest { req.with_state(Rc::new(state), router) } + #[cfg(test)] + /// Complete request creation and generate `HttpRequest` instance + pub(crate) fn finish_no_router(self) -> HttpRequest { + let TestRequest { state, method, uri, version, headers, params, cookies, payload } = self; + let req = HttpRequest::new(method, uri, version, headers, payload); + req.as_mut().cookies = cookies; + req.as_mut().params = params; + req.with_state_no_router(Rc::new(state)) + } + /// This method generates `HttpRequest` instance and runs handler /// with generated request. /// From e439d0546b992687dc57ab4d7b3b7a1dc2e320a6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Jan 2018 18:21:34 -0800 Subject: [PATCH 262/279] * fix force_close * shutdown io before exit * fix response creation with body from pool --- src/application.rs | 2 +- src/channel.rs | 4 +++- src/context.rs | 11 ---------- src/h1.rs | 53 +++++++++++++++++++++++++-------------------- src/h1writer.rs | 16 +++++++++----- src/h2writer.rs | 2 +- src/httpresponse.rs | 2 +- src/pipeline.rs | 2 +- src/router.rs | 2 ++ 9 files changed, 48 insertions(+), 46 deletions(-) diff --git a/src/application.rs b/src/application.rs index 8c284e71e..1e4d8273c 100644 --- a/src/application.rs +++ b/src/application.rs @@ -48,7 +48,7 @@ impl PipelineHandler for Inner { if path.is_empty() { req.match_info_mut().add("tail", ""); } else { - req.match_info_mut().add("tail", path.trim_left_matches('/')); + req.match_info_mut().add("tail", path.split_at(1).1); } return handler.handle(req) } diff --git a/src/channel.rs b/src/channel.rs index 633a05952..d736fd202 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -79,7 +79,9 @@ impl HttpChannel } } -/*impl Drop for HttpChannel { +/*impl Drop for HttpChannel + where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static +{ fn drop(&mut self) { println!("Drop http channel"); } diff --git a/src/context.rs b/src/context.rs index 1cdb6b9b4..dff9344a2 100644 --- a/src/context.rs +++ b/src/context.rs @@ -14,7 +14,6 @@ use actix::dev::{AsyncContextApi, ActorAddressCell, ActorItemsCell, ActorWaitCel use body::{Body, Binary}; use error::{Error, Result}; use httprequest::HttpRequest; -use httpresponse::HttpResponse; pub trait ActorHttpContext: 'static { @@ -124,16 +123,6 @@ impl HttpContext where A: Actor { self.act = Some(actor); self } - - pub fn with_actor(mut self, actor: A, mut resp: HttpResponse) -> Result { - if self.act.is_some() { - panic!("Actor is set already"); - } - self.act = Some(actor); - - resp.replace_body(Body::Actor(Box::new(self))); - Ok(resp) - } } impl HttpContext where A: Actor { diff --git a/src/h1.rs b/src/h1.rs index f49d0a2e7..e4d2930b0 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -97,11 +97,11 @@ impl Http1 (self.settings, self.stream.into_inner(), self.addr, self.read_buf.freeze()) } - fn poll_completed(&mut self) -> Result { + fn poll_completed(&mut self, shutdown: bool) -> Result { // check stream state - match self.stream.poll_completed() { - Ok(Async::Ready(_)) => Ok(false), - Ok(Async::NotReady) => Ok(true), + match self.stream.poll_completed(shutdown) { + Ok(Async::Ready(_)) => Ok(true), + Ok(Async::NotReady) => Ok(false), Err(err) => { debug!("Error sending data: {}", err); Err(()) @@ -136,7 +136,7 @@ impl Http1 if !io && !item.flags.contains(EntryFlags::EOF) { if item.flags.contains(EntryFlags::ERROR) { // check stream state - if let Ok(Async::NotReady) = self.stream.poll_completed() { + if let Ok(Async::NotReady) = self.stream.poll_completed(true) { return Ok(Async::NotReady) } return Err(()) @@ -147,12 +147,10 @@ impl Http1 not_ready = false; // overide keep-alive state - if self.settings.keep_alive_enabled() { - if self.stream.keepalive() { - self.flags.insert(Flags::KEEPALIVE); - } else { - self.flags.remove(Flags::KEEPALIVE); - } + if self.stream.keepalive() { + self.flags.insert(Flags::KEEPALIVE); + } else { + self.flags.remove(Flags::KEEPALIVE); } self.stream.reset(); @@ -172,7 +170,7 @@ impl Http1 item.flags.insert(EntryFlags::ERROR); // check stream state, we still can have valid data in buffer - if let Ok(Async::NotReady) = self.stream.poll_completed() { + if let Ok(Async::NotReady) = self.stream.poll_completed(true) { return Ok(Async::NotReady) } return Err(()) @@ -207,11 +205,14 @@ impl Http1 // no keep-alive if !self.flags.contains(Flags::KEEPALIVE) && self.tasks.is_empty() { + let h2 = self.flags.contains(Flags::H2); + // check stream state - if self.poll_completed()? { + if !self.poll_completed(!h2)? { return Ok(Async::NotReady) } - if self.flags.contains(Flags::H2) { + + if h2 { return Ok(Async::Ready(Http1Result::Switch)) } else { return Ok(Async::Ready(Http1Result::Done)) @@ -284,7 +285,7 @@ impl Http1 } } Ok(Async::NotReady) => { - // start keep-alive timer, this is also slow request timeout + // start keep-alive timer, this also is slow request timeout if self.tasks.is_empty() { if self.settings.keep_alive_enabled() { let keep_alive = self.settings.keep_alive(); @@ -300,17 +301,20 @@ impl Http1 } } else { // check stream state - if self.poll_completed()? { + if !self.poll_completed(true)? { return Ok(Async::NotReady) } // keep-alive disable, drop connection return Ok(Async::Ready(Http1Result::Done)) } - } else { - // check stream state - self.poll_completed()?; - // keep-alive unset, rely on operating system + } else if !self.poll_completed(false)? || + self.flags.contains(Flags::KEEPALIVE) + { + // check stream state or + // if keep-alive unset, rely on operating system return Ok(Async::NotReady) + } else { + return Ok(Async::Ready(Http1Result::Done)) } } break @@ -320,12 +324,13 @@ impl Http1 // check for parse error if self.tasks.is_empty() { + let h2 = self.flags.contains(Flags::H2); + // check stream state - if self.poll_completed()? { + if !self.poll_completed(!h2)? { return Ok(Async::NotReady) } - - if self.flags.contains(Flags::H2) { + if h2 { return Ok(Async::Ready(Http1Result::Switch)) } if self.flags.contains(Flags::ERROR) || self.keepalive_timer.is_none() { @@ -334,7 +339,7 @@ impl Http1 } if not_ready { - self.poll_completed()?; + self.poll_completed(false)?; return Ok(Async::NotReady) } } diff --git a/src/h1writer.rs b/src/h1writer.rs index 5352e7435..200ff0529 100644 --- a/src/h1writer.rs +++ b/src/h1writer.rs @@ -33,7 +33,7 @@ pub trait Writer { fn write_eof(&mut self) -> Result; - fn poll_completed(&mut self) -> Poll<(), io::Error>; + fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error>; } bitflags! { @@ -94,7 +94,7 @@ impl H1Writer { while !buffer.is_empty() { match self.stream.write(buffer.as_ref()) { Ok(n) => { - buffer.split_to(n); + let _ = buffer.split_to(n); }, Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { if buffer.len() > MAX_WRITE_BUFFER_SIZE { @@ -112,7 +112,6 @@ impl H1Writer { impl Writer for H1Writer { - #[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))] fn written(&self) -> u64 { self.written } @@ -218,7 +217,6 @@ impl Writer for H1Writer { self.encoder.write_eof()?; if !self.encoder.is_eof() { - // debug!("last payload item, but it is not EOF "); Err(io::Error::new(io::ErrorKind::Other, "Last payload item, but eof is not reached")) } else if self.encoder.len() > MAX_WRITE_BUFFER_SIZE { @@ -228,9 +226,15 @@ impl Writer for H1Writer { } } - fn poll_completed(&mut self) -> Poll<(), io::Error> { + fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> { match self.write_to_stream() { - Ok(WriterState::Done) => Ok(Async::Ready(())), + Ok(WriterState::Done) => { + if shutdown { + self.stream.shutdown() + } else { + Ok(Async::Ready(())) + } + }, Ok(WriterState::Pause) => Ok(Async::NotReady), Err(err) => Err(err) } diff --git a/src/h2writer.rs b/src/h2writer.rs index 9af11010f..57c4bd357 100644 --- a/src/h2writer.rs +++ b/src/h2writer.rs @@ -213,7 +213,7 @@ impl Writer for H2Writer { } } - fn poll_completed(&mut self) -> Poll<(), io::Error> { + fn poll_completed(&mut self, _shutdown: bool) -> Poll<(), io::Error> { match self.write_to_stream() { Ok(WriterState::Done) => Ok(Async::Ready(())), Ok(WriterState::Pause) => Ok(Async::NotReady), diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 5d5e85fcb..f08553013 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -674,7 +674,7 @@ impl Pool { POOL.with(|pool| { if let Some(mut resp) = pool.borrow_mut().0.pop_front() { resp.status = status; - resp.body = Body::Empty; + resp.body = body; resp } else { Box::new(InnerHttpResponse::new(status, body)) diff --git a/src/pipeline.rs b/src/pipeline.rs index ad26266fa..44c503104 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -658,7 +658,7 @@ impl ProcessResponse { // flush io but only if we need to if self.running == RunningState::Paused || self.drain.is_some() { - match io.poll_completed() { + match io.poll_completed(false) { Ok(Async::Ready(_)) => { self.running.resume(); diff --git a/src/router.rs b/src/router.rs index ca6783413..560f7de79 100644 --- a/src/router.rs +++ b/src/router.rs @@ -189,6 +189,8 @@ impl Pattern { } /// Extract pattern parameters from the text + // This method unsafe internally, assumption that Pattern instance lives + // longer than `req` pub fn update_match_info(&self, req: &mut HttpRequest, prefix: usize) { if !self.names.is_empty() { let text: &str = unsafe{ mem::transmute(&req.path()[prefix..]) }; From bf11bfed8ef6f688f2f2a777e076da842a2d1047 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Jan 2018 19:11:40 -0800 Subject: [PATCH 263/279] use explicit actix:: mod --- examples/basics/src/main.rs | 2 +- examples/diesel/src/main.rs | 2 +- examples/json/src/main.rs | 2 +- examples/multipart/src/main.rs | 2 +- examples/signals/src/main.rs | 2 +- examples/state/src/main.rs | 2 +- examples/template_tera/src/main.rs | 4 ++-- examples/tls/src/main.rs | 2 +- examples/websocket-chat/src/main.rs | 2 +- examples/websocket/src/main.rs | 2 +- guide/src/qs_3_5.md | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/basics/src/main.rs b/examples/basics/src/main.rs index 82d0639a6..64c6809c9 100644 --- a/examples/basics/src/main.rs +++ b/examples/basics/src/main.rs @@ -99,7 +99,7 @@ fn main() { // Subscribe to unix signals #[cfg(unix)] { - let signals = Arbiter::system_registry().get::(); + let signals = actix::Arbiter::system_registry().get::(); signals.send(Subscribe(addr.subscriber())); } diff --git a/examples/diesel/src/main.rs b/examples/diesel/src/main.rs index 56d7c2dc2..09e8424d1 100644 --- a/examples/diesel/src/main.rs +++ b/examples/diesel/src/main.rs @@ -73,7 +73,7 @@ fn main() { // Subscribe to unix signals #[cfg(unix)] - { let signals = Arbiter::system_registry().get::(); + { let signals = actix::Arbiter::system_registry().get::(); signals.send(Subscribe(_addr.subscriber())); } println!("Started http server: 127.0.0.1:8080"); diff --git a/examples/json/src/main.rs b/examples/json/src/main.rs index 296d4a4bc..3d1338135 100644 --- a/examples/json/src/main.rs +++ b/examples/json/src/main.rs @@ -100,7 +100,7 @@ fn main() { // Subscribe to unix signals #[cfg(unix)] { - let signals = Arbiter::system_registry().get::(); + let signals = actix::Arbiter::system_registry().get::(); signals.send(Subscribe(addr.subscriber())); } diff --git a/examples/multipart/src/main.rs b/examples/multipart/src/main.rs index 407365cd6..38b6d5325 100644 --- a/examples/multipart/src/main.rs +++ b/examples/multipart/src/main.rs @@ -59,7 +59,7 @@ fn main() { // Subscribe to unix signals #[cfg(unix)] { - let signals = Arbiter::system_registry().get::(); + let signals = actix::Arbiter::system_registry().get::(); signals.send(Subscribe(addr.subscriber())); } diff --git a/examples/signals/src/main.rs b/examples/signals/src/main.rs index 7f939081a..d010732e7 100644 --- a/examples/signals/src/main.rs +++ b/examples/signals/src/main.rs @@ -38,7 +38,7 @@ fn main() { // Subscribe to unix signals #[cfg(unix)] { - let signals = Arbiter::system_registry().get::(); + let signals = actix::Arbiter::system_registry().get::(); signals.send(Subscribe(addr.subscriber())); } diff --git a/examples/state/src/main.rs b/examples/state/src/main.rs index 68e989bf3..526657ed2 100644 --- a/examples/state/src/main.rs +++ b/examples/state/src/main.rs @@ -79,7 +79,7 @@ fn main() { // Subscribe to unix signals #[cfg(unix)] { - let signals = Arbiter::system_registry().get::(); + let signals = actix::Arbiter::system_registry().get::(); signals.send(Subscribe(addr.subscriber())); } diff --git a/examples/template_tera/src/main.rs b/examples/template_tera/src/main.rs index 1b5552234..ebd3d3ae5 100644 --- a/examples/template_tera/src/main.rs +++ b/examples/template_tera/src/main.rs @@ -46,8 +46,8 @@ fn main() { .start(); #[cfg(unix)] - { // Subscribe to unix signals - let signals = Arbiter::system_registry().get::(); + { // Subscribe to unix signals + let signals = actix::Arbiter::system_registry().get::(); signals.send(Subscribe(addr.subscriber())); } diff --git a/examples/tls/src/main.rs b/examples/tls/src/main.rs index a754e0738..6210a78f0 100644 --- a/examples/tls/src/main.rs +++ b/examples/tls/src/main.rs @@ -51,7 +51,7 @@ fn main() { // Subscribe to unix signals #[cfg(unix)] { - let signals = Arbiter::system_registry().get::(); + let signals = actix::Arbiter::system_registry().get::(); signals.send(Subscribe(addr.subscriber())); } diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs index baa1e1475..78fbcdf3c 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -218,7 +218,7 @@ fn main() { // Subscribe to unix signals #[cfg(unix)] { - let signals = Arbiter::system_registry().get::(); + let signals = actix::Arbiter::system_registry().get::(); signals.send(Subscribe(addr.subscriber())); } diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index fe55e0d83..035df38ea 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -76,7 +76,7 @@ fn main() { // Subscribe to unix signals #[cfg(unix)] { - let signals = Arbiter::system_registry().get::(); + let signals = actix::Arbiter::system_registry().get::(); signals.send(Subscribe(_addr.subscriber())); } diff --git a/guide/src/qs_3_5.md b/guide/src/qs_3_5.md index 6cce25030..98a5d8c39 100644 --- a/guide/src/qs_3_5.md +++ b/guide/src/qs_3_5.md @@ -226,7 +226,7 @@ fn main() { .start(); // Subscribe to unix signals - let signals = Arbiter::system_registry().get::(); + let signals = actix::Arbiter::system_registry().get::(); signals.send(Subscribe(addr.subscriber())); println!("Started http server: 127.0.0.1:8080"); From 1f7aee23dfddb40c4dc13994786a8e07ed479ed9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Jan 2018 22:43:44 -0800 Subject: [PATCH 264/279] shutdown io streams before exit --- examples/signals/Cargo.toml | 2 +- src/channel.rs | 139 +++++++++++++++++++++++++++++++++++- src/h1.rs | 4 ++ src/h2.rs | 6 ++ src/server.rs | 10 +-- src/worker.rs | 27 ++++--- 6 files changed, 169 insertions(+), 19 deletions(-) diff --git a/examples/signals/Cargo.toml b/examples/signals/Cargo.toml index 9352ef5e7..984cf8e3a 100644 --- a/examples/signals/Cargo.toml +++ b/examples/signals/Cargo.toml @@ -12,4 +12,4 @@ path = "src/main.rs" env_logger = "*" futures = "0.1" actix = "^0.3.5" -actix-web = { git = "https://github.com/actix/actix-web.git", features=["signal"] } +actix-web = { path = "../../", features=["signal"] } diff --git a/src/channel.rs b/src/channel.rs index d736fd202..963ef1065 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -1,9 +1,11 @@ +use std::{ptr, mem, time}; use std::rc::Rc; -use std::net::SocketAddr; +use std::net::{SocketAddr, Shutdown}; use bytes::Bytes; use futures::{Future, Poll, Async}; use tokio_io::{AsyncRead, AsyncWrite}; +use tokio_core::net::TcpStream; use {h1, h2}; use error::Error; @@ -58,25 +60,57 @@ pub struct HttpChannel where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static { proto: Option>, + node: Option>>, +} + +impl Drop for HttpChannel + where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static +{ + fn drop(&mut self) { + self.shutdown() + } } impl HttpChannel where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static { pub(crate) fn new(h: Rc>, - io: T, peer: Option, http2: bool) -> HttpChannel + io: T, peer: Option, http2: bool) -> HttpChannel { h.add_channel(); if http2 { HttpChannel { + node: None, proto: Some(HttpProtocol::H2( h2::Http2::new(h, io, peer, Bytes::new()))) } } else { HttpChannel { + node: None, proto: Some(HttpProtocol::H1( h1::Http1::new(h, io, peer))) } } } + + fn io(&mut self) -> Option<&mut T> { + match self.proto { + Some(HttpProtocol::H1(ref mut h1)) => { + Some(h1.io()) + } + _ => None, + } + } + + fn shutdown(&mut self) { + match self.proto { + Some(HttpProtocol::H1(ref mut h1)) => { + let _ = h1.io().shutdown(); + } + Some(HttpProtocol::H2(ref mut h2)) => { + h2.shutdown() + } + _ => unreachable!(), + } + } } /*impl Drop for HttpChannel @@ -94,11 +128,25 @@ impl Future for HttpChannel type Error = (); fn poll(&mut self) -> Poll { + if self.node.is_none() { + self.node = Some(Node::new(self)); + match self.proto { + Some(HttpProtocol::H1(ref mut h1)) => { + h1.settings().head().insert(self.node.as_ref().unwrap()); + } + Some(HttpProtocol::H2(ref mut h2)) => { + h2.settings().head().insert(self.node.as_ref().unwrap()); + } + _ => unreachable!(), + } + } + match self.proto { Some(HttpProtocol::H1(ref mut h1)) => { match h1.poll() { Ok(Async::Ready(h1::Http1Result::Done)) => { h1.settings().remove_channel(); + self.node.as_ref().unwrap().remove(); return Ok(Async::Ready(())) } Ok(Async::Ready(h1::Http1Result::Switch)) => (), @@ -106,6 +154,7 @@ impl Future for HttpChannel return Ok(Async::NotReady), Err(_) => { h1.settings().remove_channel(); + self.node.as_ref().unwrap().remove(); return Err(()) } } @@ -113,7 +162,10 @@ impl Future for HttpChannel Some(HttpProtocol::H2(ref mut h2)) => { let result = h2.poll(); match result { - Ok(Async::Ready(())) | Err(_) => h2.settings().remove_channel(), + Ok(Async::Ready(())) | Err(_) => { + h2.settings().remove_channel(); + self.node.as_ref().unwrap().remove(); + } _ => (), } return result @@ -134,3 +186,84 @@ impl Future for HttpChannel } } } + +pub(crate) struct Node +{ + next: Option<*mut Node<()>>, + prev: Option<*mut Node<()>>, + element: *mut T, +} + +impl Node +{ + fn new(el: &mut T) -> Self { + Node { + next: None, + prev: None, + element: el as *mut _, + } + } + + fn insert(&self, next: &Node) { + #[allow(mutable_transmutes)] + unsafe { + if let Some(ref next2) = self.next { + let n: &mut Node<()> = mem::transmute(next2.as_ref().unwrap()); + n.prev = Some(next as *const _ as *mut _); + } + let slf: &mut Node = mem::transmute(self); + slf.next = Some(next as *const _ as *mut _); + + let next: &mut Node = mem::transmute(next); + next.prev = Some(slf as *const _ as *mut _); + } + } + + fn remove(&self) { + #[allow(mutable_transmutes)] + unsafe { + if let Some(ref prev) = self.prev { + let p: &mut Node<()> = mem::transmute(prev.as_ref().unwrap()); + let slf: &mut Node = mem::transmute(self); + p.next = slf.next.take(); + } + } + } +} + + +impl Node<()> { + + pub(crate) fn head() -> Self { + Node { + next: None, + prev: None, + element: ptr::null_mut(), + } + } + + pub(crate) fn traverse(&self) where H: HttpHandler + 'static { + let mut next = self.next.as_ref(); + loop { + if let Some(n) = next { + unsafe { + let n: &Node<()> = mem::transmute(n.as_ref().unwrap()); + next = n.next.as_ref(); + + if !n.element.is_null() { + let ch: &mut HttpChannel = mem::transmute( + &mut *(n.element as *mut _)); + if let Some(io) = ch.io() { + let _ = TcpStream::set_linger(io, Some(time::Duration::new(0, 0))); + let _ = TcpStream::shutdown(io, Shutdown::Both); + continue; + } + ch.shutdown(); + } + } + } else { + return + } + } + } +} diff --git a/src/h1.rs b/src/h1.rs index e4d2930b0..e0358a159 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -97,6 +97,10 @@ impl Http1 (self.settings, self.stream.into_inner(), self.addr, self.read_buf.freeze()) } + pub(crate) fn io(&mut self) -> &mut T { + self.stream.get_mut() + } + fn poll_completed(&mut self, shutdown: bool) -> Result { // check stream state match self.stream.poll_completed(shutdown) { diff --git a/src/h2.rs b/src/h2.rs index 89998afc9..446219727 100644 --- a/src/h2.rs +++ b/src/h2.rs @@ -64,6 +64,12 @@ impl Http2 } } + pub(crate) fn shutdown(&mut self) { + self.state = State::Empty; + self.tasks.clear(); + self.keepalive_timer.take(); + } + pub fn settings(&self) -> &WorkerSettings { self.settings.as_ref() } diff --git a/src/server.rs b/src/server.rs index 7394585ac..1833e8ae2 100644 --- a/src/server.rs +++ b/src/server.rs @@ -93,7 +93,7 @@ impl ServerSettings { /// /// `H` - request handler pub struct HttpServer - where H: 'static + where H: HttpHandler + 'static { h: Option>>, io: PhantomData, @@ -110,11 +110,11 @@ pub struct HttpServer shutdown_timeout: u16, } -unsafe impl Sync for HttpServer where H: 'static {} -unsafe impl Send for HttpServer where H: 'static {} +unsafe impl Sync for HttpServer where H: HttpHandler + 'static {} +unsafe impl Send for HttpServer where H: HttpHandler + 'static {} -impl Actor for HttpServer { +impl Actor for HttpServer { type Context = Context; fn started(&mut self, ctx: &mut Self::Context) { @@ -122,7 +122,7 @@ impl Actor for HttpServer { } } -impl HttpServer { +impl HttpServer { fn update_time(&self, ctx: &mut Context) { helpers::update_date(); ctx.run_later(Duration::new(1, 0), |slf, ctx| slf.update_time(ctx)); diff --git a/src/worker.rs b/src/worker.rs index 29158924f..c6127d2a0 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -25,7 +25,7 @@ use actix::*; use actix::msgs::StopArbiter; use helpers; -use channel::{HttpChannel, HttpHandler}; +use channel::{HttpChannel, HttpHandler, Node}; #[derive(Message)] @@ -50,6 +50,7 @@ pub(crate) struct WorkerSettings { bytes: Rc, messages: Rc, channels: Cell, + node: Node<()>, } impl WorkerSettings { @@ -61,9 +62,13 @@ impl WorkerSettings { bytes: Rc::new(helpers::SharedBytesPool::new()), messages: Rc::new(helpers::SharedMessagePool::new()), channels: Cell::new(0), + node: Node::head(), } } + pub fn head(&self) -> &Node<()> { + &self.node + } pub fn handlers(&self) -> RefMut> { self.h.borrow_mut() } @@ -95,19 +100,19 @@ impl WorkerSettings { /// Http worker /// /// Worker accepts Socket objects via unbounded channel and start requests processing. -pub(crate) struct Worker { - h: Rc>, +pub(crate) struct Worker where H: HttpHandler + 'static { + settings: Rc>, hnd: Handle, handler: StreamHandlerType, } -impl Worker { +impl Worker { pub(crate) fn new(h: Vec, handler: StreamHandlerType, keep_alive: Option) -> Worker { Worker { - h: Rc::new(WorkerSettings::new(h, keep_alive)), + settings: Rc::new(WorkerSettings::new(h, keep_alive)), hnd: Arbiter::handle().clone(), handler: handler, } @@ -122,7 +127,7 @@ impl Worker { tx: oneshot::Sender, dur: time::Duration) { // sleep for 1 second and then check again ctx.run_later(time::Duration::new(1, 0), move |slf, ctx| { - let num = slf.h.channels.get(); + let num = slf.settings.channels.get(); if num == 0 { let _ = tx.send(true); Arbiter::arbiter().send(StopArbiter(0)); @@ -130,6 +135,7 @@ impl Worker { slf.shutdown_timeout(ctx, tx, d); } else { info!("Force shutdown http worker, {} connections", num); + slf.settings.head().traverse::(); let _ = tx.send(false); Arbiter::arbiter().send(StopArbiter(0)); } @@ -137,7 +143,7 @@ impl Worker { } } -impl Actor for Worker { +impl Actor for Worker where H: HttpHandler + 'static { type Context = Context; fn started(&mut self, ctx: &mut Self::Context) { @@ -154,12 +160,12 @@ impl Handler> for Worker fn handle(&mut self, msg: Conn, _: &mut Context) -> Response> { - if !self.h.keep_alive_enabled() && + if !self.settings.keep_alive_enabled() && msg.io.set_keepalive(Some(time::Duration::new(75, 0))).is_err() { error!("Can not set socket keep-alive option"); } - self.handler.handle(Rc::clone(&self.h), &self.hnd, msg); + self.handler.handle(Rc::clone(&self.settings), &self.hnd, msg); Self::empty() } } @@ -170,7 +176,7 @@ impl Handler for Worker { fn handle(&mut self, msg: StopWorker, ctx: &mut Context) -> Response { - let num = self.h.channels.get(); + let num = self.settings.channels.get(); if num == 0 { info!("Shutting down http worker, 0 connections"); Self::reply(true) @@ -181,6 +187,7 @@ impl Handler for Worker Self::async_reply(rx.map_err(|_| ()).actfuture()) } else { info!("Force shutdown http worker, {} connections", num); + self.settings.head().traverse::(); Self::reply(false) } } From 9559f6a1759c3605ef1243a510ff36ec15018586 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Jan 2018 23:41:55 -0800 Subject: [PATCH 265/279] introduce IoStream trait for low level stream operations --- src/channel.rs | 186 ++++++++++++++++++++++++++++++++++++++++--------- src/h1.rs | 47 +++++++++---- src/server.rs | 20 +++--- src/worker.rs | 4 +- 4 files changed, 201 insertions(+), 56 deletions(-) diff --git a/src/channel.rs b/src/channel.rs index 963ef1065..baae32fa0 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -1,8 +1,8 @@ -use std::{ptr, mem, time}; +use std::{ptr, mem, time, io}; use std::rc::Rc; use std::net::{SocketAddr, Shutdown}; -use bytes::Bytes; +use bytes::{Bytes, Buf, BufMut}; use futures::{Future, Poll, Async}; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_core::net::TcpStream; @@ -48,8 +48,7 @@ impl IntoHttpHandler for T { } } -enum HttpProtocol - where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static +enum HttpProtocol { H1(h1::Http1), H2(h2::Http2), @@ -57,22 +56,14 @@ enum HttpProtocol #[doc(hidden)] pub struct HttpChannel - where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static + where T: IoStream, H: HttpHandler + 'static { proto: Option>, node: Option>>, } -impl Drop for HttpChannel - where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static -{ - fn drop(&mut self) { - self.shutdown() - } -} - impl HttpChannel - where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static + where T: IoStream, H: HttpHandler + 'static { pub(crate) fn new(h: Rc>, io: T, peer: Option, http2: bool) -> HttpChannel @@ -91,19 +82,12 @@ impl HttpChannel } } - fn io(&mut self) -> Option<&mut T> { - match self.proto { - Some(HttpProtocol::H1(ref mut h1)) => { - Some(h1.io()) - } - _ => None, - } - } - fn shutdown(&mut self) { match self.proto { Some(HttpProtocol::H1(ref mut h1)) => { - let _ = h1.io().shutdown(); + let io = h1.io(); + let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0))); + let _ = IoStream::shutdown(io, Shutdown::Both); } Some(HttpProtocol::H2(ref mut h2)) => { h2.shutdown() @@ -122,7 +106,7 @@ impl HttpChannel }*/ impl Future for HttpChannel - where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static + where T: IoStream, H: HttpHandler + 'static { type Item = (); type Error = (); @@ -242,7 +226,7 @@ impl Node<()> { } } - pub(crate) fn traverse(&self) where H: HttpHandler + 'static { + pub(crate) fn traverse(&self) where T: IoStream, H: HttpHandler + 'static { let mut next = self.next.as_ref(); loop { if let Some(n) = next { @@ -251,13 +235,8 @@ impl Node<()> { next = n.next.as_ref(); if !n.element.is_null() { - let ch: &mut HttpChannel = mem::transmute( + let ch: &mut HttpChannel = mem::transmute( &mut *(n.element as *mut _)); - if let Some(io) = ch.io() { - let _ = TcpStream::set_linger(io, Some(time::Duration::new(0, 0))); - let _ = TcpStream::shutdown(io, Shutdown::Both); - continue; - } ch.shutdown(); } } @@ -267,3 +246,146 @@ impl Node<()> { } } } + + +pub trait IoStream: AsyncRead + AsyncWrite + 'static { + fn shutdown(&mut self, how: Shutdown) -> io::Result<()>; + + fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()>; + + fn set_linger(&mut self, dur: Option) -> io::Result<()>; +} + +impl IoStream for TcpStream { + #[inline] + fn shutdown(&mut self, how: Shutdown) -> io::Result<()> { + TcpStream::shutdown(self, how) + } + + #[inline] + fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { + TcpStream::set_nodelay(self, nodelay) + } + + #[inline] + fn set_linger(&mut self, dur: Option) -> io::Result<()> { + TcpStream::set_linger(self, dur) + } +} + + +pub(crate) struct WrapperStream where T: AsyncRead + AsyncWrite + 'static { + io: T, +} + +impl WrapperStream where T: AsyncRead + AsyncWrite + 'static +{ + pub fn new(io: T) -> Self { + WrapperStream{io: io} + } +} + +impl IoStream for WrapperStream + where T: AsyncRead + AsyncWrite + 'static +{ + #[inline] + fn shutdown(&mut self, _: Shutdown) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn set_nodelay(&mut self, _: bool) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn set_linger(&mut self, _: Option) -> io::Result<()> { + Ok(()) + } +} + +impl io::Read for WrapperStream + where T: AsyncRead + AsyncWrite + 'static +{ + #[inline] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.io.read(buf) + } +} + +impl io::Write for WrapperStream + where T: AsyncRead + AsyncWrite + 'static +{ + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + self.io.write(buf) + } + #[inline] + fn flush(&mut self) -> io::Result<()> { + self.io.flush() + } +} + +impl AsyncRead for WrapperStream + where T: AsyncRead + AsyncWrite + 'static +{ + fn read_buf(&mut self, buf: &mut B) -> Poll { + self.io.read_buf(buf) + } +} + +impl AsyncWrite for WrapperStream + where T: AsyncRead + AsyncWrite + 'static +{ + fn shutdown(&mut self) -> Poll<(), io::Error> { + self.io.shutdown() + } + fn write_buf(&mut self, buf: &mut B) -> Poll { + self.io.write_buf(buf) + } +} + + +#[cfg(feature="alpn")] +use tokio_openssl::SslStream; + +#[cfg(feature="alpn")] +impl IoStream for SslStream { + #[inline] + fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { + let _ = self.get_mut().shutdown(); + Ok(()) + } + + #[inline] + fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { + self.get_mut().get_mut().set_nodelay(nodelay) + } + + #[inline] + fn set_linger(&mut self, dur: Option) -> io::Result<()> { + self.get_mut().get_mut().set_linger(dur) + } +} + +#[cfg(feature="tls")] +use tokio_tls::TlsStream; + +#[cfg(feature="tls")] +impl IoStream for TlsStream { + #[inline] + fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { + let _ = self.get_mut().shutdown(); + Ok(()) + } + + #[inline] + fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { + self.get_mut().get_mut().set_nodelay(nodelay) + } + + #[inline] + fn set_linger(&mut self, dur: Option) -> io::Result<()> { + self.get_mut().get_mut().set_linger(dur) + } +} diff --git a/src/h1.rs b/src/h1.rs index e0358a159..e5f592dd2 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -10,12 +10,11 @@ use http::{Uri, Method, Version, HttpTryFrom, HeaderMap}; use http::header::{self, HeaderName, HeaderValue}; use bytes::{Bytes, BytesMut, BufMut}; use futures::{Future, Poll, Async}; -use tokio_io::{AsyncRead, AsyncWrite}; use tokio_core::reactor::Timeout; use pipeline::Pipeline; use encoding::PayloadType; -use channel::{HttpHandler, HttpHandlerTask}; +use channel::{HttpHandler, HttpHandlerTask, IoStream}; use h1writer::{Writer, H1Writer}; use worker::WorkerSettings; use httpcodes::HTTPNotFound; @@ -57,7 +56,7 @@ enum Item { Http2, } -pub(crate) struct Http1 { +pub(crate) struct Http1 { flags: Flags, settings: Rc>, addr: Option, @@ -74,8 +73,7 @@ struct Entry { } impl Http1 - where T: AsyncRead + AsyncWrite + 'static, - H: HttpHandler + 'static + where T: IoStream, H: HttpHandler + 'static { pub fn new(h: Rc>, stream: T, addr: Option) -> Self { let bytes = h.get_shared_bytes(); @@ -417,7 +415,7 @@ impl Reader { pub fn parse(&mut self, io: &mut T, buf: &mut BytesMut, settings: &WorkerSettings) -> Poll - where T: AsyncRead + where T: IoStream { // read payload if self.payload.is_some() { @@ -507,8 +505,8 @@ impl Reader { } } - fn read_from_io(&mut self, io: &mut T, buf: &mut BytesMut) - -> Poll + fn read_from_io(&mut self, io: &mut T, buf: &mut BytesMut) + -> Poll { unsafe { if buf.remaining_mut() < LW_BUFFER_SIZE { @@ -894,14 +892,17 @@ impl ChunkedState { #[cfg(test)] mod tests { - use std::{io, cmp}; - use bytes::{Bytes, BytesMut}; - use futures::{Async}; - use tokio_io::AsyncRead; + use std::{io, cmp, time}; + use std::net::Shutdown; + use bytes::{Bytes, BytesMut, Buf}; + use futures::Async; + use tokio_io::{AsyncRead, AsyncWrite}; use http::{Version, Method}; + use super::*; use application::HttpApplication; use worker::WorkerSettings; + use channel::IoStream; struct Buffer { buf: Bytes, @@ -940,6 +941,28 @@ mod tests { } } + impl IoStream for Buffer { + fn shutdown(&self, _: Shutdown) -> io::Result<()> { + Ok(()) + } + fn set_nodelay(&self, _: bool) -> io::Result<()> { + Ok(()) + } + fn set_linger(&self, _: Option) -> io::Result<()> { + Ok(()) + } + } + impl io::Write for Buffer { + fn write(&mut self, buf: &[u8]) -> io::Result {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) + } + } + macro_rules! not_ready { ($e:expr) => (match $e { Ok(Async::NotReady) => (), diff --git a/src/server.rs b/src/server.rs index 1833e8ae2..d602e2769 100644 --- a/src/server.rs +++ b/src/server.rs @@ -31,7 +31,7 @@ use tokio_openssl::SslStream; use actix::actors::signal; use helpers; -use channel::{HttpChannel, HttpHandler, IntoHttpHandler}; +use channel::{HttpChannel, HttpHandler, IntoHttpHandler, IoStream, WrapperStream}; use worker::{Conn, Worker, WorkerSettings, StreamHandlerType, StopWorker}; /// Various server settings @@ -131,7 +131,7 @@ impl HttpServer HttpServer where A: 'static, - T: AsyncRead + AsyncWrite + 'static, + T: IoStream, H: HttpHandler, U: IntoIterator + 'static, V: IntoHttpHandler, @@ -450,7 +450,7 @@ impl HttpServer, net::SocketAddr, H, } } -impl HttpServer +impl HttpServer, A, H, U> where A: 'static, T: AsyncRead + AsyncWrite + 'static, H: HttpHandler, @@ -488,7 +488,7 @@ impl HttpServer // start server HttpServer::create(move |ctx| { ctx.add_stream(stream.map( - move |(t, _)| Conn{io: t, peer: None, http2: false})); + move |(t, _)| Conn{io: WrapperStream::new(t), peer: None, http2: false})); self }) } @@ -499,7 +499,7 @@ impl HttpServer /// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and send `SystemExit(0)` /// message to `System` actor. impl Handler for HttpServer - where T: AsyncRead + AsyncWrite + 'static, + where T: IoStream, H: HttpHandler + 'static, U: 'static, A: 'static, @@ -530,13 +530,13 @@ impl Handler for HttpServer } impl StreamHandler, io::Error> for HttpServer - where T: AsyncRead + AsyncWrite + 'static, + where T: IoStream, H: HttpHandler + 'static, U: 'static, A: 'static {} impl Handler, io::Error> for HttpServer - where T: AsyncRead + AsyncWrite + 'static, + where T: IoStream, H: HttpHandler + 'static, U: 'static, A: 'static, @@ -573,7 +573,7 @@ pub struct StopServer { } impl Handler for HttpServer - where T: AsyncRead + AsyncWrite + 'static, + where T: IoStream, H: HttpHandler + 'static, U: 'static, A: 'static, @@ -589,7 +589,7 @@ impl Handler for HttpServer } impl Handler for HttpServer - where T: AsyncRead + AsyncWrite + 'static, + where T: IoStream, H: HttpHandler + 'static, U: 'static, A: 'static, @@ -605,7 +605,7 @@ impl Handler for HttpServer } impl Handler for HttpServer - where T: AsyncRead + AsyncWrite + 'static, + where T: IoStream, H: HttpHandler + 'static, U: 'static, A: 'static, diff --git a/src/worker.rs b/src/worker.rs index c6127d2a0..d0f73f63b 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -135,7 +135,7 @@ impl Worker { slf.shutdown_timeout(ctx, tx, d); } else { info!("Force shutdown http worker, {} connections", num); - slf.settings.head().traverse::(); + slf.settings.head().traverse::(); let _ = tx.send(false); Arbiter::arbiter().send(StopArbiter(0)); } @@ -187,7 +187,7 @@ impl Handler for Worker Self::async_reply(rx.map_err(|_| ()).actfuture()) } else { info!("Force shutdown http worker, {} connections", num); - self.settings.head().traverse::(); + self.settings.head().traverse::(); Self::reply(false) } } From fdf77268316ba19e73cf1825282865ac40202584 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Jan 2018 23:59:12 -0800 Subject: [PATCH 266/279] update changelog --- CHANGES.md | 4 ++++ src/channel.rs | 2 ++ src/h1.rs | 6 +++--- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 836b16b3c..22b422667 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,10 @@ * Content compression/decompression (br, gzip, deflate) +* Server multi-threading + +* Gracefull shutdown support + ## 0.2.1 (2017-11-03) diff --git a/src/channel.rs b/src/channel.rs index baae32fa0..ef8afbd16 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -248,6 +248,7 @@ impl Node<()> { } +/// Low-level io stream operations pub trait IoStream: AsyncRead + AsyncWrite + 'static { fn shutdown(&mut self, how: Shutdown) -> io::Result<()>; @@ -274,6 +275,7 @@ impl IoStream for TcpStream { } +/// Wrapper for `AsyncRead + AsyncWrite` types pub(crate) struct WrapperStream where T: AsyncRead + AsyncWrite + 'static { io: T, } diff --git a/src/h1.rs b/src/h1.rs index e5f592dd2..c0a1c68db 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -942,13 +942,13 @@ mod tests { } impl IoStream for Buffer { - fn shutdown(&self, _: Shutdown) -> io::Result<()> { + fn shutdown(&mut self, _: Shutdown) -> io::Result<()> { Ok(()) } - fn set_nodelay(&self, _: bool) -> io::Result<()> { + fn set_nodelay(&mut self, _: bool) -> io::Result<()> { Ok(()) } - fn set_linger(&self, _: Option) -> io::Result<()> { + fn set_linger(&mut self, _: Option) -> io::Result<()> { Ok(()) } } From afeffe4b19b0780cf23ea390e9262a6c3688d0a4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Jan 2018 09:32:15 -0800 Subject: [PATCH 267/279] encode returns result --- src/encoding.rs | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/encoding.rs b/src/encoding.rs index aec45929b..2475c006c 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -638,7 +638,7 @@ impl ContentEncoder { } } ContentEncoder::Identity(ref mut encoder) => { - encoder.encode(data); + encoder.encode(data)?; Ok(()) } } @@ -705,36 +705,37 @@ impl TransferEncoding { /// Encode message. Return `EOF` state of encoder #[inline] - pub fn encode(&mut self, msg: &[u8]) -> bool { + pub fn encode(&mut self, msg: &[u8]) -> io::Result { match self.kind { TransferEncodingKind::Eof => { self.buffer.get_mut().extend_from_slice(msg); - msg.is_empty() + Ok(msg.is_empty()) }, TransferEncodingKind::Chunked(ref mut eof) => { if *eof { - return true; + return Ok(true); } if msg.is_empty() { *eof = true; self.buffer.get_mut().extend_from_slice(b"0\r\n\r\n"); } else { - write!(self.buffer.get_mut(), "{:X}\r\n", msg.len()).unwrap(); + write!(self.buffer.get_mut(), "{:X}\r\n", msg.len()) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; self.buffer.get_mut().extend_from_slice(msg); self.buffer.get_mut().extend_from_slice(b"\r\n"); } - *eof + Ok(*eof) }, TransferEncodingKind::Length(ref mut remaining) => { if msg.is_empty() { - return *remaining == 0 + return Ok(*remaining == 0) } let max = cmp::min(*remaining, msg.len() as u64); self.buffer.get_mut().extend_from_slice(msg[..max as usize].as_ref()); *remaining -= max as u64; - *remaining == 0 + Ok(*remaining == 0) }, } } @@ -758,7 +759,7 @@ impl io::Write for TransferEncoding { #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { - self.encode(buf); + self.encode(buf)?; Ok(buf.len()) } @@ -834,3 +835,19 @@ impl AcceptEncoding { ContentEncoding::Identity } } + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_chunked_te() { + let bytes = SharedBytes::default(); + let mut enc = TransferEncoding::chunked(bytes.clone()); + assert!(!enc.encode(b"test").ok().unwrap()); + assert!(enc.encode(b"").ok().unwrap()); + assert_eq!(bytes.get_mut().take().freeze(), + Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n")); + } +} From 91230afc44ef84d7e68ac8670afdacf157ba2472 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Jan 2018 09:32:33 -0800 Subject: [PATCH 268/279] fix time calculations --- src/middleware/logger.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index ac1d6cc47..f7c008c1c 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -223,7 +223,7 @@ impl FormatText { FormatText::Pid => unsafe{libc::getpid().fmt(fmt)}, FormatText::Time => { let response_time = time::now() - entry_time; - let response_time = (response_time.num_seconds() * 1000) as f64 + + let response_time = response_time.num_seconds() as f64 + (response_time.num_nanoseconds().unwrap_or(0) as f64) / 1000000000.0; fmt.write_fmt(format_args!("{:.6}", response_time)) From 20d5c61c11f14c10dec1d3b89a78512743fdff12 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Jan 2018 09:32:47 -0800 Subject: [PATCH 269/279] set nodelay for streams --- src/worker.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/worker.rs b/src/worker.rs index d0f73f63b..188678a7f 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -209,6 +209,7 @@ impl StreamHandlerType { hnd: &Handle, msg: Conn) { match *self { StreamHandlerType::Normal => { + let _ = msg.io.set_nodelay(true); let io = TcpStream::from_stream(msg.io, hnd) .expect("failed to associate TCP stream"); @@ -217,6 +218,7 @@ impl StreamHandlerType { #[cfg(feature="tls")] StreamHandlerType::Tls(ref acceptor) => { let Conn { io, peer, http2 } = msg; + let _ = io.set_nodelay(true); let io = TcpStream::from_stream(io, hnd) .expect("failed to associate TCP stream"); @@ -235,6 +237,7 @@ impl StreamHandlerType { #[cfg(feature="alpn")] StreamHandlerType::Alpn(ref acceptor) => { let Conn { io, peer, .. } = msg; + let _ = io.set_nodelay(true); let io = TcpStream::from_stream(io, hnd) .expect("failed to associate TCP stream"); From dea354d6d889bd4ffa1852fea03fa230bd32a697 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Jan 2018 16:21:18 -0800 Subject: [PATCH 270/279] fix basic example in guide --- guide/src/qs_1.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/guide/src/qs_1.md b/guide/src/qs_1.md index 3f629bd1a..8d2ee83c2 100644 --- a/guide/src/qs_1.md +++ b/guide/src/qs_1.md @@ -23,12 +23,12 @@ Actix web framework requies rust version 1.20 and up. The fastest way to start experimenting with actix web is to clone the actix web repository and run the included examples in the examples/ directory. The following set of -commands runs the `basic` example: +commands runs the `basics` example: ```bash git clone https://github.com/actix/actix-web -cd actix-web -cargo run --example basic +cd actix-web/examples/basics +cargo run ``` Check [examples/](https://github.com/actix/actix-web/tree/master/examples) directory for more examples. From 5ff35f5b99e4bda221a3937dece60f75b864ff53 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Jan 2018 13:30:21 -0800 Subject: [PATCH 271/279] upgrade to actix 0.4 --- Cargo.toml | 7 +--- examples/basics/Cargo.toml | 4 +- examples/basics/src/main.rs | 8 +--- examples/diesel/Cargo.toml | 4 +- examples/hello-world/Cargo.toml | 2 +- examples/json/Cargo.toml | 4 +- examples/multipart/Cargo.toml | 4 +- examples/signals/Cargo.toml | 4 +- examples/state/Cargo.toml | 4 +- examples/template_tera/Cargo.toml | 4 +- examples/tls/Cargo.toml | 4 +- examples/websocket-chat/Cargo.toml | 4 +- examples/websocket/Cargo.toml | 4 +- guide/src/qs_3_5.md | 9 +--- guide/src/qs_9.md | 6 +-- src/context.rs | 9 ++-- src/server.rs | 66 +++++++++++++++--------------- src/worker.rs | 9 ++-- src/ws.rs | 9 ++-- 19 files changed, 75 insertions(+), 90 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5ba819c4f..d81057fcc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,9 +32,6 @@ tls = ["native-tls", "tokio-tls"] # openssl alpn = ["openssl", "openssl/v102", "openssl/v110", "tokio-openssl"] -# signals -signal = ["actix/signal"] - [dependencies] log = "0.3" failure = "0.1" @@ -77,9 +74,7 @@ tokio-tls = { version="0.1", optional = true } tokio-openssl = { version="0.1", optional = true } [dependencies.actix] -version = "^0.3.5" -default-features = false -features = [] +version = "0.4" [dependencies.openssl] version = "0.9" diff --git a/examples/basics/Cargo.toml b/examples/basics/Cargo.toml index b5eefd0f3..88b5f61e0 100644 --- a/examples/basics/Cargo.toml +++ b/examples/basics/Cargo.toml @@ -7,5 +7,5 @@ workspace = "../.." [dependencies] futures = "*" env_logger = "0.4" -actix = "^0.3.5" -actix-web = { git = "https://github.com/actix/actix-web", features=["signal"] } +actix = "0.4" +actix-web = { git = "https://github.com/actix/actix-web" } diff --git a/examples/basics/src/main.rs b/examples/basics/src/main.rs index 64c6809c9..c1d4902e2 100644 --- a/examples/basics/src/main.rs +++ b/examples/basics/src/main.rs @@ -11,7 +11,6 @@ use actix::*; use actix_web::*; use actix_web::middleware::RequestSession; use futures::future::{FutureResult, result}; -#[cfg(unix)] use actix::actors::signal::{ProcessSignals, Subscribe}; /// simple handler @@ -97,11 +96,8 @@ fn main() { .start(); // Subscribe to unix signals - #[cfg(unix)] - { - let signals = actix::Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); - } + let signals = actix::Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); println!("Starting http server: 127.0.0.1:8080"); let _ = sys.run(); diff --git a/examples/diesel/Cargo.toml b/examples/diesel/Cargo.toml index 31c8c4068..eb6628375 100644 --- a/examples/diesel/Cargo.toml +++ b/examples/diesel/Cargo.toml @@ -6,8 +6,8 @@ workspace = "../.." [dependencies] env_logger = "0.4" -actix = "^0.3.5" -actix-web = { git = "https://github.com/actix/actix-web", features=["signal"] } +actix = "0.4" +actix-web = { git = "https://github.com/actix/actix-web" } futures = "0.1" uuid = { version = "0.5", features = ["serde", "v4"] } diff --git a/examples/hello-world/Cargo.toml b/examples/hello-world/Cargo.toml index 63bb6f6a4..4cb1f70f7 100644 --- a/examples/hello-world/Cargo.toml +++ b/examples/hello-world/Cargo.toml @@ -6,5 +6,5 @@ workspace = "../.." [dependencies] env_logger = "0.4" -actix = "^0.3.5" +actix = "0.4" actix-web = { path = "../../" } diff --git a/examples/json/Cargo.toml b/examples/json/Cargo.toml index e4d7ed8fc..681b63450 100644 --- a/examples/json/Cargo.toml +++ b/examples/json/Cargo.toml @@ -14,5 +14,5 @@ serde_json = "1.0" serde_derive = "1.0" json = "*" -actix = "^0.3.5" -actix-web = { git = "https://github.com/actix/actix-web", features=["signal"] } +actix = "0.4" +actix-web = { git = "https://github.com/actix/actix-web" } diff --git a/examples/multipart/Cargo.toml b/examples/multipart/Cargo.toml index 7a92f465c..32edbea61 100644 --- a/examples/multipart/Cargo.toml +++ b/examples/multipart/Cargo.toml @@ -11,5 +11,5 @@ path = "src/main.rs" [dependencies] env_logger = "*" futures = "0.1" -actix = "^0.3.5" -actix-web = { git = "https://github.com/actix/actix-web", features=["signal"] } +actix = "0.4" +actix-web = { git = "https://github.com/actix/actix-web" } diff --git a/examples/signals/Cargo.toml b/examples/signals/Cargo.toml index 984cf8e3a..1e4006e35 100644 --- a/examples/signals/Cargo.toml +++ b/examples/signals/Cargo.toml @@ -11,5 +11,5 @@ path = "src/main.rs" [dependencies] env_logger = "*" futures = "0.1" -actix = "^0.3.5" -actix-web = { path = "../../", features=["signal"] } +actix = "0.4" +actix-web = { path = "../../" } diff --git a/examples/state/Cargo.toml b/examples/state/Cargo.toml index 7e4c7d3dd..149e8128e 100644 --- a/examples/state/Cargo.toml +++ b/examples/state/Cargo.toml @@ -7,5 +7,5 @@ workspace = "../.." [dependencies] futures = "*" env_logger = "0.4" -actix = "^0.3.5" -actix-web = { git = "https://github.com/actix/actix-web", features=["signal"] } +actix = "0.4" +actix-web = { git = "https://github.com/actix/actix-web" } \ No newline at end of file diff --git a/examples/template_tera/Cargo.toml b/examples/template_tera/Cargo.toml index 791934d09..3862fb803 100644 --- a/examples/template_tera/Cargo.toml +++ b/examples/template_tera/Cargo.toml @@ -6,6 +6,6 @@ workspace = "../.." [dependencies] env_logger = "0.4" -actix = "^0.3.5" -actix-web = { git = "https://github.com/actix/actix-web", features=["signal"] } +actix = "0.4" +actix-web = { git = "https://github.com/actix/actix-web" } tera = "*" diff --git a/examples/tls/Cargo.toml b/examples/tls/Cargo.toml index a659bc36b..e6d39742d 100644 --- a/examples/tls/Cargo.toml +++ b/examples/tls/Cargo.toml @@ -10,5 +10,5 @@ path = "src/main.rs" [dependencies] env_logger = "0.4" -actix = { version = "^0.3.5" } -actix-web = { git = "https://github.com/actix/actix-web", features=["signal", "alpn"] } +actix = "0.4" +actix-web = { git = "https://github.com/actix/actix-web", features=["alpn"] } diff --git a/examples/websocket-chat/Cargo.toml b/examples/websocket-chat/Cargo.toml index 517cf8163..f5534f9be 100644 --- a/examples/websocket-chat/Cargo.toml +++ b/examples/websocket-chat/Cargo.toml @@ -25,5 +25,5 @@ serde = "1.0" serde_json = "1.0" serde_derive = "1.0" -actix = { version = "^0.3.5" } -actix-web = { git = "https://github.com/actix/actix-web", features=["signal"] } +actix = "0.4" +actix-web = { git = "https://github.com/actix/actix-web" } diff --git a/examples/websocket/Cargo.toml b/examples/websocket/Cargo.toml index 3168601a2..5c6bd9889 100644 --- a/examples/websocket/Cargo.toml +++ b/examples/websocket/Cargo.toml @@ -10,5 +10,5 @@ path = "src/main.rs" [dependencies] env_logger = "*" futures = "0.1" -actix = "^0.3.5" -actix-web = { git = "https://github.com/actix/actix-web.git", features=["signal"] } +actix = "0.4" +actix-web = { git = "https://github.com/actix/actix-web.git" } diff --git a/guide/src/qs_3_5.md b/guide/src/qs_3_5.md index 98a5d8c39..b9f002c87 100644 --- a/guide/src/qs_3_5.md +++ b/guide/src/qs_3_5.md @@ -195,13 +195,8 @@ fn main() { } ``` -It is possible to use unix signals on compatible OSs. "signal" feature needs to be enabled -in *Cargo.toml* for *actix-web* dependency. - -```toml -[dependencies] -actix-web = { git = "https://github.com/actix/actix-web", features=["signal"] } -``` +It is possible to use signals. *CTRL-C* is available on all OSs, other signals are +available on unix systems. Then you can subscribe your server to unix signals. Http server handles three signals: diff --git a/guide/src/qs_9.md b/guide/src/qs_9.md index 7feb7d94b..0c291237b 100644 --- a/guide/src/qs_9.md +++ b/guide/src/qs_9.md @@ -23,15 +23,15 @@ impl Actor for Ws { /// Define Handler for ws::Message message # impl StreamHandler for Ws {} impl Handler for Ws { - fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext) -> Response - { + type Result=(); + + fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext) { match msg { ws::Message::Ping(msg) => ws::WsWriter::pong(ctx, &msg), ws::Message::Text(text) => ws::WsWriter::text(ctx, &text), ws::Message::Binary(bin) => ws::WsWriter::binary(ctx, bin), _ => (), } - Self::empty() } } diff --git a/src/context.rs b/src/context.rs index dff9344a2..d6261a3a0 100644 --- a/src/context.rs +++ b/src/context.rs @@ -6,9 +6,9 @@ use futures::sync::oneshot::Sender; use futures::unsync::oneshot; use actix::{Actor, ActorState, ActorContext, AsyncContext, - Handler, Subscriber, ResponseType}; + Handler, Subscriber, ResponseType, SpawnHandle}; use actix::fut::ActorFuture; -use actix::dev::{AsyncContextApi, ActorAddressCell, ActorItemsCell, ActorWaitCell, SpawnHandle, +use actix::dev::{AsyncContextApi, ActorAddressCell, ActorItemsCell, ActorWaitCell, Envelope, ToEnvelope, RemoteEnvelope}; use body::{Body, Binary}; @@ -290,13 +290,14 @@ impl ActorHttpContext for HttpContext where A: Actor, impl ToEnvelope for HttpContext where A: Actor>, { - fn pack(msg: M, tx: Option>>) -> Envelope + fn pack(msg: M, tx: Option>>, + channel_on_drop: bool) -> Envelope where A: Handler, M: ResponseType + Send + 'static, M::Item: Send, M::Error: Send { - RemoteEnvelope::new(msg, tx).into() + RemoteEnvelope::new(msg, tx, channel_on_drop).into() } } diff --git a/src/server.rs b/src/server.rs index d602e2769..45d158c51 100644 --- a/src/server.rs +++ b/src/server.rs @@ -5,12 +5,12 @@ use std::time::Duration; use std::marker::PhantomData; use std::collections::HashMap; -use actix::dev::*; -use actix::System; +use actix::prelude::*; use futures::{Future, Sink, Stream}; use futures::sync::mpsc; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_core::net::TcpStream; +use actix::actors::signal; use mio; use num_cpus; use net2::TcpBuilder; @@ -27,9 +27,6 @@ use openssl::pkcs12::ParsedPkcs12; #[cfg(feature="alpn")] use tokio_openssl::SslStream; -#[cfg(feature="signal")] -use actix::actors::signal; - use helpers; use channel::{HttpChannel, HttpHandler, IntoHttpHandler, IoStream, WrapperStream}; use worker::{Conn, Worker, WorkerSettings, StreamHandlerType, StopWorker}; @@ -202,7 +199,6 @@ impl HttpServer self } - #[cfg(feature="signal")] /// Send `SystemExit` message to actix system /// /// `SystemExit` message stops currently running system arbiter and all @@ -271,7 +267,7 @@ impl HttpServer let apps: Vec<_> = (*factory)() .into_iter() .map(|h| h.into_handler(s.clone())).collect(); - ctx.add_stream(rx); + ctx.add_message_stream(rx); Worker::new(apps, h, ka) }); workers.push(tx); @@ -494,8 +490,7 @@ impl HttpServer, A, H, U> } } -#[cfg(feature="signal")] -/// Unix Signals support +/// Signals support /// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and send `SystemExit(0)` /// message to `System` actor. impl Handler for HttpServer @@ -504,9 +499,9 @@ impl Handler for HttpServer U: 'static, A: 'static, { - fn handle(&mut self, msg: signal::Signal, ctx: &mut Context) - -> Response - { + type Result = (); + + fn handle(&mut self, msg: signal::Signal, ctx: &mut Context) { match msg.0 { signal::SignalType::Int => { info!("SIGINT received, exiting"); @@ -524,32 +519,33 @@ impl Handler for HttpServer Handler::::handle(self, StopServer{graceful: false}, ctx); } _ => (), - }; - Self::empty() + } } } -impl StreamHandler, io::Error> for HttpServer +impl StreamHandler>> for HttpServer where T: IoStream, H: HttpHandler + 'static, U: 'static, A: 'static {} -impl Handler, io::Error> for HttpServer +impl Handler>> for HttpServer where T: IoStream, H: HttpHandler + 'static, U: 'static, A: 'static, { - fn error(&mut self, err: io::Error, _: &mut Context) { - debug!("Error handling request: {}", err) - } + type Result = (); - fn handle(&mut self, msg: Conn, _: &mut Context) -> Response> - { - Arbiter::handle().spawn( - HttpChannel::new(Rc::clone(self.h.as_ref().unwrap()), msg.io, msg.peer, msg.http2)); - Self::empty() + fn handle(&mut self, msg: io::Result>, _: &mut Context) -> Self::Result { + match msg { + Ok(msg) => + Arbiter::handle().spawn( + HttpChannel::new( + Rc::clone(self.h.as_ref().unwrap()), msg.io, msg.peer, msg.http2)), + Err(err) => + debug!("Error handling request: {}", err), + } } } @@ -578,13 +574,14 @@ impl Handler for HttpServer U: 'static, A: 'static, { - fn handle(&mut self, _: PauseServer, _: &mut Context) -> Response + type Result = (); + + fn handle(&mut self, _: PauseServer, _: &mut Context) { for item in &self.accept { let _ = item.1.send(Command::Pause); let _ = item.0.set_readiness(mio::Ready::readable()); } - Self::empty() } } @@ -594,13 +591,13 @@ impl Handler for HttpServer U: 'static, A: 'static, { - fn handle(&mut self, _: ResumeServer, _: &mut Context) -> Response - { + type Result = (); + + fn handle(&mut self, _: ResumeServer, _: &mut Context) { for item in &self.accept { let _ = item.1.send(Command::Resume); let _ = item.0.set_readiness(mio::Ready::readable()); } - Self::empty() } } @@ -610,8 +607,9 @@ impl Handler for HttpServer U: 'static, A: 'static, { - fn handle(&mut self, msg: StopServer, ctx: &mut Context) -> Response - { + type Result = actix::Response; + + fn handle(&mut self, msg: StopServer, ctx: &mut Context) -> Self::Result { // stop accept threads for item in &self.accept { let _ = item.1.send(Command::Stop); @@ -636,10 +634,10 @@ impl Handler for HttpServer // we need to stop system if server was spawned if slf.exit { - Arbiter::system().send(msgs::SystemExit(0)) + Arbiter::system().send(actix::msgs::SystemExit(0)) } } - fut::ok(()) + actix::fut::ok(()) }).spawn(ctx); } @@ -649,7 +647,7 @@ impl Handler for HttpServer } else { // we need to stop system if server was spawned if self.exit { - Arbiter::system().send(msgs::SystemExit(0)) + Arbiter::system().send(actix::msgs::SystemExit(0)) } Self::empty() } diff --git a/src/worker.rs b/src/worker.rs index 188678a7f..d3bcf88c1 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -157,8 +157,9 @@ impl StreamHandler> for Worker impl Handler> for Worker where H: HttpHandler + 'static, { + type Result = (); + fn handle(&mut self, msg: Conn, _: &mut Context) - -> Response> { if !self.settings.keep_alive_enabled() && msg.io.set_keepalive(Some(time::Duration::new(75, 0))).is_err() @@ -166,7 +167,6 @@ impl Handler> for Worker error!("Can not set socket keep-alive option"); } self.handler.handle(Rc::clone(&self.settings), &self.hnd, msg); - Self::empty() } } @@ -174,8 +174,9 @@ impl Handler> for Worker impl Handler for Worker where H: HttpHandler + 'static, { - fn handle(&mut self, msg: StopWorker, ctx: &mut Context) -> Response - { + type Result = Response; + + fn handle(&mut self, msg: StopWorker, ctx: &mut Context) -> Self::Result { let num = self.settings.channels.get(); if num == 0 { info!("Shutting down http worker, 0 connections"); diff --git a/src/ws.rs b/src/ws.rs index 69e74aadb..cd83ea2f7 100644 --- a/src/ws.rs +++ b/src/ws.rs @@ -26,16 +26,15 @@ //! # impl StreamHandler for Ws {} //! # //! impl Handler for Ws { -//! fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext) -//! -> Response -//! { +//! type Result = (); +//! +//! fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext) { //! match msg { //! ws::Message::Ping(msg) => ws::WsWriter::pong(ctx, &msg), //! ws::Message::Text(text) => ws::WsWriter::text(ctx, &text), //! ws::Message::Binary(bin) => ws::WsWriter::binary(ctx, bin), //! _ => (), //! } -//! Self::empty() //! } //! } //! # @@ -94,7 +93,7 @@ pub fn start(mut req: HttpRequest, actor: A) -> Result Date: Fri, 5 Jan 2018 14:01:19 -0800 Subject: [PATCH 272/279] update example to use actix 0.4 --- examples/basics/src/main.rs | 1 - examples/diesel/src/db.rs | 8 +- examples/diesel/src/main.rs | 7 +- examples/diesel/test.db | Bin 20480 -> 20480 bytes examples/json/src/main.rs | 11 +- examples/multipart/src/main.rs | 8 +- examples/signals/src/main.rs | 14 +-- examples/state/src/main.rs | 15 +-- examples/template_tera/src/main.rs | 10 +- examples/tls/src/main.rs | 9 +- examples/websocket-chat/src/client.rs | 74 +++++------- examples/websocket-chat/src/main.rs | 23 ++-- examples/websocket-chat/src/server.rs | 47 +++----- examples/websocket-chat/src/session.rs | 151 ++++++++++--------------- examples/websocket/src/main.rs | 15 +-- 15 files changed, 150 insertions(+), 243 deletions(-) diff --git a/examples/basics/src/main.rs b/examples/basics/src/main.rs index c1d4902e2..b0674c8dd 100644 --- a/examples/basics/src/main.rs +++ b/examples/basics/src/main.rs @@ -7,7 +7,6 @@ extern crate env_logger; extern crate futures; use futures::Stream; -use actix::*; use actix_web::*; use actix_web::middleware::RequestSession; use futures::future::{FutureResult, result}; diff --git a/examples/diesel/src/db.rs b/examples/diesel/src/db.rs index 4e7bced91..04daa6ed0 100644 --- a/examples/diesel/src/db.rs +++ b/examples/diesel/src/db.rs @@ -27,9 +27,9 @@ impl Actor for DbExecutor { } impl Handler for DbExecutor { - fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) - -> Response - { + type Result = MessageResult; + + fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result { use self::schema::users::dsl::*; let uuid = format!("{}", uuid::Uuid::new_v4()); @@ -48,6 +48,6 @@ impl Handler for DbExecutor { .load::(&self.0) .expect("Error loading person"); - Self::reply(items.pop().unwrap()) + Ok(items.pop().unwrap()) } } diff --git a/examples/diesel/src/main.rs b/examples/diesel/src/main.rs index 09e8424d1..6c3170ede 100644 --- a/examples/diesel/src/main.rs +++ b/examples/diesel/src/main.rs @@ -18,8 +18,6 @@ extern crate env_logger; use actix::*; use actix_web::*; -use actix::prelude::*; -#[cfg(unix)] use actix::actors::signal::{ProcessSignals, Subscribe}; use diesel::prelude::*; @@ -72,9 +70,8 @@ fn main() { .start(); // Subscribe to unix signals - #[cfg(unix)] - { let signals = actix::Arbiter::system_registry().get::(); - signals.send(Subscribe(_addr.subscriber())); } + let signals = actix::Arbiter::system_registry().get::(); + signals.send(Subscribe(_addr.subscriber())); println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); diff --git a/examples/diesel/test.db b/examples/diesel/test.db index 3afa1579ec71d061fd73a4bfbb498d853f58d024..65e590a6e5f8f16622eee5da4c64c5a18f7b3423 100644 GIT binary patch delta 147 zcmZozz}T>Wae_1>>qHr6M%Il9OZ2&z`O6siFY_Wae_1>%S0JxMwX2UOY}LI_!l$qU*=!DSx{g)|K!E?i9nI{4E*c)ZvaIW K^G{yy9|!==_Ypw= diff --git a/examples/json/src/main.rs b/examples/json/src/main.rs index 3d1338135..462826393 100644 --- a/examples/json/src/main.rs +++ b/examples/json/src/main.rs @@ -7,9 +7,7 @@ extern crate serde_json; #[macro_use] extern crate serde_derive; #[macro_use] extern crate json; -use actix::*; use actix_web::*; -#[cfg(unix)] use actix::actors::signal::{ProcessSignals, Subscribe}; use bytes::BytesMut; @@ -23,7 +21,7 @@ struct MyObj { } /// This handler uses `HttpRequest::json()` for loading serde json object. -fn index(mut req: HttpRequest) -> Box> { +fn index(req: HttpRequest) -> Box> { req.json() .from_err() // convert all errors into `Error` .and_then(|val: MyObj| { @@ -98,11 +96,8 @@ fn main() { .start(); // Subscribe to unix signals - #[cfg(unix)] - { - let signals = actix::Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); - } + let signals = actix::Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); diff --git a/examples/multipart/src/main.rs b/examples/multipart/src/main.rs index 38b6d5325..965cc82c6 100644 --- a/examples/multipart/src/main.rs +++ b/examples/multipart/src/main.rs @@ -6,7 +6,6 @@ extern crate futures; use actix::*; use actix_web::*; -#[cfg(unix)] use actix::actors::signal::{ProcessSignals, Subscribe}; use futures::{Future, Stream}; @@ -57,11 +56,8 @@ fn main() { .start(); // Subscribe to unix signals - #[cfg(unix)] - { - let signals = actix::Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); - } + let signals = actix::Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); println!("Starting http server: 127.0.0.1:8080"); let _ = sys.run(); diff --git a/examples/signals/src/main.rs b/examples/signals/src/main.rs index d010732e7..92ceb5a8a 100644 --- a/examples/signals/src/main.rs +++ b/examples/signals/src/main.rs @@ -5,7 +5,6 @@ extern crate env_logger; use actix::*; use actix_web::*; -#[cfg(unix)] use actix::actors::signal::{ProcessSignals, Subscribe}; struct MyWebSocket; @@ -16,8 +15,10 @@ impl Actor for MyWebSocket { impl StreamHandler for MyWebSocket {} impl Handler for MyWebSocket { - fn handle(&mut self, _: ws::Message, _: &mut Self::Context) -> Response { - Self::empty() + type Result = (); + + fn handle(&mut self, _: ws::Message, _: &mut Self::Context) { + {} } } @@ -36,11 +37,8 @@ fn main() { .start(); // Subscribe to unix signals - #[cfg(unix)] - { - let signals = actix::Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); - } + let signals = actix::Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); diff --git a/examples/state/src/main.rs b/examples/state/src/main.rs index 526657ed2..e60e7d706 100644 --- a/examples/state/src/main.rs +++ b/examples/state/src/main.rs @@ -11,7 +11,6 @@ use std::cell::Cell; use actix::*; use actix_web::*; -#[cfg(unix)] use actix::actors::signal::{ProcessSignals, Subscribe}; /// Application state @@ -40,9 +39,9 @@ impl Actor for MyWebSocket { impl StreamHandler for MyWebSocket {} impl Handler for MyWebSocket { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) - -> Response - { + type Result = (); + + fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { self.counter += 1; println!("WS({}): {:?}", self.counter, msg); match msg { @@ -54,7 +53,6 @@ impl Handler for MyWebSocket { } _ => (), } - Self::empty() } } @@ -77,11 +75,8 @@ fn main() { .start(); // Subscribe to unix signals - #[cfg(unix)] - { - let signals = actix::Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); - } + let signals = actix::Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); diff --git a/examples/template_tera/src/main.rs b/examples/template_tera/src/main.rs index ebd3d3ae5..28706217f 100644 --- a/examples/template_tera/src/main.rs +++ b/examples/template_tera/src/main.rs @@ -4,9 +4,7 @@ extern crate env_logger; #[macro_use] extern crate tera; -use actix::*; use actix_web::*; -#[cfg(unix)] use actix::actors::signal::{ProcessSignals, Subscribe}; @@ -45,11 +43,9 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); - #[cfg(unix)] - { // Subscribe to unix signals - let signals = actix::Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); - } + // Subscribe to unix signals + let signals = actix::Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); diff --git a/examples/tls/src/main.rs b/examples/tls/src/main.rs index 6210a78f0..ad0d4b23b 100644 --- a/examples/tls/src/main.rs +++ b/examples/tls/src/main.rs @@ -6,9 +6,7 @@ extern crate env_logger; use std::fs::File; use std::io::Read; -use actix::*; use actix_web::*; -#[cfg(unix)] use actix::actors::signal::{ProcessSignals, Subscribe}; /// somple handle @@ -49,11 +47,8 @@ fn main() { .start_ssl(&pkcs12).unwrap(); // Subscribe to unix signals - #[cfg(unix)] - { - let signals = actix::Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); - } + let signals = actix::Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); println!("Started http server: 127.0.0.1:8443"); let _ = sys.run(); diff --git a/examples/websocket-chat/src/client.rs b/examples/websocket-chat/src/client.rs index c46a4875c..f1e52e051 100644 --- a/examples/websocket-chat/src/client.rs +++ b/examples/websocket-chat/src/client.rs @@ -1,4 +1,4 @@ -extern crate actix; +#[macro_use] extern crate actix; extern crate bytes; extern crate byteorder; extern crate futures; @@ -56,13 +56,9 @@ fn main() { struct ChatClient; +#[derive(Message)] struct ClientCommand(String); -impl ResponseType for ClientCommand { - type Item = (); - type Error = (); -} - impl Actor for ChatClient { type Context = FramedContext; @@ -70,6 +66,13 @@ impl Actor for ChatClient { // start heartbeats otherwise server will disconnect after 10 seconds self.hb(ctx) } + + fn stopping(&mut self, _: &mut FramedContext) { + println!("Disconnected"); + + // Stop application on disconnect + Arbiter::system().send(actix::msgs::SystemExit(0)); + } } impl ChatClient { @@ -83,14 +86,13 @@ impl ChatClient { } /// Handle stdin commands -impl Handler for ChatClient -{ - fn handle(&mut self, msg: ClientCommand, ctx: &mut FramedContext) - -> Response - { +impl Handler for ChatClient { + type Result = (); + + fn handle(&mut self, msg: ClientCommand, ctx: &mut FramedContext) { let m = msg.0.trim(); if m.is_empty() { - return Self::empty() + return } // we check for /sss type of messages @@ -112,8 +114,6 @@ impl Handler for ChatClient } else { let _ = ctx.send(codec::ChatRequest::Message(m.to_owned())); } - - Self::empty() } } @@ -122,40 +122,26 @@ impl Handler for ChatClient impl FramedActor for ChatClient { type Io = TcpStream; type Codec = codec::ClientChatCodec; -} -impl StreamHandler for ChatClient { - - fn finished(&mut self, _: &mut FramedContext) { - println!("Disconnected"); - - // Stop application on disconnect - Arbiter::system().send(msgs::SystemExit(0)); - } -} - -impl Handler for ChatClient { - - fn handle(&mut self, msg: codec::ChatResponse, _: &mut FramedContext) - -> Response - { + fn handle(&mut self, msg: io::Result, ctx: &mut FramedContext) { match msg { - codec::ChatResponse::Message(ref msg) => { - println!("message: {}", msg); - } - codec::ChatResponse::Joined(ref msg) => { - println!("!!! joined: {}", msg); - } - codec::ChatResponse::Rooms(rooms) => { - println!("\n!!! Available rooms:"); - for room in rooms { - println!("{}", room); + Err(_) => ctx.stop(), + Ok(msg) => match msg { + codec::ChatResponse::Message(ref msg) => { + println!("message: {}", msg); } - println!(""); + codec::ChatResponse::Joined(ref msg) => { + println!("!!! joined: {}", msg); + } + codec::ChatResponse::Rooms(rooms) => { + println!("\n!!! Available rooms:"); + for room in rooms { + println!("{}", room); + } + println!(""); + } + _ => (), } - _ => (), } - - Self::empty() } } diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs index 78fbcdf3c..39e7978f5 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -10,6 +10,7 @@ extern crate serde; extern crate serde_json; #[macro_use] extern crate serde_derive; +#[macro_use] extern crate actix; extern crate actix_web; @@ -17,7 +18,6 @@ use std::time::Instant; use actix::*; use actix_web::*; -#[cfg(unix)] use actix::actors::signal::{ProcessSignals, Subscribe}; mod codec; @@ -58,19 +58,18 @@ impl Actor for WsChatSession { /// Handle messages from chat server, we simply send it to peer websocket impl Handler for WsChatSession { - fn handle(&mut self, msg: session::Message, ctx: &mut Self::Context) - -> Response - { + type Result = (); + + fn handle(&mut self, msg: session::Message, ctx: &mut Self::Context) { ws::WsWriter::text(ctx, &msg.0); - Self::empty() } } /// WebSocket message handler impl Handler for WsChatSession { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) - -> Response - { + type Result = (); + + fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { println!("WEBSOCKET MESSAGE: {:?}", msg); match msg { ws::Message::Ping(msg) => @@ -142,7 +141,6 @@ impl Handler for WsChatSession { } _ => (), } - Self::empty() } } @@ -216,11 +214,8 @@ fn main() { .start(); // Subscribe to unix signals - #[cfg(unix)] - { - let signals = actix::Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); - } + let signals = actix::Arbiter::system_registry().get::(); + signals.send(Subscribe(addr.subscriber())); println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); diff --git a/examples/websocket-chat/src/server.rs b/examples/websocket-chat/src/server.rs index c15644a6a..4eae86d00 100644 --- a/examples/websocket-chat/src/server.rs +++ b/examples/websocket-chat/src/server.rs @@ -13,7 +13,7 @@ use session; /// New chat session is created pub struct Connect { - pub addr: Box + Send>, + pub addr: Box + Send>, } /// Response type for Connect message @@ -25,16 +25,13 @@ impl ResponseType for Connect { } /// Session is disconnected +#[derive(Message)] pub struct Disconnect { pub id: usize, } -impl ResponseType for Disconnect { - type Item = (); - type Error = (); -} - /// Send message to specific room +#[derive(Message)] pub struct Message { /// Id of the client session pub id: usize, @@ -44,11 +41,6 @@ pub struct Message { pub room: String, } -impl ResponseType for Message { - type Item = (); - type Error = (); -} - /// List of available rooms pub struct ListRooms; @@ -58,6 +50,7 @@ impl ResponseType for ListRooms { } /// Join room, if room does not exists create new one. +#[derive(Message)] pub struct Join { /// Client id pub id: usize, @@ -65,15 +58,10 @@ pub struct Join { pub name: String, } -impl ResponseType for Join { - type Item = (); - type Error = (); -} - /// `ChatServer` manages chat rooms and responsible for coordinating chat session. /// implementation is super primitive pub struct ChatServer { - sessions: HashMap + Send>>, + sessions: HashMap + Send>>, rooms: HashMap>, rng: RefCell, } @@ -118,8 +106,9 @@ impl Actor for ChatServer { /// /// Register new session and assign unique id to this session impl Handler for ChatServer { + type Result = MessageResult; - fn handle(&mut self, msg: Connect, _: &mut Context) -> Response { + fn handle(&mut self, msg: Connect, _: &mut Context) -> Self::Result { println!("Someone joined"); // notify all users in same room @@ -133,14 +122,15 @@ impl Handler for ChatServer { self.rooms.get_mut(&"Main".to_owned()).unwrap().insert(id); // send id back - Self::reply(id) + Ok(id) } } /// Handler for Disconnect message. impl Handler for ChatServer { + type Result = (); - fn handle(&mut self, msg: Disconnect, _: &mut Context) -> Response { + fn handle(&mut self, msg: Disconnect, _: &mut Context) { println!("Someone disconnected"); let mut rooms: Vec = Vec::new(); @@ -158,40 +148,39 @@ impl Handler for ChatServer { for room in rooms { self.send_message(&room, "Someone disconnected", 0); } - - Self::empty() } } /// Handler for Message message. impl Handler for ChatServer { + type Result = (); - fn handle(&mut self, msg: Message, _: &mut Context) -> Response { + fn handle(&mut self, msg: Message, _: &mut Context) { self.send_message(&msg.room, msg.msg.as_str(), msg.id); - - Self::empty() } } /// Handler for `ListRooms` message. impl Handler for ChatServer { + type Result = MessageResult; - fn handle(&mut self, _: ListRooms, _: &mut Context) -> Response { + fn handle(&mut self, _: ListRooms, _: &mut Context) -> Self::Result { let mut rooms = Vec::new(); for key in self.rooms.keys() { rooms.push(key.to_owned()) } - Self::reply(rooms) + Ok(rooms) } } /// Join room, send disconnect message to old room /// send join message to new room impl Handler for ChatServer { + type Result = (); - fn handle(&mut self, msg: Join, _: &mut Context) -> Response { + fn handle(&mut self, msg: Join, _: &mut Context) { let Join {id, name} = msg; let mut rooms = Vec::new(); @@ -211,7 +200,5 @@ impl Handler for ChatServer { } self.send_message(&name, "Someone connected", id); self.rooms.get_mut(&name).unwrap().insert(id); - - Self::empty() } } diff --git a/examples/websocket-chat/src/session.rs b/examples/websocket-chat/src/session.rs index 961955a59..f7000f416 100644 --- a/examples/websocket-chat/src/session.rs +++ b/examples/websocket-chat/src/session.rs @@ -6,20 +6,16 @@ use std::time::{Instant, Duration}; use futures::Stream; use tokio_core::net::{TcpStream, TcpListener}; -use actix::*; +use actix::prelude::*; use server::{self, ChatServer}; use codec::{ChatRequest, ChatResponse, ChatCodec}; /// Chat server sends this messages to session +#[derive(Message)] pub struct Message(pub String); -impl ResponseType for Message { - type Item = (); - type Error = (); -} - /// `ChatSession` actor is responsible for tcp peer communitions. pub struct ChatSession { /// unique session id @@ -36,104 +32,87 @@ impl Actor for ChatSession { /// For tcp communication we are going to use `FramedContext`. /// It is convinient wrapper around `Framed` object from `tokio_io` type Context = FramedContext; -} -/// To use `FramedContext` we have to define Io type and Codec -impl FramedActor for ChatSession { - type Io = TcpStream; - type Codec= ChatCodec; -} - -/// Also `FramedContext` requires Actor which is able to handle stream -/// of `::Item` items. -impl StreamHandler for ChatSession { - - fn started(&mut self, ctx: &mut FramedContext) { + fn started(&mut self, ctx: &mut Self::Context) { // we'll start heartbeat process on session start. self.hb(ctx); // register self in chat server. `AsyncContext::wait` register // future within context, but context waits until this future resolves // before processing any other events. - self.addr.call(self, server::Connect{addr: ctx.sync_subscriber()}).then(|res, act, ctx| { - match res { - Ok(Ok(res)) => act.id = res, - // something is wrong with chat server - _ => ctx.stop(), - } - fut::ok(()) - }).wait(ctx); + let addr: SyncAddress<_> = ctx.address(); + self.addr.call(self, server::Connect{addr: addr.subscriber()}) + .then(|res, act, ctx| { + match res { + Ok(Ok(res)) => act.id = res, + // something is wrong with chat server + _ => ctx.stop(), + } + actix::fut::ok(()) + }).wait(ctx); } - fn finished(&mut self, ctx: &mut FramedContext) { + fn stopping(&mut self, ctx: &mut Self::Context) { // notify chat server self.addr.send(server::Disconnect{id: self.id}); - ctx.stop() } } -impl Handler for ChatSession { - - /// We'll stop chat session actor on any error, high likely it is just - /// termination of the tcp stream. - fn error(&mut self, _: io::Error, ctx: &mut FramedContext) { - ctx.stop() - } +/// To use `FramedContext` we have to define Io type and Codec +impl FramedActor for ChatSession { + type Io = TcpStream; + type Codec= ChatCodec; /// This is main event loop for client requests - fn handle(&mut self, msg: ChatRequest, ctx: &mut FramedContext) - -> Response - { + fn handle(&mut self, msg: io::Result, ctx: &mut FramedContext) { match msg { - ChatRequest::List => { - // Send ListRooms message to chat server and wait for response - println!("List rooms"); - self.addr.call(self, server::ListRooms).then(|res, _, ctx| { - match res { - Ok(Ok(rooms)) => { - let _ = ctx.send(ChatResponse::Rooms(rooms)); - }, + Err(_) => ctx.stop(), + Ok(msg) => match msg { + ChatRequest::List => { + // Send ListRooms message to chat server and wait for response + println!("List rooms"); + self.addr.call(self, server::ListRooms).then(|res, _, ctx| { + match res { + Ok(Ok(rooms)) => { + let _ = ctx.send(ChatResponse::Rooms(rooms)); + }, _ => println!("Something is wrong"), - } - fut::ok(()) - }).wait(ctx) - // .wait(ctx) pauses all events in context, - // so actor wont receive any new messages until it get list of rooms back - }, - ChatRequest::Join(name) => { - println!("Join to room: {}", name); - self.room = name.clone(); - self.addr.send(server::Join{id: self.id, name: name.clone()}); - let _ = ctx.send(ChatResponse::Joined(name)); - }, - ChatRequest::Message(message) => { - // send message to chat server - println!("Peer message: {}", message); - self.addr.send( - server::Message{id: self.id, - msg: message, room: - self.room.clone()}) + } + actix::fut::ok(()) + }).wait(ctx) + // .wait(ctx) pauses all events in context, + // so actor wont receive any new messages until it get list of rooms back + }, + ChatRequest::Join(name) => { + println!("Join to room: {}", name); + self.room = name.clone(); + self.addr.send(server::Join{id: self.id, name: name.clone()}); + let _ = ctx.send(ChatResponse::Joined(name)); + }, + ChatRequest::Message(message) => { + // send message to chat server + println!("Peer message: {}", message); + self.addr.send( + server::Message{id: self.id, + msg: message, room: + self.room.clone()}) + } + // we update heartbeat time on ping from peer + ChatRequest::Ping => + self.hb = Instant::now(), } - // we update heartbeat time on ping from peer - ChatRequest::Ping => - self.hb = Instant::now(), } - - Self::empty() } } /// Handler for Message, chat server sends this message, we just send string to peer impl Handler for ChatSession { + type Result = (); - fn handle(&mut self, msg: Message, ctx: &mut FramedContext) - -> Response - { + fn handle(&mut self, msg: Message, ctx: &mut FramedContext) { // send message to peer let _ = ctx.send(ChatResponse::Message(msg.0)); - - Self::empty() } } @@ -188,7 +167,9 @@ impl TcpServer { // So to be able to handle this events `Server` actor has to implement // stream handler `StreamHandler<(TcpStream, net::SocketAddr), io::Error>` let _: () = TcpServer::create(|ctx| { - ctx.add_stream(listener.incoming().map(|(t, a)| TcpConnect(t, a))); + ctx.add_message_stream(listener.incoming() + .map_err(|_| ()) + .map(|(t, a)| TcpConnect(t, a))); TcpServer{chat: chat} }); } @@ -200,27 +181,19 @@ impl Actor for TcpServer { type Context = Context; } +#[derive(Message)] struct TcpConnect(TcpStream, net::SocketAddr); -impl ResponseType for TcpConnect { - type Item = (); - type Error = (); -} - /// Handle stream of TcpStream's -impl StreamHandler for TcpServer {} +impl StreamHandler for TcpServer {} -impl Handler for TcpServer { +impl Handler for TcpServer { + type Result = (); - fn handle(&mut self, msg: TcpConnect, _: &mut Context) -> Response - { + fn handle(&mut self, msg: TcpConnect, _: &mut Context) { // For each incoming connection we create `ChatSession` actor // with out chat server address. let server = self.chat.clone(); let _: () = ChatSession::new(server).framed(msg.0, ChatCodec); - - // this is response for message, which is defined by `ResponseType` trait - // in this case we just return unit. - Self::empty() } } diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index 035df38ea..b7433203f 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -10,7 +10,6 @@ extern crate env_logger; use actix::*; use actix_web::*; -#[cfg(unix)] use actix::actors::signal::{ProcessSignals, Subscribe}; /// do websocket handshake and start `MyWebSocket` actor @@ -38,9 +37,9 @@ impl StreamHandler for MyWebSocket { } impl Handler for MyWebSocket { - fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext) - -> Response - { + type Result = (); + + fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext) { // process websocket messages println!("WS: {:?}", msg); match msg { @@ -52,7 +51,6 @@ impl Handler for MyWebSocket { } _ => (), } - Self::empty() } } @@ -74,11 +72,8 @@ fn main() { .start(); // Subscribe to unix signals - #[cfg(unix)] - { - let signals = actix::Arbiter::system_registry().get::(); - signals.send(Subscribe(_addr.subscriber())); - } + let signals = actix::Arbiter::system_registry().get::(); + signals.send(Subscribe(_addr.subscriber())); println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); From 524493e0b0642d348e18bd1b4265a404e8931ee5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Jan 2018 14:29:40 -0800 Subject: [PATCH 273/279] pin nightly --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ce14e6369..4642eb065 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ rust: - 1.20.0 - stable - beta - - nightly + - nightly-2018-01-03 sudo: required dist: trusty @@ -58,7 +58,7 @@ script: # Upload docs after_success: - | - if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; then + if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "nightly-2018-01-03" ]]; then cargo doc --features alpn --no-deps && echo "" > target/doc/index.html && cargo install mdbook && From 473ec38439097b3956945361d5446766c7d0685c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Jan 2018 14:50:33 -0800 Subject: [PATCH 274/279] use dev cookies package as temp solution for ring problem --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d81057fcc..585a32116 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,10 @@ percent-encoding = "1.0" smallvec = "0.6" bitflags = "1.0" num_cpus = "1.0" -cookie = { version="0.10", features=["percent-encode", "secure"] } + +# temp solution +# cookie = { version="0.10", features=["percent-encode", "secure"] } +cookie = { git="https://github.com/alexcrichton/cookie-rs.git", features=["percent-encode", "secure"] } # io mio = "0.6" @@ -110,6 +113,3 @@ members = [ "examples/websocket", "examples/websocket-chat", ] - -[patch.crates-io] -ring = { git = "https://github.com/SergioBenitez/ring", branch = "v0.12" } From 3ed9e872ada2eb96a33bf5ef7d43053108209a86 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Jan 2018 16:32:36 -0800 Subject: [PATCH 275/279] subscriber to os signals automatically --- Cargo.toml | 1 - README.md | 6 +-- examples/basics/src/main.rs | 6 +-- examples/diesel/src/main.rs | 5 -- examples/json/src/main.rs | 5 -- examples/multipart/src/main.rs | 5 -- examples/signals/Cargo.toml | 15 ------ examples/signals/README.md | 17 ------- examples/signals/src/main.rs | 45 ----------------- examples/state/src/main.rs | 5 -- examples/template_tera/src/main.rs | 5 -- examples/tls/src/main.rs | 6 +-- examples/websocket-chat/src/main.rs | 5 -- examples/websocket/src/main.rs | 5 -- guide/src/qs_2.md | 15 ++---- guide/src/qs_3_5.md | 78 ++++++++--------------------- src/server.rs | 71 +++++++++++++++++++------- 17 files changed, 81 insertions(+), 214 deletions(-) delete mode 100644 examples/signals/Cargo.toml delete mode 100644 examples/signals/README.md delete mode 100644 examples/signals/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 585a32116..f3c83ccc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,7 +106,6 @@ members = [ "examples/json", "examples/hello-world", "examples/multipart", - "examples/signals", "examples/state", "examples/template_tera", "examples/tls", diff --git a/README.md b/README.md index f861e9601..124fb9e83 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ Actix web is a small, fast, down-to-earth, open source rust web framework. ```rust,ignore -extern crate actix; extern crate actix_web; use actix_web::*; @@ -12,14 +11,11 @@ fn index(req: HttpRequest) -> String { } fn main() { - let sys = actix::System::new("readme"); HttpServer::new( || Application::new() .resource("/{name}", |r| r.f(index))) .bind("127.0.0.1:8080").unwrap() - .start(); - - sys.run(); + .run(); } ``` diff --git a/examples/basics/src/main.rs b/examples/basics/src/main.rs index b0674c8dd..2c7b714f4 100644 --- a/examples/basics/src/main.rs +++ b/examples/basics/src/main.rs @@ -10,7 +10,7 @@ use futures::Stream; use actix_web::*; use actix_web::middleware::RequestSession; use futures::future::{FutureResult, result}; -use actix::actors::signal::{ProcessSignals, Subscribe}; + /// simple handler fn index(mut req: HttpRequest) -> Result { @@ -94,10 +94,6 @@ fn main() { .bind("0.0.0.0:8080").unwrap() .start(); - // Subscribe to unix signals - let signals = actix::Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); - println!("Starting http server: 127.0.0.1:8080"); let _ = sys.run(); } diff --git a/examples/diesel/src/main.rs b/examples/diesel/src/main.rs index 6c3170ede..4c4bc4cda 100644 --- a/examples/diesel/src/main.rs +++ b/examples/diesel/src/main.rs @@ -18,7 +18,6 @@ extern crate env_logger; use actix::*; use actix_web::*; -use actix::actors::signal::{ProcessSignals, Subscribe}; use diesel::prelude::*; use futures::future::Future; @@ -69,10 +68,6 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); - // Subscribe to unix signals - let signals = actix::Arbiter::system_registry().get::(); - signals.send(Subscribe(_addr.subscriber())); - println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); } diff --git a/examples/json/src/main.rs b/examples/json/src/main.rs index 462826393..719d74853 100644 --- a/examples/json/src/main.rs +++ b/examples/json/src/main.rs @@ -8,7 +8,6 @@ extern crate serde_json; #[macro_use] extern crate json; use actix_web::*; -use actix::actors::signal::{ProcessSignals, Subscribe}; use bytes::BytesMut; use futures::{Future, Stream}; @@ -95,10 +94,6 @@ fn main() { .shutdown_timeout(1) .start(); - // Subscribe to unix signals - let signals = actix::Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); - println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); } diff --git a/examples/multipart/src/main.rs b/examples/multipart/src/main.rs index 965cc82c6..7da6145a9 100644 --- a/examples/multipart/src/main.rs +++ b/examples/multipart/src/main.rs @@ -6,7 +6,6 @@ extern crate futures; use actix::*; use actix_web::*; -use actix::actors::signal::{ProcessSignals, Subscribe}; use futures::{Future, Stream}; use futures::future::{result, Either}; @@ -55,10 +54,6 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); - // Subscribe to unix signals - let signals = actix::Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); - println!("Starting http server: 127.0.0.1:8080"); let _ = sys.run(); } diff --git a/examples/signals/Cargo.toml b/examples/signals/Cargo.toml deleted file mode 100644 index 1e4006e35..000000000 --- a/examples/signals/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "signals" -version = "0.1.0" -authors = ["Nikolay Kim "] -workspace = "../.." - -[[bin]] -name = "server" -path = "src/main.rs" - -[dependencies] -env_logger = "*" -futures = "0.1" -actix = "0.4" -actix-web = { path = "../../" } diff --git a/examples/signals/README.md b/examples/signals/README.md deleted file mode 100644 index 368182e7f..000000000 --- a/examples/signals/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Signals - -This example shows how to handle Unix signals and properly stop http server. This example does not work with Windows. - -## Usage - -```bash -cd actix-web/examples/signal -cargo run (or ``cargo watch -x run``) -# Started http server: 127.0.0.1:8080 -# CTRL+C -# INFO:actix_web::server: SIGINT received, exiting -# INFO:actix_web::worker: Shutting down http worker, 0 connections -# INFO:actix_web::worker: Shutting down http worker, 0 connections -# INFO:actix_web::worker: Shutting down http worker, 0 connections -# INFO:actix_web::worker: Shutting down http worker, 0 connections -``` \ No newline at end of file diff --git a/examples/signals/src/main.rs b/examples/signals/src/main.rs deleted file mode 100644 index 92ceb5a8a..000000000 --- a/examples/signals/src/main.rs +++ /dev/null @@ -1,45 +0,0 @@ -extern crate actix; -extern crate actix_web; -extern crate futures; -extern crate env_logger; - -use actix::*; -use actix_web::*; -use actix::actors::signal::{ProcessSignals, Subscribe}; - -struct MyWebSocket; - -impl Actor for MyWebSocket { - type Context = HttpContext; -} - -impl StreamHandler for MyWebSocket {} -impl Handler for MyWebSocket { - type Result = (); - - fn handle(&mut self, _: ws::Message, _: &mut Self::Context) { - {} - } -} - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - let _ = env_logger::init(); - let sys = actix::System::new("signals-example"); - - let addr = HttpServer::new(|| { - Application::new() - // enable logger - .middleware(middleware::Logger::default()) - .resource("/ws/", |r| r.f(|req| ws::start(req, MyWebSocket))) - .resource("/", |r| r.h(httpcodes::HTTPOk))}) - .bind("127.0.0.1:8080").unwrap() - .start(); - - // Subscribe to unix signals - let signals = actix::Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); - - println!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); -} diff --git a/examples/state/src/main.rs b/examples/state/src/main.rs index e60e7d706..6c247329c 100644 --- a/examples/state/src/main.rs +++ b/examples/state/src/main.rs @@ -11,7 +11,6 @@ use std::cell::Cell; use actix::*; use actix_web::*; -use actix::actors::signal::{ProcessSignals, Subscribe}; /// Application state struct AppState { @@ -74,10 +73,6 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); - // Subscribe to unix signals - let signals = actix::Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); - println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); } diff --git a/examples/template_tera/src/main.rs b/examples/template_tera/src/main.rs index 28706217f..17a14c70c 100644 --- a/examples/template_tera/src/main.rs +++ b/examples/template_tera/src/main.rs @@ -5,7 +5,6 @@ extern crate env_logger; extern crate tera; use actix_web::*; -use actix::actors::signal::{ProcessSignals, Subscribe}; struct State { @@ -43,10 +42,6 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); - // Subscribe to unix signals - let signals = actix::Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); - println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); } diff --git a/examples/tls/src/main.rs b/examples/tls/src/main.rs index ad0d4b23b..15cfcc666 100644 --- a/examples/tls/src/main.rs +++ b/examples/tls/src/main.rs @@ -7,7 +7,7 @@ use std::fs::File; use std::io::Read; use actix_web::*; -use actix::actors::signal::{ProcessSignals, Subscribe}; + /// somple handle fn index(req: HttpRequest) -> Result { @@ -46,10 +46,6 @@ fn main() { .bind("127.0.0.1:8443").unwrap() .start_ssl(&pkcs12).unwrap(); - // Subscribe to unix signals - let signals = actix::Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); - println!("Started http server: 127.0.0.1:8443"); let _ = sys.run(); } diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs index 39e7978f5..76cc29e99 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -18,7 +18,6 @@ use std::time::Instant; use actix::*; use actix_web::*; -use actix::actors::signal::{ProcessSignals, Subscribe}; mod codec; mod server; @@ -213,10 +212,6 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); - // Subscribe to unix signals - let signals = actix::Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); - println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); } diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index b7433203f..ba88cf211 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -10,7 +10,6 @@ extern crate env_logger; use actix::*; use actix_web::*; -use actix::actors::signal::{ProcessSignals, Subscribe}; /// do websocket handshake and start `MyWebSocket` actor fn ws_index(r: HttpRequest) -> Result { @@ -71,10 +70,6 @@ fn main() { .bind("127.0.0.1:8080").unwrap() .start(); - // Subscribe to unix signals - let signals = actix::Arbiter::system_registry().get::(); - signals.send(Subscribe(_addr.subscriber())); - println!("Started http server: 127.0.0.1:8080"); let _ = sys.run(); } diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md index c42eaac1c..3c347ce65 100644 --- a/guide/src/qs_2.md +++ b/guide/src/qs_2.md @@ -61,7 +61,7 @@ connections. Server accepts function that should return `HttpHandler` instance: || Application::new() .resource("/", |r| r.f(index))) .bind("127.0.0.1:8088")? - .start(); + .run(); ``` That's it. Now, compile and run the program with cargo run. @@ -69,9 +69,8 @@ Head over to ``http://localhost:8088/`` to see the results. Here is full source of main.rs file: -```rust -extern crate actix; -extern crate actix_web; +```rust,ignore +# extern crate actix_web; use actix_web::*; fn index(req: HttpRequest) -> &'static str { @@ -79,17 +78,11 @@ fn index(req: HttpRequest) -> &'static str { } fn main() { - let sys = actix::System::new("example"); - HttpServer::new( || Application::new() .resource("/", |r| r.f(index))) .bind("127.0.0.1:8088").expect("Can not bind to 127.0.0.1:8088") - .start(); - - println!("Started http server: 127.0.0.1:8088"); -# actix::Arbiter::system().send(actix::msgs::SystemExit(0)); - let _ = sys.run(); + .run(); } ``` diff --git a/guide/src/qs_3_5.md b/guide/src/qs_3_5.md index b9f002c87..580f029d9 100644 --- a/guide/src/qs_3_5.md +++ b/guide/src/qs_3_5.md @@ -47,15 +47,27 @@ address of the started http server. Actix http server accept several messages: # extern crate actix_web; # use futures::Future; use actix_web::*; +use std::thread; +use std::sync::mpsc; fn main() { - let addr = HttpServer::new( - || Application::new() - .resource("/", |r| r.h(httpcodes::HTTPOk))) - .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") - .spawn(); + let (tx, rx) = mpsc::channel(); + + thread::spawn(move || { + let sys = actix::System::new("http-server"); + let addr = HttpServer::new( + || Application::new() + .resource("/", |r| r.h(httpcodes::HTTPOk))) + .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") + .shutdown_timeout(60) // <- Set shutdown timeout to 60 seconds + .start(); + let _ = tx.send(addr); + let _ = sys.run(); + }); - let _ = addr.call_fut(dev::StopServer{graceful: true}).wait(); // <- Send `StopServer` message to server. + let addr = rx.recv().unwrap(); + let _ = addr.call_fut( + dev::StopServer{graceful:true}).wait(); // <- Send `StopServer` message to server. } ``` @@ -173,59 +185,13 @@ timeout are force dropped. By default shutdown timeout sets to 30 seconds. You can change this parameter with `HttpServer::shutdown_timeout()` method. You can send stop message to server with server address and specify if you what -graceful shutdown or not. `start()` or `spawn()` methods return address of the server. +graceful shutdown or not. `start()` methods return address of the server. -```rust -# extern crate futures; -# extern crate actix; -# extern crate actix_web; -# use futures::Future; -use actix_web::*; - -fn main() { - let addr = HttpServer::new( - || Application::new() - .resource("/", |r| r.h(httpcodes::HTTPOk))) - .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") - .shutdown_timeout(60) // <- Set shutdown timeout to 60 seconds - .spawn(); - - let _ = addr.call_fut( - dev::StopServer{graceful:true}).wait(); // <- Send `StopServer` message to server. -} -``` - -It is possible to use signals. *CTRL-C* is available on all OSs, other signals are -available on unix systems. - -Then you can subscribe your server to unix signals. Http server handles three signals: +Http server handles several OS signals. *CTRL-C* is available on all OSs, +other signals are available on unix systems. * *SIGINT* - Force shutdown workers * *SIGTERM* - Graceful shutdown workers * *SIGQUIT* - Force shutdown workers -```rust,ignore -# extern crate futures; -# extern crate actix; -# extern crate actix_web; -use actix_web::*; -use actix::actors::signal::{ProcessSignals, Subscribe}; - -fn main() { - let sys = actix::System::new("signals"); - - let addr = HttpServer::new(|| { - Application::new() - .resource("/", |r| r.h(httpcodes::HTTPOk))}) - .bind("127.0.0.1:8080").unwrap() - .start(); - - // Subscribe to unix signals - let signals = actix::Arbiter::system_registry().get::(); - signals.send(Subscribe(addr.subscriber())); - - println!("Started http server: 127.0.0.1:8080"); - # actix::Arbiter::system().send(actix::msgs::SystemExit(0)); - let _ = sys.run(); -} -``` +It is possible to disable signals handling with `HttpServer::disable_signals()` method. diff --git a/src/server.rs b/src/server.rs index 45d158c51..a38bb2b1d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -6,11 +6,11 @@ use std::marker::PhantomData; use std::collections::HashMap; use actix::prelude::*; +use actix::actors::signal; use futures::{Future, Sink, Stream}; use futures::sync::mpsc; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_core::net::TcpStream; -use actix::actors::signal; use mio; use num_cpus; use net2::TcpBuilder; @@ -105,6 +105,8 @@ pub struct HttpServer accept: Vec<(mio::SetReadiness, sync_mpsc::Sender)>, exit: bool, shutdown_timeout: u16, + signals: Option>, + no_signals: bool, } unsafe impl Sync for HttpServer where H: HttpHandler + 'static {} @@ -150,6 +152,8 @@ impl HttpServer accept: Vec::new(), exit: false, shutdown_timeout: 30, + signals: None, + no_signals: false, } } @@ -208,6 +212,18 @@ impl HttpServer self } + /// Set alternative address for `ProcessSignals` actor. + pub fn signals(mut self, addr: SyncAddress) -> Self { + self.signals = Some(addr); + self + } + + /// Disable signal handling + pub fn disable_signals(mut self) -> Self { + self.no_signals = true; + self + } + /// Timeout for graceful workers shutdown. /// /// After receiving a stop signal, workers have this much time to finish serving requests. @@ -276,6 +292,18 @@ impl HttpServer info!("Starting {} http workers", self.threads); workers } + + // subscribe to os signals + fn subscribe_to_signals(&self, addr: &SyncAddress>) { + if self.no_signals { + let msg = signal::Subscribe(addr.subscriber()); + if let Some(ref signals) = self.signals { + signals.send(msg); + } else { + Arbiter::system_registry().get::().send(msg); + } + } + } } impl HttpServer @@ -327,18 +355,21 @@ impl HttpServer } // start http server actor - HttpServer::create(|_| {self}) + HttpServer::create(|ctx| { + self.subscribe_to_signals(&ctx.address()); + self + }) } } /// Spawn new thread and start listening for incomming connections. /// /// This method spawns new thread and starts new actix system. Other than that it is - /// similar to `start()` method. This method does not block. + /// similar to `start()` method. This method blocks. /// /// This methods panics if no socket addresses get bound. /// - /// ```rust + /// ```rust,ignore /// # extern crate futures; /// # extern crate actix; /// # extern crate actix_web; @@ -346,27 +377,22 @@ impl HttpServer /// use actix_web::*; /// /// fn main() { - /// let addr = HttpServer::new( + /// HttpServer::new( /// || Application::new() /// .resource("/", |r| r.h(httpcodes::HTTPOk))) /// .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") - /// .spawn(); - /// - /// let _ = addr.call_fut( - /// dev::StopServer{graceful:true}).wait(); // <- Send `StopServer` message to server. + /// .run(); /// } /// ``` - pub fn spawn(mut self) -> SyncAddress { + pub fn run(mut self) { self.exit = true; + self.no_signals = false; - let (tx, rx) = sync_mpsc::channel(); - thread::spawn(move || { + let _ = thread::spawn(move || { let sys = System::new("http-server"); - let addr = self.start(); - let _ = tx.send(addr); - sys.run(); - }); - rx.recv().unwrap() + self.start(); + let _ = sys.run(); + }).join(); } } @@ -401,7 +427,10 @@ impl HttpServer, net::SocketAddr, H, } // start http server actor - Ok(HttpServer::create(|_| {self})) + Ok(HttpServer::create(|ctx| { + self.subscribe_to_signals(&ctx.address()); + self + })) } } } @@ -441,7 +470,10 @@ impl HttpServer, net::SocketAddr, H, } // start http server actor - Ok(HttpServer::create(|_| {self})) + Ok(HttpServer::create(|ctx| { + self.subscribe_to_signals(&ctx.address()); + self + })) } } } @@ -485,6 +517,7 @@ impl HttpServer, A, H, U> HttpServer::create(move |ctx| { ctx.add_stream(stream.map( move |(t, _)| Conn{io: WrapperStream::new(t), peer: None, http2: false})); + self.subscribe_to_signals(&ctx.address()); self }) } From 247c23c1ea5de36e6abe36fc5c0416ad3057ff4a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 6 Jan 2018 01:06:35 -0800 Subject: [PATCH 276/279] no need for StreamHandler --- Cargo.toml | 2 +- examples/state/src/main.rs | 1 - examples/websocket-chat/src/main.rs | 57 ++++++++++++-------------- examples/websocket-chat/src/session.rs | 2 - examples/websocket/src/main.rs | 12 +----- guide/src/qs_9.md | 3 +- src/worker.rs | 3 -- src/ws.rs | 6 +-- 8 files changed, 31 insertions(+), 55 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f3c83ccc9..6d8310687 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,7 +77,7 @@ tokio-tls = { version="0.1", optional = true } tokio-openssl = { version="0.1", optional = true } [dependencies.actix] -version = "0.4" +version = "^0.4.1" [dependencies.openssl] version = "0.9" diff --git a/examples/state/src/main.rs b/examples/state/src/main.rs index 6c247329c..395007aed 100644 --- a/examples/state/src/main.rs +++ b/examples/state/src/main.rs @@ -36,7 +36,6 @@ impl Actor for MyWebSocket { type Context = HttpContext; } -impl StreamHandler for MyWebSocket {} impl Handler for MyWebSocket { type Result = (); diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs index 76cc29e99..633c6556c 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -53,6 +53,32 @@ struct WsChatSession { impl Actor for WsChatSession { type Context = HttpContext; + + /// Method is called on actor start. + /// We register ws session with ChatServer + fn started(&mut self, ctx: &mut Self::Context) { + // register self in chat server. `AsyncContext::wait` register + // future within context, but context waits until this future resolves + // before processing any other events. + // HttpContext::state() is instance of WsChatSessionState, state is shared across all + // routes within application + let subs = ctx.sync_subscriber(); + ctx.state().addr.call( + self, server::Connect{addr: subs}).then( + |res, act, ctx| { + match res { + Ok(Ok(res)) => act.id = res, + // something is wrong with chat server + _ => ctx.stop(), + } + fut::ok(()) + }).wait(ctx); + } + + fn stopping(&mut self, ctx: &mut Self::Context) { + // notify chat server + ctx.state().addr.send(server::Disconnect{id: self.id}); + } } /// Handle messages from chat server, we simply send it to peer websocket @@ -143,37 +169,6 @@ impl Handler for WsChatSession { } } -impl StreamHandler for WsChatSession -{ - /// Method is called when stream get polled first time. - /// We register ws session with ChatServer - fn started(&mut self, ctx: &mut Self::Context) { - // register self in chat server. `AsyncContext::wait` register - // future within context, but context waits until this future resolves - // before processing any other events. - // HttpContext::state() is instance of WsChatSessionState, state is shared across all - // routes within application - let subs = ctx.sync_subscriber(); - ctx.state().addr.call( - self, server::Connect{addr: subs}).then( - |res, act, ctx| { - match res { - Ok(Ok(res)) => act.id = res, - // something is wrong with chat server - _ => ctx.stop(), - } - fut::ok(()) - }).wait(ctx); - } - - /// Method is called when stream finishes, even if stream finishes with error. - fn finished(&mut self, ctx: &mut Self::Context) { - // notify chat server - ctx.state().addr.send(server::Disconnect{id: self.id}); - ctx.stop() - } -} - fn main() { let _ = env_logger::init(); let sys = actix::System::new("websocket-example"); diff --git a/examples/websocket-chat/src/session.rs b/examples/websocket-chat/src/session.rs index f7000f416..47c0d0be4 100644 --- a/examples/websocket-chat/src/session.rs +++ b/examples/websocket-chat/src/session.rs @@ -185,8 +185,6 @@ impl Actor for TcpServer { struct TcpConnect(TcpStream, net::SocketAddr); /// Handle stream of TcpStream's -impl StreamHandler for TcpServer {} - impl Handler for TcpServer { type Result = (); diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index ba88cf211..fea8929de 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -24,17 +24,7 @@ impl Actor for MyWebSocket { type Context = HttpContext; } -/// Standard actix's stream handler for a stream of `ws::Message` -impl StreamHandler for MyWebSocket { - fn started(&mut self, ctx: &mut Self::Context) { - println!("WebSocket session openned"); - } - - fn finished(&mut self, ctx: &mut Self::Context) { - println!("WebSocket session closed"); - } -} - +/// Handler for `ws::Message` impl Handler for MyWebSocket { type Result = (); diff --git a/guide/src/qs_9.md b/guide/src/qs_9.md index 0c291237b..9c45fbd04 100644 --- a/guide/src/qs_9.md +++ b/guide/src/qs_9.md @@ -21,10 +21,9 @@ impl Actor for Ws { } /// Define Handler for ws::Message message -# impl StreamHandler for Ws {} impl Handler for Ws { type Result=(); - + fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext) { match msg { ws::Message::Ping(msg) => ws::WsWriter::pong(ctx, &msg), diff --git a/src/worker.rs b/src/worker.rs index d3bcf88c1..bb8190fde 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -151,9 +151,6 @@ impl Actor for Worker where H: HttpHandler + 'static { } } -impl StreamHandler> for Worker - where H: HttpHandler + 'static {} - impl Handler> for Worker where H: HttpHandler + 'static, { diff --git a/src/ws.rs b/src/ws.rs index cd83ea2f7..c49cb7d4e 100644 --- a/src/ws.rs +++ b/src/ws.rs @@ -23,8 +23,6 @@ //! } //! //! // Define Handler for ws::Message message -//! # impl StreamHandler for Ws {} -//! # //! impl Handler for Ws { //! type Result = (); //! @@ -49,7 +47,7 @@ use http::{Method, StatusCode, header}; use bytes::BytesMut; use futures::{Async, Poll, Stream}; -use actix::{Actor, AsyncContext, ResponseType, StreamHandler}; +use actix::{Actor, AsyncContext, ResponseType, Handler}; use payload::ReadAny; use error::{Error, WsHandshakeError}; @@ -86,7 +84,7 @@ impl ResponseType for Message { /// Do websocket handshake and start actor pub fn start(mut req: HttpRequest, actor: A) -> Result - where A: Actor> + StreamHandler, + where A: Actor> + Handler, S: 'static { let mut resp = handshake(&req)?; From 665f4edf6b3b5c2eb5a349b19e24cef70ca27a13 Mon Sep 17 00:00:00 2001 From: ami44 Date: Sat, 6 Jan 2018 16:43:59 +0100 Subject: [PATCH 277/279] upd examples basics --- examples/basics/README.md | 4 +-- examples/basics/src/main.rs | 59 ++++++++++++++++++++++++++++++---- examples/static/actixLogo.png | Bin 0 -> 8898 bytes examples/static/favicon.ico | Bin 0 -> 1150 bytes 4 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 examples/static/actixLogo.png create mode 100644 examples/static/favicon.ico diff --git a/examples/basics/README.md b/examples/basics/README.md index 45772e915..154fad9de 100644 --- a/examples/basics/README.md +++ b/examples/basics/README.md @@ -1,11 +1,11 @@ -# basic +# basics ## Usage ### server ```bash -cd actix-web/examples/basic +cd actix-web/examples/basics cargo run # Started http server: 127.0.0.1:8080 ``` diff --git a/examples/basics/src/main.rs b/examples/basics/src/main.rs index 2c7b714f4..68dc17f30 100644 --- a/examples/basics/src/main.rs +++ b/examples/basics/src/main.rs @@ -11,10 +11,16 @@ use actix_web::*; use actix_web::middleware::RequestSession; use futures::future::{FutureResult, result}; +/// favicon handler +fn favicon(req: HttpRequest) -> Result { + Ok(fs::NamedFile::open("../static/favicon.ico")?) +} -/// simple handler +/// simple index handler fn index(mut req: HttpRequest) -> Result { println!("{:?}", req); + + // example of ... if let Ok(ch) = req.payload_mut().readany().poll() { if let futures::Async::Ready(Some(d)) = ch { println!("{}", String::from_utf8_lossy(d.as_ref())); @@ -22,16 +28,48 @@ fn index(mut req: HttpRequest) -> Result { } // session + let mut counter = 1; if let Some(count) = req.session().get::("counter")? { println!("SESSION value: {}", count); - req.session().set("counter", count+1)?; + counter = count + 1; + req.session().set("counter", counter)?; } else { - req.session().set("counter", 1)?; + req.session().set("counter", counter)?; } - Ok("Welcome!".into()) + // html + let html = format!(r#"actix - basics + +

    Welcome

    + session counter = {} + +"#, counter); + + // response + Ok(HttpResponse::build(StatusCode::OK) + .content_type("text/html; charset=utf-8") + .body(&html).unwrap()) + } +/// 404 handler +fn p404(req: HttpRequest) -> Result { + + // html + let html = format!(r#"actix - basics + +
    back to home +

    404

    + +"#); + + // response + Ok(HttpResponse::build(StatusCode::NOT_FOUND) + .content_type("text/html; charset=utf-8") + .body(&html).unwrap()) +} + + /// async handler fn index_async(req: HttpRequest) -> FutureResult { @@ -68,6 +106,8 @@ fn main() { .secure(false) .finish() )) + // register favicon + .resource("/favicon.ico", |r| r.f(favicon)) // register simple route, handle all methods .resource("/index.html", |r| r.f(index)) // with path parameters @@ -90,8 +130,15 @@ fn main() { HttpResponse::Found() .header("LOCATION", "/index.html") .finish() - }))) - .bind("0.0.0.0:8080").unwrap() + })) + // default + .default_resource(|r| { + r.method(Method::GET).f(p404); + r.route().p(pred::Not(pred::Get())).f(|req| httpcodes::HTTPMethodNotAllowed); + })) + + .bind("127.0.0.1:8080").expect("Can not bind to 127.0.0.1:8080") + .shutdown_timeout(0) // <- Set shutdown timeout to 0 seconds (default 60s) .start(); println!("Starting http server: 127.0.0.1:8080"); diff --git a/examples/static/actixLogo.png b/examples/static/actixLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..142e4e8d57704b6779d54f979dcf50764906d9fe GIT binary patch literal 8898 zcmWk!1vngT7{~lEJ9Er1rcF)f#OXZU-EF$N+0+b^6T`%5yXl^uo}F&nOn2A+o9A&J zj_K&s1qwBZmH*Z!pZ^5K~?|L@3#2TVdEXN}o9Hmvw zUgf#X=c4CLW5-7P(dLiEYCnfkdHxq1&b@I+FI^}v=OR>bOBZ=7zG5f>?Bc6L_m{&GyACz|@LN%e+%6KCs(tB)_}c-l-% zwY0R1jdwRT8hrLmR8>{WOT4_ia>PP{ot;O!x>k2)k>xs-u`w~V{(nwRPSQ<-1$lXi zo;*Rmo@OVGv)*7di+@3fEG{k{6TKep>dG%Hw6wGPTQ_ZjB>HGjDH?Re%g%|7jh&<7 zb9J)0vSOC6X6H{9Z4NIwioh4Ob8+!;b^W`ycXoIUHMxAR^G`njM0LqUVq2)u?-(Ybh-Zi{`3Zh8zt%D;-Z)! zPMefb&y(HNB~hf&1eTJMlLPPa=$C@!B$st?aIm*GKOf)IX!8WwW0bC`ahpG?;&(pN z*4`c^0fG4xj0_T8UtbUQWZV&KT&eRWI$9Lcy%dqIZ)&k~H$9t=H^xG)HcPs@yK$omkVqaOp}C=6<2*qaR=jW%V((ZeM3mXJD+akI+Eu zNhw#Ec2sE~JcSldx2Ko=x7jqqRtK!O19TrmVlRwR* z<*qfKA+&@oI7Ev4|mvk9K-Iv+P$vFf^@$XHOW!Y)z!7# z-^wgNIeSa4ZXr9&-szb}>>Zw)$q^CV&0gC*7PYf-Ws{qI zeSJH|*u@!ndFeyH)3{Rxtpr?_q}6#}z8o4G8(TuvR1g2z*tkDdMMW85tRHt9(s03=OZRJMOfpp)+-4 zXQ`R8g`4~P{rCK&5b*oEySqc_9LvpKuO%fh-gGCyi}1&cywrKgl)>rR*)fSCIM~_u zc6a$GE`PaS&dlh^%fB@)H#g0cfMRynbeVBdPHcL@K+oRZf^{}rU$wQhDZZXKJ^DRl zY;24ZzR(t+TRm-<>q-^P+cFg2y_)eIf-M2n4ERBaA@RPdioL+}aHGzLV&__YAx&5uY?VVT$A0P3%fm`01z(5+)+Du(-Imu&_Yl>gML=?fr)}^xRS?N2N$(WMo7c z%nxPdk#hA^`h(JggF)-NH?fie_!84V0%l8aAH5tZR)Ho7*k-ap&><9 z;%b9B7EaFb-d-7M=)q+mf50KwnRMYqsTNC-h9G!RFu~^Mg}J!$)tEAV7*~o`>y&FV z5joV&O-@es_V%8&%&;Jvnwkpb62Yx%Zfv}`z6MvS%B0Qz^5}PX#QjE4tqWt&bp1tldYxRZl} zgOigsLW^I8*h^@!vs2v8u2iK&vmC{pSy5P6SW;qZWo6~;%q}8wQGz+>xPfs2(Bemc9goMP_ z+tuH{8G%rsP2%O_o23;L5NH66ySlo1cNdJ*Ho^%%IzIOB@NoL7H)k#KF)z=>G}QZ{ ziHXUxXV3KY^_P~G=vD1FNxu&bA&5;(bZnS;^3^gjGn>r2qX!2E$pxH0q^14fDH|Cc zHfr-v{i`d>P+0%n@%Q&ssMTvrz89RFoG)L#WMfliB4Tf5T9}!61%oM{5~ENk>^cMX z^w*b9FeSi?1)S{+j*eE9l{I_s&UJR8X{v7g{Yy$nXmR}|w37Q6;KbQk{o?2&iT8<# zsyaG4va-?g^cY_mKBRpZZ%mghL?96Q9l@}Phn$c{QQxw&vp;+wLTLGeZGgIv32;kM zLou73cQ&T@$EdBJK#SFVr;~X6v z41flN!<#)fe~f$-aQa!|Dl?r%=<1lG>GA4$t)8Re!?3Whj>{1jlfXk6IEII-YiUJk zjlQsF45zf{w*O_&1A~3YZl2Eg@IC8f%`q#|jJ zaa45l58wFC&Q4v^Ag_P0X42J`l;mt-$cFRV+FFZa_0YG)d)I%vvLQS?ywqg-&!0d0 z`>LlbTtq8F*#@zaYDBD_Zz-0q@YQt1qR-{tZq%t$iT7asEK7ufRrGS zDSe#77PSQh1xvo`2|s^o!Gj_UD*2BCCDe%|pp`m`o4e{6nnPA!khWvrf4fg%Jb&Ep zhR**HTxdv@POV-=0=dB6e8bqtNP>opau??g$>pUEjFS2Aq&>*b&+nv4nZ{N^{&~JyuZcirD(ug}#V$5pw62hzzIrVrI^amYx6({suc6c%1LUCQLKMH@*4m$LD7{0sM{8-Qf|$SFef zIf1NR_VLi6-=fz$$T8S?_O6a)f&J{+?mTlUUCN+*t1(IM#Dv?%Xbwl)N%HPC$CD>d zczAe186ONnx@$gvrZ?Q`**MM%*lSu?FfzpLnsBJoK>XZNqQL$!Az^oB>1AYtBXcRD9a&}-#hV@Bfl^sqwRdX6K~bx7wbnwRjJ=@ zScQayczHi)IDuw({QOm?eEM4tniV(SAYu=?-@Q+^I@RRRsku9o8iar}*co@Lnx5wDcjE z?*a9TmxFx?oVly8XSJG?^Re^mb zKyE~HqXUYiEX&8(m?DD48BioJ@RwM1;YP_8qU)MgCh{`H0&WGC$oH9sVNZnvv>%pM zzCRuWcMvpd(D|ZITrPl&g&#lG+Qh+!nVFfmI9j``>eqwUGB!BjM$MjxPoKo|)y7M? z;(F&gLc|gt4I;|R=?vVBKBn1t(=#*2Bo80#B{65Nrd75DoS&VZvdyv4;BbEp-IVb0 zIj=Nq0EesZRG~x9oH96?EhwdGcCj;0DbZhqIms=W=YsI*?eEu2@UnAq%E`+5k{|vD zkTdR=FXNW#0Gs>t=@Sw;xmB%0sf5t_F%<+b+IwfF%hz#WVaE{`=8&F|VJCR$;pX(A!oK$^T5g#<1(>%Yasi}WD9gZY#kAYtk4Gql+87`!|u{ZJX?fOwHZh=g(*Y=b? zOr=}=`-d|-&^NHN8OxI;SyxxiB%+Fe;n~^C&ael(@wx5o?EqRnt9QuI{2;vy3^odE zMdi6_62G{*y2=<)LhC*#R{m(mstS%<^#0~p`j~$^2e6lc<1sN+)XImP96aZ2EIEdh z#U_vbEmdY_X6aN2F7DCcVZJ{{-$$~<{`n>k#utjvxLyEkJT@$~k*+I)!^29*@qYP( z<71>-TQm+KW1naEI|9bAzF%3*x<>`{jEtIOC=CsbftHt&nLr{j<^>Wi;Is4brS|!> zF)_sw&X#BA+9=?0akAAA1Z!a@VCN!@p$9|)z-CoyjccC(vm0w2X#O}&~yjVI858^;^tH!9i7@PQ>5YL?G2|-j*iBH;(LXOi(7uh&Mzy2%dN=O1>gF=uX@G6pEkB(cq&7k z-&zshrxga^@i`P)^Vg`Lv9S?I4zM&E8yna&D2>Jsu>qy(X?#39)bunUr-}?I_Pk`B zb3HvhGt9@3n7&3)x(A;zAHPX?|K2RKIwjkXkI$hlYivWaIre$J8X)NHrNe4OIXO9O z930gM#NqL=A|Sg;N)QNy*K5~;T=n*9yE-l|t_xQ0Y6fm%LfxA)^R%%kqL7dfSNzdB zdwVVG;zKrb>8T7P0$=RzwGT{CP+v_= zT-&Vi#W(I3taQ+4QC;NER00D<@b0Ao>=FqG2zclA0EGoh#50!$zt7~dq|yclha#mH ze_L#QKS4_Jh=M1oe_da8jUDdc!IOZa;r{Q8g?_GNC~O2|`UM1}xLk@$OG`(>g!)ED zM*}dq(ALJst%*7ahyJUVkx>7zg}YGjN=WF(`)40VmRkLgn|cSg+tJj2Hy)`auzul+ ze}_Z38=*nholg>~+2?tV)9tFKM_Qzs-MuPucd-r#*5!m`>mdhwddutSs*+rP+`!`A z^+UUA{G?CQ+b??1gSJbDh6e|kv&^sw37wbQ@%}2JtH{gaG!DOdKDxKqlIOejkf-iO zZdzDKNc8G&;pOFJXhb~{iG)Ee`5K%F*v>0W+On1?ri+VzG{(9qV~$r@K@an13*EmTaR!Dt}?}`(Rd5h!=){UKNWCgC1)o~S}s^G43F6$CE^2BR8+9>DFb(E z`zxkq=V(Y(;Bbu0oW`amF6S>=={q^$jmeoa zvLTHI?UN%K)ZEnMH&M{lX5D4(z~ZEUTS=2J%l45P2jd}pZ)Zou@90-gPb5m=IMFJa zT3%ZlW$4o8wfgP9^9wmvsW`O&6AUW5d zvOVzfh}@-BU0pqYa&wseJYp&YXixx$1?}yWH^?tsjT3~)j+hDx3iaep(^bZbmd~2O zb+SWBk{$Sp5Xb%ZMOr{e=&GUPmW`d=d4H*`_}Gj;*Q=(c=HTY?SSx5MbL3%c0n^|L z4WhzoffN0$7R&AZV@fL)d$vKF&pP&wj;%r0-YP2pPSdT8s&BEZ<7(?AnFJODD^2j# zD_&k+PUkgC%sK;Q;o7tpti*9;YsFeDV|ij6sql{FUO*650pmK+naos9P$lxb=Is1* zCcj?c^5 zTuK?@?`xegleMq+3>Z3pZ+YEX-*tqqD2f1_xIa^g4(cah%pfWmX#=SG_Zv# z=KzE3_>VF)6|?8XqsRa{I(l+{5&EwU4XR2?GmUwS(dMBX%GA)$B_;cFbwlo&b5zX1 zP|VA#tG(q8F+su0zY~RVRnp^e>d45RGoYK?)_y7Uu~~5dc{lMZCo=ZvC~tqxdMJnn z2gA=^R8$lksD-0rh4#q8ceFkmB#?6w$%RVbwVbEBIOBT~s9~^0%29@sDQr^x|Nc`j z=>TR-vX{X&{7sh`n*FznTqxQpePLT$4t=>PkZuXT4Ws%j1e{sONJ(oBr>M6}$r7S_ z7$;+gwOCNVgHwf$kT3h-f6BFb@9Pub?^kKXL{(uuc(O+8lwCS9Hm0hg!tQTiX}O+6 zC--(PqG=dNeSloy`zlR#6D+B?pRwre>J03)pH5V7+S{Mae@@T=W#fSonco=j!M&&B0-aj=pEdlc+QS{2r%Bm+egf5(#D)@pXVW-Y#Zz10ak4Aye zR&2MM$bZgKTl;C7awsnuUc%Q33JQvU+eN@H+J^0fMVV6JbCgf41zeH?J-v5H-I7yM zC=j->x-6-9czC?djOjzbPP8}j^+8|nSRNYg?v5~_b*|KzUs_Ue(PPMjUte5=%Bhb> zk8MoGp_fd<>c4&+HxzU8@Hjp=;JmwT8wP{iH2CuO-@kTcew&<-^6Kgm^{2`7ZoZ!U zD2z>s>VS&M$|ADKVw8hD1jf$J4h8^6>p?i;%_2U}?##?gX=y323jnh@Iy#Dpc_=Ow z04N;zd4YPT&3p2wC%Y*aUC9?0zKo2FW!r7Q8evJ*fJbw4aZM~P;`wra9638Wf&-%j z4kwK3wYfucL~81?)&TJO^T#GeTEo<|qf`P@MjfHm3@p}zCZcfar#ug}3u;UGNi^G2W{Nl7aBL{Ii}-@1@tnta$DUt&izHj*9;3;XPa+Fe31Wu&E9w|+@6@I-V`r@|%T zdzoU>K72q^`3bf&0K*DuZIk$Jjti!{KaJ-y@zXF!BxyA0NAz63+pAL<#*j#_Ue1cT zy1JSg==ju+g#|~UH`J>`JhlU>D_mrOk1^VATfx=2^w=PVIayg@ipZVz#*uu?${N^E ziR9K~BFYOodv@Xc7MLQs)h5QC1v=^`?RIB)f`v^SBvFB@5!sJO*iSg>z5Q>myj-co z6J$NsdLAAnC3E(zoor5OG429q?iYU&I8#;8OREX@&V--wYR1OO*9MppcQ+eiDLwzj zfl-ThLh^_?aK+*{+NlRm&1HaGNlQ!9Q8V?-$jt>{!9B5^CQn~|e|Jv3c(vH#lSmT5~sY%$4KGm`YZcnihDj3cpoq?DKF7bac)?XsW zHaZUb$U)nI-f0O9bSMM5+D2dB)P=+#^VLRaF&m23^`O8QR|I(i7euCWd^?dh+c% zubrLU+uTl9xtfss>t!%5O>+e`0ePy1W`l236R8?R?(Xl$?#Bn}X_Msd#+L7$6@!S2 zoWbQY4LKf}McUdYegIJM;K2hOYnvh$K-<5(SgUVqqX=9(GL4s}t?lKR>+OxQ%XKKq z@$>a<1qQQ#^8zWbz_PNkva>%syaRn?1UbD;x*K{GaK@^w45x2?w@lq8Mer{ZEPs}0R4Ngx6x5Y6{NXnzm~CnriODvxn-adB{< zG13@ep<^2!O-6*8IQjbc;nYhQdwYA}n9Fsm`(W4jRH6`WyZLs+?mZ#6$@Px2l7mpG z5gTOSrMq}a*KM{Lo4rnzkpjvct67Y1bR#b$`0mE<;24x~*>58!G!*T+sTHQR(BiZI z&#J(R4SNq1YF?hIJw-%#q@1j5+1nt3N^XEiUD#AJe;+hyWn^UmB9_SX5F&7;w)ieL zd>HL1lm~e4pi}y^c>AP=04E(xVYK&sJUTL>u563W!c(N7uc0A1rdv`~wbv8#gqP5t z{pnAKQ?MHoZEczTTOgz1#ssCqAPA7dA!G?xQ&)Eb?R}sfUW5^CE|l}{kBW+lzB-xt zlG(ZXMSQO)Egc{(WTUJ(Iqaw6Atk%w{G?M3Y5@eUW84H`cMTA7p@vQ3=X(uBNzV|K zmH+hbGu7COPo(7J*T5(FHc({`?%Ike&)=n6U^%8;F+sYYKYo);xr{=c-d*oE)YY+_ zJ~RSD@eNh5yw^Dx3`H&E~=7@QiT%&>`@L@)>hZm@HDdsJX+ucEctCvz~>X5 zbcK@V1NdG(`Sw<)oQ;VI2y2+@ia6-GWaxa3u#ZaiD+!J}AbT{Q@(@4KHR4STOQABAY=$f?NT)#I2IvxJOkNlQrL#%wMQlh!BxB7z zd$`{Jf^ZO6U39dxz9LHx(z8Keu(t<>jk~7i6FU8Z=9U(9qPn0*4-KuOU1TgRX(iw|ct*aP(H5`TcgJPQ)g1{-B^ z@ONNFw6~kE65HC?R2ep?FcB>Y3<9HAz6j`Cje|xy*RKXn;D931pCzD~LxB?s0*meK z>#g!?;2UXbXn;Iu_w#2^6CR$)sVN;z&B^icPa24f5k-5hStPHL_Idh zgkpKoy#3wXHpkg72L}f$D=W##$$(kTRiowMYXi3a^C!>{QIU~7{rz#h7F1MJAUGWA z3>^ZrxT2$@131XQb+@tUJF|-9g!K3KgCl9HuC8ur3Gnsxb#xpqb#Q<6T=uNyi{{$0m^KCaiZ_s;=VC&~ZX-X?$k{5c`v+V=L)#<8DB zZebyvb5@u+sR3FyCy@*RCdgdDOfH-l23~$*B0dCia(=$t;-ku*&NE+TWNe&B3ENs* z!;paHhQQg=!SkM;o&Y=O>FFI8n^9-2eUKRmc{P^Qj@w6 zjAiH157*SlH@JbhX8SES^gIkrVq@1S!%R{I2uo6LD#@B1;oN*C2WB49XkZNi0?r7< ziH(h=OwPxFf^;Ay#S#Pt-@h|3GLFQrpKp-YRS#3+SRiMM6Ia*Qk-!21PD_$}q`97% z*$OsQV2##Pg*oX`N639N-oJX)JI(8WOaRP}ECz~jYO>4!5EiEYOx=w?e~5{RC81v( zP-Vb9tTt|$vXhCV##vr=M+_)YLrHzIpWsF%!p`(SL=Y5o+pruQ*Z<*jQIYzHob-e1 z(J@!L`IQywX!D^owkJ54HW6W6X1;$iZPQ}$a~c}XL8@rmq`m_DnSlYE*a8U&iFlV* zyO_nvMb{>yKaXHR%)1Di4!b&otJ}(Lj3xhDykdg!k^PJ{UZqjvn!2rv}dmC+= z3*yHB!hnRcoVt>UOi!N%C@W1n(d4mFF$aJ-Fk?=Ah?uelROO2Ue?np+hynZivBJb% zZ!X*d&Q~8lehkv77UFu5w=pq--aC3yQW5$Qk06k*J{00L`!^6wF)=aCL_tQz5-_*# zKsJ7Se%_xbEhm?*V!7?=HLu#f%V^A-{l-!9f^ z5;_yj`s2{~XSqqk)6=>PDH2e6;<)4EW8F#v>oTtEt1DS)X+69_kjUMks)L_CdnPC< zdbiiIPgv9fxNpGi>2x}q;nd<{5J;rTdA{+zp32zM8C`V+(Ir4ul|0b|VFTb1#PnK3 zQ_eByy?IRGU&9BU#l^?>adT5pQVIl>2ie3hai1Bf4RSV3ol%5xAJ~5&WeyajGF3q~ zPjf)J!GNBrF@KQQ54LuEbaVxFz*-ruDsmX9$I7q$+MM+4+RM5{vez>N&YKP&C!$}o>AWTVugcwU}ThiX!`gK5-@D1 z`TERydElwIEQ^MToB7kE4=o~X6F2SpEo!;BxzF~OUHqMZh?|YV=TVfh0S5F!Bf&Jz zjN{oT0EL|y^I29f}YX6YbI^W%eHH&ISBC%Kd%hF@{l**g7fr22DXmMY>ATEc|~unt+o4 literal 0 HcmV?d00001 diff --git a/examples/static/favicon.ico b/examples/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..03018db5b5de10571c2038b583fd329f9533bb58 GIT binary patch literal 1150 zcmbVMOGukR5dI6j^&&m=++!~m6f`LYjY?EB2*#pCZPZ#cMl6Vri=?8pAgNlbIzn?717ZzMGfz9aBv`E zl%lw}7-wf^n4FwMYHBLiO94F|FIrkYaqR2sYhDSjRK8+gQc%9>>FFTOC=cR#eSMvM zDW9UEw_HP4*Ee)`cZ>XTk(>J(q0n#knVz0z%+%Br->X)uv9`9xHWDV2N#HwiczB5Z z{(dMFO0>1LMeKwp%EljE{Wo>FHto($Z4IZnN2tnVHGl9v>g0 zprC-?FC6~cgO!y=?Ck77uh%0#{|%@wY0z3&Scr;>3S3@ZvW_^2i;IiA2`McWE1H{I z1Wy_0=;&ZS5_Y>C@$vE8)3P!Y3Zb#FQE;rp;NT#ucXxM@m8BIum4VI8U#zoOEPRjI zY{u#7DeL6^r5B_-(X?L}Q(J*uleh+HgOqe7uTdwV6Q>)c-Z~A;b5MMN83?J^#)aTSQ#F5|s6LWKOSX^Ah#>NJ7MK<#J7c2h< H{&)QYdQcr1 literal 0 HcmV?d00001 From 71da72efdbbc3c1607356e38a7cf0bcfd3ce6d8d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 6 Jan 2018 22:59:39 -0800 Subject: [PATCH 278/279] use general context impl --- Cargo.toml | 3 +- src/context.rs | 152 +++++++++---------------------------------------- src/server.rs | 8 +-- src/worker.rs | 4 +- 4 files changed, 31 insertions(+), 136 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6d8310687..5f9f27734 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,7 +77,8 @@ tokio-tls = { version="0.1", optional = true } tokio-openssl = { version="0.1", optional = true } [dependencies.actix] -version = "^0.4.1" +#version = "^0.4.2" +git = "https://github.com/actix/actix.git" [dependencies.openssl] version = "0.9" diff --git a/src/context.rs b/src/context.rs index d6261a3a0..30d03e8ae 100644 --- a/src/context.rs +++ b/src/context.rs @@ -8,11 +8,11 @@ use futures::unsync::oneshot; use actix::{Actor, ActorState, ActorContext, AsyncContext, Handler, Subscriber, ResponseType, SpawnHandle}; use actix::fut::ActorFuture; -use actix::dev::{AsyncContextApi, ActorAddressCell, ActorItemsCell, ActorWaitCell, - Envelope, ToEnvelope, RemoteEnvelope}; +use actix::dev::{AsyncContextApi, ActorAddressCell, + ContextImpl, Envelope, ToEnvelope, RemoteEnvelope}; use body::{Body, Binary}; -use error::{Error, Result}; +use error::{Error, Result, ErrorInternalServerError}; use httprequest::HttpRequest; @@ -30,13 +30,8 @@ pub enum Frame { /// Http actor execution context pub struct HttpContext where A: Actor>, { - act: Option, - state: ActorState, - modified: bool, - items: ActorItemsCell, - address: ActorAddressCell, + inner: ContextImpl, stream: VecDeque, - wait: ActorWaitCell, request: HttpRequest, disconnected: bool, } @@ -46,23 +41,17 @@ impl ActorContext for HttpContext where A: Actor /// Stop actor execution fn stop(&mut self) { self.stream.push_back(Frame::Payload(None)); - self.items.stop(); - self.address.close(); - if self.state == ActorState::Running { - self.state = ActorState::Stopping; - } + self.inner.stop(); } /// Terminate actor execution fn terminate(&mut self) { - self.address.close(); - self.items.close(); - self.state = ActorState::Stopped; + self.inner.terminate() } /// Actor execution state fn state(&self) -> ActorState { - self.state + self.inner.state() } } @@ -71,31 +60,24 @@ impl AsyncContext for HttpContext where A: Actor fn spawn(&mut self, fut: F) -> SpawnHandle where F: ActorFuture + 'static { - self.modified = true; - self.items.spawn(fut) + self.inner.spawn(fut) } fn wait(&mut self, fut: F) where F: ActorFuture + 'static { - self.modified = true; - self.wait.add(fut); + self.inner.wait(fut) } fn cancel_future(&mut self, handle: SpawnHandle) -> bool { - self.modified = true; - self.items.cancel_future(handle) - } - - fn cancel_future_on_stop(&mut self, handle: SpawnHandle) { - self.items.cancel_future_on_stop(handle) + self.inner.cancel_future(handle) } } #[doc(hidden)] impl AsyncContextApi for HttpContext where A: Actor { fn address_cell(&mut self) -> &mut ActorAddressCell { - &mut self.address + self.inner.address_cell() } } @@ -107,12 +89,7 @@ impl HttpContext where A: Actor { pub fn from_request(req: HttpRequest) -> HttpContext { HttpContext { - act: None, - state: ActorState::Started, - modified: false, - items: ActorItemsCell::default(), - address: ActorAddressCell::default(), - wait: ActorWaitCell::default(), + inner: ContextImpl::new(None), stream: VecDeque::new(), request: req, disconnected: false, @@ -120,7 +97,7 @@ impl HttpContext where A: Actor { } pub fn actor(mut self, actor: A) -> HttpContext { - self.act = Some(actor); + self.inner.set_actor(actor); self } } @@ -154,7 +131,7 @@ impl HttpContext where A: Actor { /// Returns drain future pub fn drain(&mut self) -> Drain { let (tx, rx) = oneshot::channel(); - self.modified = true; + self.inner.modify(); self.stream.push_back(Frame::Drain(tx)); Drain::new(rx) } @@ -169,120 +146,43 @@ impl HttpContext where A: Actor { #[doc(hidden)] pub fn subscriber(&mut self) -> Box> - where A: Handler, - M: ResponseType + 'static, + where A: Handler, M: ResponseType + 'static { - Box::new(self.address.unsync_address()) + self.inner.subscriber() } #[doc(hidden)] pub fn sync_subscriber(&mut self) -> Box + Send> where A: Handler, - M: ResponseType + Send + 'static, - M::Item: Send, - M::Error: Send, + M: ResponseType + Send + 'static, M::Item: Send, M::Error: Send, { - Box::new(self.address.sync_address()) + self.inner.sync_subscriber() } } impl ActorHttpContext for HttpContext where A: Actor, S: 'static { fn disconnected(&mut self) { - self.items.stop(); self.disconnected = true; - if self.state == ActorState::Running { - self.state = ActorState::Stopping; - } + self.stop(); } fn poll(&mut self) -> Poll, Error> { - if self.act.is_none() { - return Ok(Async::Ready(None)) - } - let act: &mut A = unsafe { - std::mem::transmute(self.act.as_mut().unwrap() as &mut A) - }; let ctx: &mut HttpContext = unsafe { std::mem::transmute(self as &mut HttpContext) }; - // update state - match self.state { - ActorState::Started => { - Actor::started(act, ctx); - self.state = ActorState::Running; - }, - ActorState::Stopping => { - Actor::stopping(act, ctx); - } - _ => () - } - - let mut prep_stop = false; - loop { - self.modified = false; - - // check wait futures - if self.wait.poll(act, ctx) { + match self.inner.poll(ctx) { + Ok(Async::NotReady) => { // get frame if let Some(frame) = self.stream.pop_front() { - return Ok(Async::Ready(Some(frame))) + Ok(Async::Ready(Some(frame))) + } else { + Ok(Async::NotReady) } - return Ok(Async::NotReady) } - - // incoming messages - self.address.poll(act, ctx); - - // spawned futures and streams - self.items.poll(act, ctx); - - // are we done - if self.modified { - continue - } - - // get frame - if let Some(frame) = self.stream.pop_front() { - return Ok(Async::Ready(Some(frame))) - } - - // check state - match self.state { - ActorState::Stopped => { - self.state = ActorState::Stopped; - Actor::stopped(act, ctx); - return Ok(Async::Ready(None)) - }, - ActorState::Stopping => { - if prep_stop { - if self.address.connected() || !self.items.is_empty() { - self.state = ActorState::Running; - continue - } else { - self.state = ActorState::Stopped; - Actor::stopped(act, ctx); - return Ok(Async::Ready(None)) - } - } else { - Actor::stopping(act, ctx); - prep_stop = true; - continue - } - }, - ActorState::Running => { - if !self.address.connected() && self.items.is_empty() { - self.state = ActorState::Stopping; - Actor::stopping(act, ctx); - prep_stop = true; - continue - } - }, - _ => (), - } - - return Ok(Async::NotReady) + Ok(Async::Ready(())) => Ok(Async::Ready(None)), + Err(_) => Err(ErrorInternalServerError("error").into()), } } } diff --git a/src/server.rs b/src/server.rs index a38bb2b1d..501445174 100644 --- a/src/server.rs +++ b/src/server.rs @@ -556,12 +556,6 @@ impl Handler for HttpServer } } -impl StreamHandler>> for HttpServer - where T: IoStream, - H: HttpHandler + 'static, - U: 'static, - A: 'static {} - impl Handler>> for HttpServer where T: IoStream, H: HttpHandler + 'static, @@ -682,7 +676,7 @@ impl Handler for HttpServer if self.exit { Arbiter::system().send(actix::msgs::SystemExit(0)) } - Self::empty() + Self::reply(Ok(())) } } } diff --git a/src/worker.rs b/src/worker.rs index bb8190fde..7b996a430 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -177,7 +177,7 @@ impl Handler for Worker let num = self.settings.channels.get(); if num == 0 { info!("Shutting down http worker, 0 connections"); - Self::reply(true) + Self::reply(Ok(true)) } else if let Some(dur) = msg.graceful { info!("Graceful http worker shutdown, {} connections", num); let (tx, rx) = oneshot::channel(); @@ -186,7 +186,7 @@ impl Handler for Worker } else { info!("Force shutdown http worker, {} connections", num); self.settings.head().traverse::(); - Self::reply(false) + Self::reply(Ok(false)) } } } From 896981cdf89e0bf23a98f100eb3c94bbdfd6adc7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 6 Jan 2018 23:22:10 -0800 Subject: [PATCH 279/279] update examples --- examples/websocket-chat/Cargo.toml | 3 ++- examples/websocket-chat/src/client.rs | 4 +++- examples/websocket-chat/src/main.rs | 3 ++- examples/websocket-chat/src/session.rs | 4 ++-- examples/websocket/Cargo.toml | 3 ++- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/examples/websocket-chat/Cargo.toml b/examples/websocket-chat/Cargo.toml index f5534f9be..a155e0e11 100644 --- a/examples/websocket-chat/Cargo.toml +++ b/examples/websocket-chat/Cargo.toml @@ -25,5 +25,6 @@ serde = "1.0" serde_json = "1.0" serde_derive = "1.0" -actix = "0.4" +#actix = "0.4" +actix = { git = "https://github.com/actix/actix" } actix-web = { git = "https://github.com/actix/actix-web" } diff --git a/examples/websocket-chat/src/client.rs b/examples/websocket-chat/src/client.rs index f1e52e051..c57825fc9 100644 --- a/examples/websocket-chat/src/client.rs +++ b/examples/websocket-chat/src/client.rs @@ -67,11 +67,13 @@ impl Actor for ChatClient { self.hb(ctx) } - fn stopping(&mut self, _: &mut FramedContext) { + fn stopping(&mut self, _: &mut FramedContext) -> bool { println!("Disconnected"); // Stop application on disconnect Arbiter::system().send(actix::msgs::SystemExit(0)); + + true } } diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs index 633c6556c..aec05ec74 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -75,9 +75,10 @@ impl Actor for WsChatSession { }).wait(ctx); } - fn stopping(&mut self, ctx: &mut Self::Context) { + fn stopping(&mut self, ctx: &mut Self::Context) -> bool { // notify chat server ctx.state().addr.send(server::Disconnect{id: self.id}); + true } } diff --git a/examples/websocket-chat/src/session.rs b/examples/websocket-chat/src/session.rs index 47c0d0be4..b0725fde4 100644 --- a/examples/websocket-chat/src/session.rs +++ b/examples/websocket-chat/src/session.rs @@ -52,10 +52,10 @@ impl Actor for ChatSession { }).wait(ctx); } - fn stopping(&mut self, ctx: &mut Self::Context) { + fn stopping(&mut self, ctx: &mut Self::Context) -> bool { // notify chat server self.addr.send(server::Disconnect{id: self.id}); - ctx.stop() + true } } diff --git a/examples/websocket/Cargo.toml b/examples/websocket/Cargo.toml index 5c6bd9889..e75f5c390 100644 --- a/examples/websocket/Cargo.toml +++ b/examples/websocket/Cargo.toml @@ -10,5 +10,6 @@ path = "src/main.rs" [dependencies] env_logger = "*" futures = "0.1" -actix = "0.4" +#actix = "0.4" +actix = { git = "https://github.com/actix/actix.git" } actix-web = { git = "https://github.com/actix/actix-web.git" }
  • {}/